Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"name": "t3-pstack",
"source": "./plugins/pstack",
"description": "pstack for T3 Code: rigorous agent workflows whose panels and fan-outs run across model families through T3. Original pstack by Lauren Tan (poteto).",
"version": "2.0.0-rc.2"
"version": "2.0.0-rc.3"
}
]
}
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:
run: bun test --timeout 10000 tests/session-hook-windows.test.mjs
env:
PSTACK_REQUIRE_WINDOWS_HOOK: "1"
- name: Test the installer's CLI launch
run: bun test --timeout 30000 tests/install-cli.test.mjs

forks:
name: Fork registry
Expand Down
24 changes: 24 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@

This file is the release changelog, with one `## <version> - <title>` entry per release, newest first. The Cursor-to-T3 rewrite rules live in [`tools/substitutions.json`](tools/substitutions.json), and the [sync boundary](CONTRIBUTING.md#the-sync-boundary) in `CONTRIBUTING.md` defines how a change to upstream's skill content is declared.

## 2.0.0-rc.3 - One installer and doctor for Claude Code and Codex, a trusted Codex hook, and one sheet per machine

`npx -y github:chhoumann/t3-pstack install` installs or updates t3-pstack in every Claude Code and Codex home T3 can run, and `doctor` checks them. The homes are `~/.claude` and `~/.codex`, `CLAUDE_CONFIG_DIR` and `CODEX_HOME`, each `claudeAgent` and `codex` provider instance in T3's `${T3CODE_HOME:-~/.t3}/userdata/settings.json`, disabled ones included, and any home named with `--claude-home` or `--codex-home`. Each step converges, so a second run changes nothing. Each home's `t3-pstack` marketplace must come from `chhoumann/t3-pstack` or from the run's `--source`. A home whose marketplace has another source, a counterfeit or a local `--source` from an earlier run, gets nothing installed and no hook trusted, and `doctor` fails it with that source.

Until now the Codex session-start hook never ran under T3. Codex 0.162.1 runs a plugin hook only once `hooks.state."<key>".trusted_hash` in `config.toml` matches the hook's entry, and `codex app-server`, which T3 runs, never asks for that trust. The installer trusts it on every run the way Codex's own TUI does: `hooks/list`, then `config/value/write` with the hash Codex reports and `mergeStrategy: "upsert"`, so the user's other trusted hooks stay. It trusts an entry only when it comes from the hooks file the Codex manifest names, at the digest `tools/frozen-hooks.json` pins, and when the event, matcher, and command Codex lists are an entry of that file's text, read once and never through a symlink. It checks the marketplace source, lists the hooks, and writes the trust in one app-server session. The write names the `config.toml` version the source check read, so Codex refuses it when the file changed in between. When Codex then lists a different entry, the installer takes the trust back and fails the home. In a throwaway home with the plugin installed by hand, a T3-shaped app-server session fired no hook event and its rollout held no routing text. After the installer, the same session fired `hook/started` and `hook/completed`, and its rollout held the routing text as one developer message. `tests/install-e2e.test.mjs` runs that check with the real CLIs.

Codex trusts a hook by a hash of its entry, and a Git marketplace upgrades the plugin at every app-server start, so a changed entry would stop routing in every home with no announcement. `tools/frozen-hooks.json` now pins both hooks files, and `bun tools/generate.mjs --check` fetches `origin/main`, which the marketplaces deliver, and compares each with it. A file that differs fails the check unless its pin has the new digest, its `changedIn` names this `VERSION`, that `VERSION` is newer than main's, and this release's entry here tells users to rerun the installer. Neither file changes in this release.

The model sheet moves to `~/.config/t3-pstack/models.md` (`$XDG_CONFIG_HOME/t3-pstack/models.md` when that is set), one per machine, because provider instance IDs differ per machine, not per harness. The installer moves an existing `t3-pstack-models.md` there once and leaves a symlink at each home's old path, so a release before this one still reads the same sheet. Until the installer runs, the hook, the routing text, the skills' Models sections, and setup-pstack fall back to the old per-home sheet.

| Change | 2.0.0-rc.2 | 2.0.0-rc.3 | Source |
| --- | --- | --- | --- |
| Install and update | `claude plugin` and `codex plugin` commands by hand, in each home. | `npx -y github:chhoumann/t3-pstack install`, or `update`, the same command, for every home T3 can run. It runs `main`'s latest commit, the channel the marketplaces auto-update from, and `#v<version>` pins one release. A listed home that does not exist yet is created, and a home whose CLI is not on `PATH` is skipped with a warning. A home already at the installer's version gets only the steps it still lacks, such as the Codex trust, and one whose plugin files are missing is reinstalled. A file it replaces keeps its mode and, where the system allows, its owner. | T3 installer |
| Marketplace source | Whatever each home was given. | `chhoumann/t3-pstack`, or the run's `--source`, which reads `owner/repo` as GitHub as both CLIs do, so a local directory needs a path such as `./t3-pstack`. Nothing installs from a `t3-pstack` marketplace with any other source, and none of its hooks is trusted. | T3 installer |
| Codex session-start hook | Never ran under T3, because the app-server never asks for hook trust. The README said to trust it with `/hooks` in the Codex CLI. | The installer trusts it in every Codex home on every run, with the hash Codex reports, once its hooks file is the one this release pins and the listed entry is one of that file's. Source check, listing, and trust write share one app-server session, and a write after `config.toml` changed is refused. The home fails unless Codex then reports the same entry `trusted`, and otherwise the trust is taken back. A rerun writes nothing. | T3 installer |
| `doctor` | None. | Reports, per home, the marketplace's source, the installed version against the installer's and whether its files are all there, whether the plugin is enabled, the Codex hook's trust and pin, Claude Code records at another scope that pin an old version, copies of the plugin's skills whose `SKILL.md` matches the plugin's, and the model sheet: present, parsed, and with `--catalog <file>` checked against an `orchestrator_capabilities` dump. It exits 1 on any problem and prints a fix for each. A missing sheet is a warning. | T3 installer |
| Claude Code marketplace auto-update | Off, Claude Code's default for a third-party marketplace. | The installer sets `extraKnownMarketplaces.t3-pstack.autoUpdate` in each home's `settings.json`. A home behind the installer's version gets `claude plugin marketplace update` and `claude plugin update`. | T3 installer |
| A stale project-scope record | A second Claude Code config home could keep a project-scope `t3-pstack@t3-pstack` record for the home folder at an old version, because Claude Code reads `~/.claude/settings.json` as that folder's project settings. | The installer backs up that home's `plugins/installed_plugins.json` and brings each stale record at another scope to the user-scope install. It never runs `claude plugin uninstall --scope project`, which would edit the main home's settings. | T3 installer |
| Model sheet location | `t3-pstack-models.md` in each host's config directory, linked between hosts by hand. | `~/.config/t3-pstack/models.md`, one per machine. The installer moves the old sheet there once and links each home's old path to it. A home sheet that links to a file outside every home, such as one in a dotfiles repository, becomes the machine sheet's link target instead of a copy. A home sheet that differs is left, and `doctor` names it. A home sheet that links to another file is left too. | T3 adaptation |
| setup-pstack | Wrote the host's sheet and linked the other host's path to it. | Writes the machine sheet, reads the old per-home sheet when the machine sheet is missing, and links this host's old path to the machine sheet. | Declared fork |
| A changed hooks file | `session-hook.test.mjs` pinned the 1.0.14 entries literally. | `tools/frozen-hooks.json` pins each hooks file's digest and the release that last changed it. The generator check fetches `origin/main` and compares each hooks file with it. It fails on an unpinned change, on a repinned one whose `changedIn` is not this `VERSION` or whose `VERSION` is not newer than main's, and on one whose release entry does not tell users to rerun the installer. | T3 adaptation |
| Windows | Not tested. | The installer starts the `.cmd` shims npm installs for `claude` and `codex` through `cmd.exe`, with each argument quoted and escaped as cross-spawn does, and reads a full Windows path in `binaryPath` as a path. CI's Windows job runs the launch tests. | T3 installer |
| `t3-sheet.mjs` without a catalog | A catalog was required. | `checkSheet` with no catalog checks the grammar only. `doctor` uses it. | T3 adaptation |

## 2.0.0-rc.2 - Lauren's 0.15.24, four of Michael's rules, and three watch-pr fixes

t3-pstack moves to Lauren Tan's pstack 0.15.24, cursor/plugins `1e5c90a`, for all three components. Only pstack changed upstream: the autopilot playbooks catch the operator up through `/ketchup` ([pstack #616](https://github.com/cursor/plugins/pull/616)), a new `/sharpen` skill aims a task before it starts ([pstack #615](https://github.com/cursor/plugins/pull/615)), and the version moves to 0.15.24 ([pstack #617](https://github.com/cursor/plugins/pull/617)). `bun tools/sync.mjs` wrote every change cleanly, and at `1e5c90a` each component's dry run reports no undeclared fork, no denylist hit, and no substitution that matches nothing.
Expand Down
4 changes: 3 additions & 1 deletion CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ Names for the concepts this repo's design discussions keep reaching for. Archite
- **Host** - the harness a T3 thread runs on and that loads the plugin: Claude Code (`.claude-plugin/`) or Codex (`.codex-plugin/`). Both read the same `plugins/pstack/` tree. The skill text names each host's own form inline where the two differ, such as a skills directory or a question tool.
- **T3 form** - upstream's skill text with every Cursor mechanism rewritten into what T3 provides: `delegate_task` children and persona briefs read by path, `schedule_task`, `watch_pull_request`, `t3_thread_*` reads, and sheet entries for models. It lives in the text itself, so a skill needs no second file to run on T3.
- **Agent Skill** - one directory under `plugins/pstack/skills/` with portable `name` and `description` frontmatter. Upstream marks every pstack and thermos skill but `setup-pstack` `disable-model-invocation: true`, and the six cursor-team-kit skills carry no flag. The port drops the flag from `poteto-mode` alone, so the session-start hook can invoke it, and `poteto-mode` opens the others by path.
- **Model sheet** - `t3-pstack-models.md` in the host's config directory, one role per line, each value a T3 entry (`<providerInstanceId>/<modelId>` plus `<optionId>=<value>` pairs) or `inherit-parent`. `setup-pstack` writes it and `t3-sheet.mjs` checks it against `orchestrator_capabilities`.
- **Model sheet** - `~/.config/t3-pstack/models.md` (`$XDG_CONFIG_HOME/t3-pstack/models.md` when that is set), one per machine, one role per line, each value a T3 entry (`<providerInstanceId>/<modelId>` plus `<optionId>=<value>` pairs) or `inherit-parent`. `setup-pstack` writes it and `t3-sheet.mjs` checks it against `orchestrator_capabilities`.
- **Installer** - `tools/install.mjs`, the package's `t3-pstack` bin, run as `npx -y github:chhoumann/t3-pstack <install|update|doctor>`. It installs the plugin in every Claude Code and Codex home T3 can run, trusts the Codex session-start hook, moves the model sheet to its machine path, and checks the result.
- **Frozen hooks file** - `hooks/hooks.json` or `hooks/codex-hooks.json`, pinned in `tools/frozen-hooks.json`. Codex trusts a hook by a hash of its entry, so a change stops the hook in every Codex home until the installer reruns. What the hook says lives in `session-start.sh` and the routing text, which change freely.
- **Model policy** - the role defaults in `plugins/pstack/models.json`, as tiers of T3 entries. The generator stamps them into each skill's Models section, interrogate's reviewer table, and setup-pstack's sheet block.
- **Role** - a named unit of delegated work with its own model choice (`arena runners`, `how explorer`, `swarm workers`). The role vocabulary lives in `models.json`.
- **Panel** - a role whose value is a list (`arena runners`, `architect runners`, `interrogate reviewers`, `arena cross-judge pool`). A panel spans at least two model families; a family is the first segment of a model ID's last `/` part.
Expand Down
10 changes: 8 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ bun tools/generate.mjs
GIT_CONFIG_GLOBAL=/dev/null bun test tests/ --timeout 30000
```

The generator stamps `VERSION` into the Claude Code and Codex manifests, the marketplace entry, and `package.json`. It renders each model-consuming skill's Models section, interrogate's reviewer table, and setup-pstack's sheet block from `plugins/pstack/models.json`, lists the plugin's agents in the Claude Code manifest, and copies the license and notice files into the installed plugin. It checks each skill's frontmatter, every local Markdown link, the plugin layout, agent frontmatter, the Codex marketplace, and both hooks files. CI runs `bun tools/generate.mjs --check`, which writes nothing and fails naming each generated file that is stale or orphaned, so commit the generator's output. `.pre-commit-config.yaml` runs the same check before each commit once you run `prek install` (or `pre-commit install`).
The generator stamps `VERSION` into the Claude Code and Codex manifests, the marketplace entry, and `package.json`. It renders each model-consuming skill's Models section, interrogate's reviewer table, and setup-pstack's sheet block from `plugins/pstack/models.json`, lists the plugin's agents in the Claude Code manifest, and copies the license and notice files into the installed plugin. It checks each skill's frontmatter, every local Markdown link, the plugin layout, agent frontmatter, the Codex marketplace, and both hooks files against their pins in `tools/frozen-hooks.json`. CI runs `bun tools/generate.mjs --check`, which writes nothing and fails naming each generated file that is stale or orphaned, so commit the generator's output. `.pre-commit-config.yaml` runs the same check before each commit once you run `prek install` (or `pre-commit install`).

Change model defaults in `models.json`, never in a skill body. A role names a tier (`default`, `strongest`, or `panel`), and each tier holds T3 entries in the grammar `t3-sheet.mjs` checks. The panel must span two model families. A role may carry an `alias`, the label a sheet written before upstream renamed the role still uses.

Expand All @@ -63,6 +63,12 @@ bunx [email protected] --check watch-pr/settle.ts watch-pr/settle.test.ts resolve-c

Upstream's scripts are not Prettier output, so the format check covers the port's own TypeScript only.

If you touched the installer (`tools/install.mjs` or `tools/install/`), also run its end-to-end test. It needs the `claude` and `codex` CLIs, runs in a throwaway `HOME`, and takes a few minutes:

```shell
T3_PSTACK_E2E=1 GIT_CONFIG_GLOBAL=/dev/null bun test tests/install-e2e.test.mjs --timeout 300000
```

If you touched a workflow, audit it before pushing:

```shell
Expand All @@ -75,7 +81,7 @@ uvx [email protected] --persona pedantic --min-severity low --collect all -- .
- **A substitution that no longer matches upstream.** Update it to upstream's current sentence or delete it.
- **`disable-model-invocation` on `poteto-mode`.** The session-start hook invokes it by name, and the flag makes the Skill tool refuse that.
- **A root `plugins/pstack/plugin.json`.** Codex reads a plugin with upstream's Agent Plugins manifest as an Agent Plugin and truncates each injected SKILL.md to 8,000 bytes, which cuts `poteto-mode` off mid-file. `tools/upstream.json` excludes the file.
- **A changed hook entry.** Codex trusts a plugin hook by a hash of its entry, so changing a command, matcher, or timeout in `hooks/hooks.json` or `hooks/codex-hooks.json` silently stops routing until each user trusts it again. Change `session-start.sh` or the routing text instead.
- **A changed hooks file.** Codex trusts a plugin hook by a hash of its entry, and a Git marketplace delivers a new entry at the next app-server start, so any change to `hooks/hooks.json` or `hooks/codex-hooks.json` stops routing in every Codex home until the installer reruns. `tools/frozen-hooks.json` pins both. Change `session-start.sh` or the routing text instead. When an entry must change, pin its new digest with `changedIn` set to the release, and tell users to rerun the installer in that release's `CHANGES.md` entry. The check fetches `origin/main`, which the marketplaces deliver, and compares each hooks file with it, so a file that differs from main's needs a `VERSION` newer than main's and a `changedIn` that names it, and the installer trusts only the entry its own release pins. The fetch means the check needs the network.
- **Stale generated output, or a missing `CHANGES.md` heading for `VERSION`.**
- **An unresolved sync conflict marker** anywhere under `plugins/pstack`.
- **A missing or escaping local Markdown link.**
Expand Down
Loading
Loading