pi-pstack-adapter 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project are documented here. release-please writes new entries from Conventional Commit PR titles.
4
4
 
5
+ ## [0.7.0](https://github.com/elderfo/pi-pstack-adapter/compare/v0.6.0...v0.7.0) (2026-10-19)
6
+
7
+
8
+ ### Features
9
+
10
+ * **pstack:** certify pstack 0.15.26 with create-formal-verification-skill ([#17](https://github.com/elderfo/pi-pstack-adapter/issues/17)) ([82367d9](https://github.com/elderfo/pi-pstack-adapter/commit/82367d93e372fc49c603f56e79469197a77814a2))
11
+ * **pstack:** certify pstack 0.15.28 with maintain-formal-verification-skill ([#20](https://github.com/elderfo/pi-pstack-adapter/issues/20)) ([0dee303](https://github.com/elderfo/pi-pstack-adapter/commit/0dee303dbd349dfd9726ff6e3c17a8d96ecf95a2))
12
+ * **roles:** add a general role and cap delegate thinking at the configured level ([#19](https://github.com/elderfo/pi-pstack-adapter/issues/19)) ([df1db04](https://github.com/elderfo/pi-pstack-adapter/commit/df1db045c49907665b7a2ef504b3ab52da287502))
13
+
5
14
  ## [0.6.0](https://github.com/elderfo/pi-pstack-adapter/compare/v0.5.0...v0.6.0) (2026-10-17)
6
15
 
7
16
 
package/README.md CHANGED
@@ -135,8 +135,10 @@ skill.
135
135
 
136
136
  ## Choose models for pstack roles
137
137
 
138
- pstack assigns work to named model roles such as `feature`, `bug-fix`, and `judgment`. A role you
139
- leave unset uses the current Pi session model.
138
+ pstack assigns work to named model roles such as `feature`, `bug-fix`, and `judgment`. The
139
+ `general` role covers every other delegate, such as explorers, swarm workers, and ad-hoc helpers.
140
+ A role you leave unset uses `general`, and uses the model of the agent that starts it only when
141
+ `general` is unset too.
140
142
 
141
143
  To list the models in your Pi installation and assign them to roles, run the setup skill:
142
144
 
@@ -159,9 +161,11 @@ PISTACK_MODELS='feature=provider/model:medium,judgment=provider/other-model:high
159
161
 
160
162
  Role names are lowercase letters, digits, and hyphens. A model value is printable ASCII with no spaces or markup characters, at most 128 characters. A config entry that breaks these rules stops the adapter at startup with an error that names the file, so fix the key there and restart Pi.
161
163
 
162
- A role without a level passes none, so the delegation backend applies its own default. A role you leave unset inherits the
163
- session model and the session thinking level, so a session at high thinking makes every
164
- inheriting delegate expensive. The `pstack_adapter` tool's `set_model` action takes the same
164
+ A role without a level passes none, so the delegation backend applies its own default. A role's
165
+ level is a ceiling: a delegate may lower it for routine work but never raises it. A role you leave
166
+ unset, with `general` unset too, inherits the model and thinking level of the agent that starts
167
+ it. A `strongest-judgment` delegate at `xhigh` would then start every helper at `xhigh`, so set
168
+ `general` to keep helpers at a cheaper level. The `pstack_adapter` tool's `set_model` action takes the same
165
169
  level in its `thinking` parameter. A level the model does not offer is clamped by Pi at run time.
166
170
 
167
171
  ## Run Pi noninteractively
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0
1
+ 0.7.0
@@ -9,8 +9,9 @@ Three things are configurable. Model roles, the upstream source, and the delegat
9
9
  ## Model roles
10
10
 
11
11
  pstack skills name a model per role. In Pi those names mean nothing, so each role maps to a
12
- model in your own catalogue. A role you never configure inherits the parent session model,
13
- which is a working default, not a failure.
12
+ model in your own catalogue. A role you never configure uses the `general` role. When `general` is
13
+ unconfigured too, the role inherits the model of whichever agent starts it, which is a working
14
+ default, not a failure.
14
15
 
15
16
  Roles used by pstack workflows.
16
17
 
@@ -23,6 +24,7 @@ Roles used by pstack workflows.
23
24
  | `hillclimb` | sustained metric improvement |
24
25
  | `judgment` | prose, review, and judgment delegates |
25
26
  | `strongest-judgment` | the hardest cross-cutting changes |
27
+ | `general` | every other delegate: explorers, investigators, swarm and verification workers, ad-hoc helpers, and any unconfigured role |
26
28
 
27
29
  The status report lists the same roles, so read it back rather than trusting this table if the
28
30
  adapter has been upgraded.
@@ -30,9 +32,11 @@ adapter has been upgraded.
30
32
  Each role also has a thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or
31
33
  `max`. Upstream pstack calls this a reasoning budget and encodes it in the model slug. A role with
32
34
  no thinking level passes none, so the delegation backend applies its own default. A role left on
33
- `inherit` uses the parent session's model and thinking level, so a parent at high thinking makes
34
- every inheriting child expensive. Model choice often affects cost more than thinking level does,
35
- so consider a cheaper model for the bulk roles as well.
35
+ `inherit` with `general` also on `inherit` uses the model and thinking level of the agent that
36
+ starts it. A `strongest-judgment` delegate at `xhigh` would then start every helper at `xhigh`, so
37
+ configure `general` to stop that. A role's level is a ceiling: delegates may lower it for routine
38
+ work, but never raise it. Model choice often affects cost more than thinking level does, so
39
+ consider a cheaper model for the bulk roles as well.
36
40
 
37
41
  Steps.
38
42
 
@@ -45,10 +49,10 @@ Steps.
45
49
  role is on `inherit`. It applies to every role that got a model in step 2. A role on
46
50
  `inherit` cannot have a level, so tell the user that and say that those roles follow the
47
51
  parent session's level. Ask one single-select question first. Put the whole choice in each option
48
- title so the user can pick without opening anything else, for example "Balanced: code roles
52
+ title so the user can pick without opening anything else, for example "Balanced: code and general roles
49
53
  medium, judgment high, strongest-judgment xhigh (suggested)".
50
54
 
51
- | choice | code roles (`feature`, `refactoring`, `bug-fix`, `perf-issue`, `hillclimb`) | `judgment` | `strongest-judgment` |
55
+ | choice | code and general roles (`feature`, `refactoring`, `bug-fix`, `perf-issue`, `hillclimb`, `general`) | `judgment` | `strongest-judgment` |
52
56
  | --- | --- | --- | --- |
53
57
  | Balanced (suggest this first) | `medium` | `high` | `xhigh` |
54
58
  | Low cost | `low` | `medium` | `high` |
@@ -58,7 +62,7 @@ Steps.
58
62
 
59
63
  These are starting points, not measurements. They keep the heaviest reasoning on the roles
60
64
  that make the hardest calls. Offer Custom for a user who wants different levels. Ask once for
61
- the code roles, once for `judgment`, and once for `strongest-judgment`. Ask a role on its own
65
+ the code and general roles, once for `judgment`, and once for `strongest-judgment`. Ask a role on its own
62
66
  only when the user says it should differ from its group. Backend default passes no level and
63
67
  leaves the choice to the delegation backend. A Custom user who wants thinking disabled picks
64
68
  `off`.
@@ -4,7 +4,7 @@ The compatibility registry in `src/registry.ts` assigns one support tier to ever
4
4
  certified pstack revision. Generation reads it, the wrapper frontmatter publishes it, and
5
5
  `/pistack-status` reports it.
6
6
 
7
- Certified revision: pstack `0.15.25` at `101b274dd991f694e1f7024be2157e5f3adf07de` in
7
+ Certified revision: pstack `0.15.28` at `d457567332aa6989e753ea289f30ea2bfbc80bc0` in
8
8
  `https://github.com/cursor/plugins`.
9
9
 
10
10
  ## Platforms
@@ -21,17 +21,76 @@ executables in the shell the skills run in, with Pi's `shellCommandPrefix` appli
21
21
  that matters most, because Git Bash adds `/usr/bin` and `/mingw64/bin` to the `PATH` Pi
22
22
  started with. A shell that is missing, inactive, or cannot run a command blocks every workflow.
23
23
 
24
- Windows evidence was last gathered at pstack `0.15.23`, on Windows 11 with Git for Windows 2.53,
25
- Node 22, Bun 1.3, and Pi 1.2.0. CI runs the unit suite on Windows at `0.15.25`; the rest of this
26
- list has not been rerun there:
27
-
28
- - The unit suite and the type check pass. `npm run test:isolated` passes from Git Bash: Pi
29
- registers all 54 skills, and a later run reuses the cache without the network. CI runs the
30
- unit suite on Windows for every change.
31
- - The certification probe passes on an LF export of the tree: no diagnostics, 52 bodies
32
- byte-identical, and none mismatched. The adapter's cache checkout matches that export byte for
33
- byte.
34
- - `in-your-own-words`, `ketchup`, and `poteto-help` ran end to end in the isolated install, with
24
+ Windows evidence was last gathered at pstack `0.15.28`, on Windows 11 Pro with Git for Windows
25
+ 2.53.0, Node 22.23.2, Bun 1.3.14, and Pi 1.3.0:
26
+
27
+ - The type check and unit suite pass: 183 tests, 179 passed, none failed, and the four symlink
28
+ tests skipped. All eight integration tests pass with no skips, including the Windows 8.3
29
+ short-path case and both formal-verification wrapper regressions.
30
+ - The isolated install passes from Git Bash with the same CLIProxyAPI scratch copy as below: 57
31
+ skills, no `subagent` or `ask_user` with the provider extension loaded, and cache reuse without
32
+ bootstrap permission. The installed checkout's 172 files match an LF export of the pinned tree
33
+ byte for byte. The probe reports 57 skills, two agents, no diagnostics, 55 bodies
34
+ byte-identical, `setup-pstack` replaced, and none mismatched.
35
+ - The installed status report shows `tested` trust at the certified commit, and
36
+ `/pistack-check pistack-maintain-formal-verification-skill` clears the workflow. `git` is
37
+ its only executable, and Git for Windows bundles it.
38
+ - `maintain-formal-verification-skill` ran in the isolated install against a fixture whose
39
+ `.pi/skills/` verify skill recorded a sync commit, followed by a rename of a covered function
40
+ and a new batch function that wrote the guarded state without its duplicate-ID guard. It
41
+ found the skill, ran the stale check (it crashed on the rename), read all three commits since
42
+ the sync commit, renamed the call, extended the harness to the new function, and reported the
43
+ bug with the commit that introduced it and a two-step counterexample. It proved the updated
44
+ check could pass and fail by temporarily fixing and then breaking the product file, restored
45
+ it, moved the sync commit, and committed only the verification and verify-skill changes.
46
+ This supports `dependency-gated` on `git`.
47
+ - The changed `create-formal-verification-skill` reran in the isolated install. It generated
48
+ `.pi/skills/verify-inventory-ledger/SKILL.md` with the coverage record and sync commit new in
49
+ `0.15.28`, plus a dependency-free Node verifier over the real functions (28 states, 168 calls).
50
+ The check caught six deliberate breaks, and the product file's content was unchanged after
51
+ each restore. `adapted` still holds.
52
+
53
+ Earlier Windows evidence, gathered at pstack `0.15.26` with the same toolchain:
54
+
55
+ - The type check and unit suite pass: 173 tests passed, none failed, and four symlink tests
56
+ skipped because Windows requires Developer Mode or administrator rights. The seven scheduled
57
+ integration tests pass with no skips: six cache-containment cases, including a Windows 8.3
58
+ short-path case, and a formal-verification wrapper regression.
59
+ Run these separately with `npm run test:integration`; their CI lane is daily and manually
60
+ dispatchable, not per PR. The existing unit-suite CI matrix still covers all three platforms.
61
+ - The isolated install passes from Git Bash: the cold run registers no skills without bootstrap
62
+ permission, the permitted run registers all 56 skills, and a later run reuses the cache
63
+ without bootstrap permission. The local CLIProxyAPI provider requires its extension, config,
64
+ and model catalog in the sandbox; a scratch copy of the install script supplies them. Its
65
+ tool probe still reports no `subagent` or `ask_user`. Sandbox credentials are removed on exit.
66
+ - The certification probe passes on an LF export of the pinned tree: 56 skills, two agents,
67
+ no diagnostics, 54 bodies byte-identical, `setup-pstack` replaced, and none mismatched.
68
+ All 171 files in the isolated installed cache match the LF export byte for byte.
69
+ - `create-formal-verification-skill` completed in the isolated install without `subagent` or
70
+ `ask_user`. It generated `.pi/skills/verify-inventory-reservations/SKILL.md` and a dependency-free
71
+ Node 22 explicit-state verifier that imports the fixture's actual reserve/release functions.
72
+ It checked 49 reachable states and 490 transitions for stock 0–3 and five request IDs, caught
73
+ six mutant copies, then caught a removed retry guard in the actual product file. Restoring
74
+ that file restored the passing result and its original hash. This supports `adapted`: the
75
+ generated skill path is mapped to Pi. It does not certify TLA+, proof assistants, installation
76
+ of other verifiers, unbounded inputs, or real database/async concurrency.
77
+ - The revised `ketchup` ran read-only in a separate fixture. It listed both supplied product
78
+ choices (CSV versus JSON and seven versus thirty days of history), drew a Mermaid sequence
79
+ diagram for a late-retry edge case it reproduced, and left the fixture unchanged. It did not
80
+ contact GitHub for this fixture-only task.
81
+ - Installed status reports show native Windows plugin and generated-skill paths and `tested`
82
+ trust at the certified commit. `/pistack-check` clears the formal-verification workflow and
83
+ `ketchup`, but still names `uptime` as missing for `benchmark-checklist`. Delegation and
84
+ structured questions remain unavailable in the isolated install; no delegating tier changed.
85
+ - A cache inside a Git worktree must own its worktree, Git directory, and common directory before
86
+ bootstrap or reuse can change Git state. The Windows run exposed ancestor discovery from an
87
+ empty nested cache; this is fixed. Regressions also cover incomplete metadata, a foreign Git
88
+ file, foreign worktree/common directory settings, and short paths, while checking that the
89
+ enclosing repository's HEAD, config, index, and tracked bytes stay unchanged.
90
+
91
+ Historical coverage below has not been rerun at `0.15.26` unless stated otherwise:
92
+
93
+ - At `0.15.23`, `in-your-own-words`, `ketchup`, and `poteto-help` ran end to end in the isolated install, with
35
94
  no `subagent` or `ask_user` tool. `ketchup` reported a commit and a pending decision with
36
95
  Mermaid diagrams and changed nothing. `poteto-help` answered with `/pistack-mode on` and read
37
96
  the guide from the `plugin directory` row, whose Windows path it resolved.
@@ -123,14 +182,15 @@ Lowering a tier needs one reproduction of the failure and a note saying what bro
123
182
  | `typescript-best-practices` | `/skill:pistack-typescript-best-practices` | - | - | - |
124
183
  | `unslop` | `/skill:pistack-unslop` | - | - | - |
125
184
 
126
- ### adapted (2)
185
+ ### adapted (3)
127
186
 
128
187
  | upstream skill | Pi skill | capabilities | executables | why |
129
188
  | --- | --- | --- | --- | --- |
189
+ | `create-formal-verification-skill` | `/skill:pistack-create-formal-verification-skill` | - | - | Manual invocation only; no delegation. It installs or runs the verification tool it chooses and temporarily breaks product code to prove the check. Generated skills belong under `.pi/skills/` in Pi. Verified on a Windows Node finite-state fixture; other toolchains need their own prerequisites. |
130
190
  | `create-verification-skill` | `/skill:pistack-create-verification-skill` | - | - | Only the generated skill's output path needs remapping to Pi's skill directory. |
131
191
  | `ketchup` | `/skill:pistack-ketchup` | - | - | Read-only. Pi has no `/visualize` skill or image generation, so its visuals are Mermaid diagrams and screenshots that already exist. It checks PR and CI state only for work the chat touched. |
132
192
 
133
- ### dependency-gated (13)
193
+ ### dependency-gated (14)
134
194
 
135
195
  | upstream skill | Pi skill | capabilities | executables | why |
136
196
  | --- | --- | --- | --- | --- |
@@ -142,6 +202,7 @@ Lowering a tier needs one reproduction of the failure and a note saying what bro
142
202
  | `figure-it-out` | `/skill:pistack-figure-it-out` | `delegation` | - | Phase B fan-out and the architect/arena routing require parallel subagents. |
143
203
  | `how` | `/skill:pistack-how` | `delegation` | - | Every path spawns explorer or explainer subagents; nothing runs inline. |
144
204
  | `interrogate` | `/skill:pistack-interrogate` | `delegation` | `git` | Multi-model adversarial review is entirely one subagent per configured model. |
205
+ | `maintain-formal-verification-skill` | `/skill:pistack-maintain-formal-verification-skill` | - | `git` | Verified end to end on a Windows Node finite-state fixture at pstack 0.15.28. Manual invocation only; no delegation. It diffs the covered code with git from the verify skill's sync commit, edits only the verification and the verify skill, and temporarily breaks product code to prove an updated check can still fail. In Pi, look for the verify skill under `.pi/skills/`; the verification tool needs its own prerequisites. |
145
206
  | `maintain-verification-skill` | `/skill:pistack-maintain-verification-skill` | `delegation` | - | Source wave launches one read-only subagent per feature file. |
146
207
  | `no-comments` | `/skill:pistack-no-comments` | `delegation` | `git` | Step 1 must spawn the comment-sicko agent; the skill only triages its report. |
147
208
  | `show-me-your-work` | `/skill:pistack-show-me-your-work` | `delegation` | - | The mandatory cross-model review of the trail requires spawning a different-family subagent. |
@@ -206,8 +267,10 @@ through `delegate_task`. That run is the evidence the tier rules above require.
206
267
 
207
268
  ## Skills the adapter replaces
208
269
 
209
- `setup-pstack` writes a Cursor `.mdc` rule and nothing else, so the adapter substitutes its own
210
- body and publishes it as `/skill:pistack-setup`. The upstream skill stays represented, and the
270
+ `setup-pstack` writes a Cursor `.mdc` rule, so the adapter substitutes its own body and
271
+ publishes it as `/skill:pistack-setup`. Upstream's optional closing offers to run
272
+ `create-verification-skill` and `create-formal-verification-skill` are left out of the Pi body;
273
+ both skills stay available by name. The upstream skill stays represented, and the
211
274
  wrapper frontmatter records `pistack-upstream-skill: setup-pstack`.
212
275
 
213
276
  ## Unknown skills
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-pstack-adapter",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Run Cursor's official pstack plugin inside Pi from an unmodified upstream checkout",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,6 +47,7 @@
47
47
  "scripts": {
48
48
  "check": "tsc --noEmit",
49
49
  "test": "node --experimental-strip-types --test test/*.test.ts",
50
+ "test:integration": "node --experimental-strip-types --test test/integration/*.test.ts",
50
51
  "test:isolated": "bash test/isolated-install.sh"
51
52
  },
52
53
  "devDependencies": {
package/src/certified.ts CHANGED
@@ -5,9 +5,9 @@
5
5
  export const CERTIFIED = {
6
6
  repo: "https://github.com/cursor/plugins",
7
7
  ref: "main",
8
- commit: "101b274dd991f694e1f7024be2157e5f3adf07de",
8
+ commit: "d457567332aa6989e753ea289f30ea2bfbc80bc0",
9
9
  pluginPath: "pstack",
10
- pstackVersion: "0.15.25",
11
- skillCount: 54,
10
+ pstackVersion: "0.15.28",
11
+ skillCount: 56,
12
12
  agentCount: 2,
13
13
  } as const;
package/src/generate.ts CHANGED
@@ -459,6 +459,17 @@ ${agentSystemPrompt(notes, upstreamPrompt)}
459
459
  `;
460
460
  }
461
461
 
462
+ /**
463
+ * A child that starts its own agents would otherwise hand them its own model and thinking, so a
464
+ * strongest-judgment delegate's helpers would all run at its level. The rule travels in every brief.
465
+ */
466
+ const NESTED_DELEGATION_RULE = `Nested delegation: every agent that delegates, a child included, gives each new child an
467
+ explicit role, using \`general\` when no other role fits, and never lets it inherit the delegating
468
+ agent's model or thinking. Put this line in each brief, verbatim, just before any read-only or
469
+ no-commit rule: "If you start agents, give each the model and thinking of its own pstack role from the
470
+ \`pstack_adapter\` status report, using \`general\` when no role fits. Never let one inherit your
471
+ model or thinking, and never raise a role above its configured level."`;
472
+
462
473
  function delegationSection(namespace: string, preferred: DelegationProvider): string {
463
474
  const sections = delegationOrder(preferred).map((backend) => (backend.id === "t3" ? t3Section(namespace) : subagentSection(namespace)));
464
475
  const order = delegationOrder(preferred).map((backend) => `\`${backend.tool}\``).join(", then ");
@@ -488,8 +499,9 @@ function t3Section(namespace: string): string {
488
499
  \`target.options\` under the option id that \`${T3_CAPABILITIES_TOOL}\` lists for that model's
489
500
  thinking setting. For Pi models the id is \`thinking\`, as in \`options: { thinking: "<level>" }\`.
490
501
  A role whose thinking reads "backend default" omits \`options\`, so the backend applies its own
491
- default. An unconfigured role omits \`target\`, so the child inherits the parent model and
492
- thinking.
502
+ default. An unconfigured role uses the \`general\` role. Only when \`general\` is unconfigured
503
+ too does the child omit \`target\` and inherit the parent model and thinking.
504
+ - ${NESTED_DELEGATION_RULE}
493
505
  - If the model does not list the level, use the highest level it lists that is below the requested
494
506
  one, and tell the user. If it lists none below the requested level, or lists no thinking option,
495
507
  omit \`options\` and tell the user. If the level is \`off\` and the model does not list \`off\`, omit
@@ -520,7 +532,9 @@ function subagentSection(namespace: string): string {
520
532
  - Model: pass a role's \`provider/model\` as \`model\`. When the thinking column of the \`model roles\`
521
533
  table in the \`pstack_adapter\` \`status\` report names a level for the role, append it as
522
534
  \`provider/model:<level>\`. A role whose thinking reads "backend default" gets no suffix, so
523
- pi-subagents applies its own default. An unconfigured role omits \`model\`.
535
+ pi-subagents applies its own default. An unconfigured role uses the \`general\` role, and omits
536
+ \`model\` only when \`general\` is unconfigured too.
537
+ - ${NESTED_DELEGATION_RULE}
524
538
  - Background: \`async: true\`.`;
525
539
  }
526
540
 
@@ -536,12 +550,15 @@ that names a Cursor tool, path, or model.
536
550
  | \`run_in_background: true\` | \`mode: "async"\` on \`delegate_task\`, or \`async: true\` on \`subagent\`. |
537
551
  | \`AskUserQuestion\`, \`AskQuestion\` | the \`ask_user\` tool. |
538
552
  | \`TodoWrite\`, \`TaskCreate\`, \`TaskUpdate\` | no Pi equivalent. Keep an uncommitted \`todo.md\` checklist instead. |
539
- | \`claude-opus-5-5-xhigh\`, \`grok-4.7-xhigh-fast\`, other Cursor model slugs | the role models configured by \`/skill:${namespace}-setup\`. Call the \`pstack_adapter\` tool with \`action: "status"\` to read the current role table. An effort word in a slug, such as \`-xhigh\` or \`-max\`, is not a model id. The role table's thinking column carries that level, and \`-fast\` has no equivalent. An unconfigured role inherits the parent model and thinking, so omit \`target\` or \`model\`. |
540
- | "pick each subagent's reasoning level and fast or standard speed", a role's starting level from the \`/setup-pstack\` budget | the thinking column of the \`model roles\` table is a role's starting level. When it names a level, you may move it along \`off\`, \`minimal\`, \`low\`, \`medium\`, \`high\`, \`xhigh\`, \`max\` as the body asks, then pass it by the backend rules in [Delegation](#delegation). A role whose thinking reads "backend default", and an unconfigured role, keep their setting, because they have no level to move. Fast or standard speed has no Pi equivalent, so leave it out. If the backend rejects the moved level, use the role's configured level and say so. |
541
- | a role line valued \`auto\` or \`inherit-parent\` | an unconfigured role. Omit \`target\` or \`model\` so the child runs on the parent model. |
553
+ | \`claude-opus-5-5-xhigh\`, \`grok-4.7-xhigh-fast\`, other Cursor model slugs | the role models configured by \`/skill:${namespace}-setup\`. Call the \`pstack_adapter\` tool with \`action: "status"\` to read the current role table. An effort word in a slug, such as \`-xhigh\` or \`-max\`, is not a model id. The role table's thinking column carries that level, and \`-fast\` has no equivalent. An unconfigured role uses the \`general\` role. When \`general\` is unconfigured too, omit \`target\` or \`model\` so the child inherits the parent model and thinking. |
554
+ | "pick each subagent's reasoning level and fast or standard speed", a role's starting level from the \`/setup-pstack\` budget | the thinking column of the \`model roles\` table is a role's ceiling. When it names a level, you may lower it along \`max\`, \`xhigh\`, \`high\`, \`medium\`, \`low\`, \`minimal\`, \`off\` as the body asks, then pass it by the backend rules in [Delegation](#delegation). Never raise a role above its configured level, even where the body says to raise it for the hardest work. Send that work to \`strongest-judgment\` at its own level instead. A role whose thinking reads "backend default" keeps that setting, because it has no level to move. An unconfigured role takes the \`general\` role's level as its ceiling, and keeps the parent's setting only when \`general\` is unconfigured too. Fast or standard speed has no Pi equivalent, so leave it out. If the backend rejects the moved level, use the role's configured level and say so. |
555
+ | a role line valued \`auto\` or \`inherit-parent\` | an unconfigured role. Use the \`general\` role, or omit \`target\` or \`model\` only when \`general\` is unconfigured too. |
542
556
  | \`~/.cursor/rules/pstack-models.mdc\` | adapter configuration written by \`/skill:${namespace}-setup\`. |
543
557
  | role lines \`feature, refactoring\`, \`bug-fix\`, \`perf-issue\`, \`hillclimb\`, \`judgment and prose\`, \`hardest tasks\` | adapter roles \`feature\` or \`refactoring\`, \`bug-fix\`, \`perf-issue\`, \`hillclimb\`, \`judgment\`, and \`strongest-judgment\`. |
544
- | any other role line, such as \`how explorer\`, \`arena runners\`, or \`interrogate reviewers\` | no adapter role. Omit \`target\` or \`model\` so the child runs on the parent model. |
558
+ | role lines \`how explainer\`, \`why synthesizer\`, \`reflect judgment, divergent, synthesizer\` | adapter role \`judgment\`. |
559
+ | role lines \`how explorer\`, \`why investigators\`, \`reflect tooling\`, \`swarm workers\`, and verification | adapter role \`general\`. |
560
+ | panel role lines \`arena runners\`, \`arena cross-judge pool\`, \`architect runners\`, \`interrogate reviewers\` | the list \`judgment\`, \`general\`, one entry each, so the panel keeps two models when those roles differ. |
561
+ | a delegate no role line names, such as an ad-hoc helper | adapter role \`general\`. |
545
562
  | a Custom Mode, "Use as Mode", or Option+Enter / Alt+Enter on \`/poteto-mode\` | \`/${namespace}-mode on\`, which keeps Poteto Mode active for the Pi session until \`/${namespace}-mode off\`. |
546
563
  | searching \`~/.cursor/plugins/\` for an installed pstack skill, or comparing \`.cursor-plugin/plugin.json\` versions | the generated skill directory. A pstack skill is \`../<pi-name>/SKILL.md\` beside the current wrapper, where \`<pi-name>\` is its Pi name in [skill names](skill-names.md). Most names only gain the \`${namespace}-\` prefix, but some are renamed. The \`generated skills\` row of the \`pstack_adapter\` tool's \`action: "status"\` report gives that directory's absolute path. A parent passes the absolute path to \`${namespace}-poteto-mode/SKILL.md\` to any \`${namespace}-poteto-agent\` it spawns. |
547
564
  | a link that leaves the skill directory, such as \`../../README.md\` or \`../../docs/guide/\` | the same path under the \`plugin directory\` row of the \`pstack_adapter\` status report. That is the upstream checkout at the resolved commit, so read it there, not from a public \`main\` copy. |
package/src/registry.ts CHANGED
@@ -13,12 +13,14 @@ export const SKILL_REGISTRY: Readonly<Record<string, RegistryEntry>> = {
13
13
  "blast-radius": { tier: "dependency-gated", capabilities: [], executables: ["git", "gh"], note: "Needs git and gh to read the diff, commits, and PR before any analysis." },
14
14
  "bro": { tier: "native", capabilities: [], executables: [] },
15
15
  "correct": { tier: "dependency-gated", capabilities: [], executables: ["git"], note: "Mines commit history, then commits fixes and edits lint, CI, and agent instruction files. Run it on a branch you will review." },
16
+ "create-formal-verification-skill": { tier: "adapted", capabilities: [], executables: [], note: "Manual invocation only; no delegation. It installs or runs the verification tool it chooses and temporarily breaks product code to prove the check. Generated skills belong under `.pi/skills/` in Pi. Verified on a Windows Node finite-state fixture; other toolchains need their own prerequisites." },
16
17
  "create-verification-skill": { tier: "adapted", capabilities: [], executables: [], note: "Only the generated skill's output path needs remapping to Pi's skill directory." },
17
18
  "figure-it-out": { tier: "dependency-gated", capabilities: ["delegation"], executables: [], note: "Phase B fan-out and the architect/arena routing require parallel subagents." },
18
19
  "how": { tier: "dependency-gated", capabilities: ["delegation"], executables: [], note: "Every path spawns explorer or explainer subagents; nothing runs inline." },
19
20
  "in-your-own-words": { tier: "native", capabilities: [], executables: [] },
20
21
  "interrogate": { tier: "dependency-gated", capabilities: ["delegation"], executables: ["git"], note: "Multi-model adversarial review is entirely one subagent per configured model." },
21
22
  "ketchup": { tier: "adapted", capabilities: [], executables: [], note: "Read-only. Pi has no `/visualize` skill or image generation, so its visuals are Mermaid diagrams and screenshots that already exist. It checks PR and CI state only for work the chat touched." },
23
+ "maintain-formal-verification-skill": { tier: "dependency-gated", capabilities: [], executables: ["git"], note: "Verified end to end on a Windows Node finite-state fixture at pstack 0.15.28. Manual invocation only; no delegation. It diffs the covered code with git from the verify skill's sync commit, edits only the verification and the verify skill, and temporarily breaks product code to prove an updated check can still fail. In Pi, look for the verify skill under `.pi/skills/`; the verification tool needs its own prerequisites." },
22
24
  "maintain-verification-skill": { tier: "dependency-gated", capabilities: ["delegation"], executables: [], note: "Source wave launches one read-only subagent per feature file." },
23
25
  "make-bot-ui": { tier: "unsupported", capabilities: [], executables: ["curl", "tailscale", "sudo"], note: "Built entirely on Cursor Automations webhook routines and the secret-request card." },
24
26
  "no-comments": { tier: "dependency-gated", capabilities: ["delegation"], executables: ["git"], note: "Step 1 must spawn the comment-sicko agent; the skill only triages its report." },
@@ -89,7 +91,13 @@ export const CAPABILITY_LABELS: Readonly<Record<CapabilityId, string>> = {
89
91
 
90
92
  export const ADAPTER_OWNED = "(adapter-owned)";
91
93
 
92
- /** Model roles pstack workflows name. A role with no configured model inherits the parent. */
94
+ /**
95
+ * Model roles pstack workflows name. `general` covers every delegate no other role names. A role
96
+ * with no configured model falls back to `general`, and inherits the parent only when `general`
97
+ * is unconfigured too.
98
+ */
99
+ export const GENERAL_ROLE = "general";
100
+
93
101
  export const MODEL_ROLES: readonly string[] = [
94
102
  "feature",
95
103
  "refactoring",
@@ -98,6 +106,7 @@ export const MODEL_ROLES: readonly string[] = [
98
106
  "hillclimb",
99
107
  "judgment",
100
108
  "strongest-judgment",
109
+ GENERAL_ROLE,
101
110
  ];
102
111
 
103
112
  export function entryFor(upstreamName: string, generatedBareName?: string): RegistryEntry {
package/src/status.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CERTIFIED } from "./certified.ts";
2
- import { CAPABILITY_LABELS, MODEL_ROLES, TIER_ORDER } from "./registry.ts";
2
+ import { CAPABILITY_LABELS, GENERAL_ROLE, MODEL_ROLES, TIER_ORDER } from "./registry.ts";
3
3
  import type { AdapterConfig } from "./config.ts";
4
4
  import { splitModelSpec, trustWarning } from "./config.ts";
5
5
  import { DELEGATION_BACKENDS, delegationOrder } from "./delegation.ts";
@@ -91,8 +91,14 @@ export function renderStatus(input: StatusInput): string {
91
91
  lines.push(`| role | model | thinking |`);
92
92
  lines.push(`| --- | --- | --- |`);
93
93
  const extra = Object.keys(config.models).filter((role) => !MODEL_ROLES.includes(role)).sort();
94
+ const general = config.models[GENERAL_ROLE];
94
95
  for (const role of [...MODEL_ROLES, ...extra]) {
95
96
  const configured = config.models[role];
97
+ if (configured === undefined && general !== undefined) {
98
+ const spec = splitModelSpec(general);
99
+ lines.push(`| ${role} | ${spec.model} (from ${GENERAL_ROLE}) | ${spec.thinking ?? "backend default"} (from ${GENERAL_ROLE}) |`);
100
+ continue;
101
+ }
96
102
  if (configured === undefined) {
97
103
  lines.push(`| ${role} | inherit (parent session model) | inherit (parent session thinking) |`);
98
104
  continue;
package/src/upstream.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFile } from "node:child_process";
2
- import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
2
+ import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  import { promisify } from "node:util";
5
5
  import { MANIFEST_RELATIVE_PATH } from "./manifest.ts";
@@ -202,7 +202,7 @@ async function reuseCheckout(
202
202
  wanted: string,
203
203
  pluginPath: string,
204
204
  ): Promise<{ commit: string; pluginDir: string } | undefined> {
205
- if (!existsSync(join(dir, ".git"))) return undefined;
205
+ if (!(await isCacheCheckout(git, dir))) return undefined;
206
206
  if ((await git(["cat-file", "-e", `${wanted}^{commit}`], dir)).code !== 0) return undefined;
207
207
 
208
208
  // A read-only cache cannot take the setting. That is fine while its files are already LF, and
@@ -307,15 +307,43 @@ async function headCommit(git: GitRunner, dir: string): Promise<string> {
307
307
  return result.code === 0 ? result.stdout.trim() : "";
308
308
  }
309
309
 
310
+ /**
311
+ * Git discovers ancestor repositories from an empty cache inside a project. A successful
312
+ * rev-parse alone therefore does not authorize config, fetch, or checkout. Adapter caches
313
+ * must own both their worktree and standalone metadata, including the common directory.
314
+ */
315
+ async function isCacheCheckout(git: GitRunner, dir: string): Promise<boolean> {
316
+ const metadata = join(dir, ".git");
317
+ try {
318
+ const entry = lstatSync(metadata);
319
+ if (!entry.isDirectory() || entry.isSymbolicLink()) return false;
320
+ const identity = await git([
321
+ "rev-parse", "--show-toplevel", "--absolute-git-dir", "--git-common-dir",
322
+ ], dir);
323
+ if (identity.code !== 0) return false;
324
+ const paths = identity.stdout.trim().split(/\r?\n/);
325
+ if (paths.length !== 3) return false;
326
+ return [dir, metadata, metadata].every((expected, index) => {
327
+ const actual = paths[index];
328
+ return actual !== undefined && relative(realpathSync.native(expected), realpathSync.native(resolve(dir, actual))) === "";
329
+ });
330
+ } catch {
331
+ return false;
332
+ }
333
+ }
334
+
310
335
  async function bootstrap(git: GitRunner, dir: string, repo: string, ref: string, pluginPath: string): Promise<void> {
311
336
  mkdirSync(dir, { recursive: true });
312
- if ((await git(["rev-parse", "--git-dir"], dir)).code === 0) {
337
+ if (await isCacheCheckout(git, dir)) {
313
338
  await git(["remote", "set-url", "origin", repo], dir);
314
339
  } else {
315
340
  // A half-created or damaged cache is disposable. Start it over rather than repairing it.
316
341
  rmSync(dir, { recursive: true, force: true });
317
342
  mkdirSync(dir, { recursive: true });
318
343
  await expect(git(["init", "--quiet", "."], dir), "initialize the adapter cache checkout");
344
+ if (!(await isCacheCheckout(git, dir))) {
345
+ throw new Error(`The pstack adapter cache at ${dir} could not verify ownership of its Git worktree and metadata after initialization. Check directory permissions and inherited Git directory overrides, then start Pi again.`);
346
+ }
319
347
  await expect(git(["remote", "add", "origin", repo], dir), `add ${repo} as the cache remote`);
320
348
  }
321
349
  await pinLineEndings(git, dir);