@mgiles/perk 3.2.0 → 3.3.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.
Files changed (202) hide show
  1. package/README.md +5 -0
  2. package/extension/authoring/gist/draft.ts +198 -0
  3. package/extension/authoring/gist/prose.ts +46 -0
  4. package/extension/authoring/gist/review.ts +133 -0
  5. package/extension/authoring/gist/save.ts +118 -0
  6. package/extension/authoring/objective/draft.ts +345 -0
  7. package/extension/{factories/objectiveDreamReport.ts → authoring/objective/dreamReportGate.ts} +74 -131
  8. package/extension/authoring/objective/planning.ts +124 -0
  9. package/extension/authoring/objective/prose.ts +103 -0
  10. package/extension/authoring/objective/review.ts +128 -0
  11. package/extension/authoring/objective/save.ts +224 -0
  12. package/extension/authoring/plan/draft.ts +84 -0
  13. package/extension/authoring/plan/prose.ts +41 -0
  14. package/extension/authoring/plan/review.ts +269 -0
  15. package/extension/authoring/plan/save.ts +256 -0
  16. package/extension/authoring/plan/source.ts +82 -0
  17. package/extension/authoring/refinement/context.ts +468 -0
  18. package/extension/authoring/refinement/draft.ts +261 -0
  19. package/extension/authoring/refinement/prose.ts +79 -0
  20. package/extension/authoring/refinement/review.ts +111 -0
  21. package/extension/authoring/refinement/save.ts +119 -0
  22. package/extension/authoring/review/approvalGate.ts +34 -0
  23. package/extension/authoring/review/draftContext.ts +68 -0
  24. package/extension/codeReview/automated.ts +352 -0
  25. package/extension/codeReview/submission.ts +229 -0
  26. package/extension/delivery/address.ts +295 -0
  27. package/extension/delivery/ci.ts +355 -0
  28. package/extension/delivery/commitCompact.ts +93 -0
  29. package/extension/delivery/conflictResolution.ts +247 -0
  30. package/extension/delivery/ready.ts +193 -0
  31. package/extension/delivery/stackConflict.ts +361 -0
  32. package/extension/delivery/stackObjective.ts +16 -0
  33. package/extension/delivery/stackReconcile.ts +165 -0
  34. package/extension/delivery/submit.ts +171 -0
  35. package/extension/index.ts +365 -380
  36. package/extension/learning/analystWave.ts +324 -0
  37. package/extension/learning/audit.ts +667 -0
  38. package/extension/learning/capture.ts +92 -0
  39. package/extension/learning/containment.ts +104 -0
  40. package/extension/{waves/dreamWave.ts → learning/dream.ts} +112 -94
  41. package/extension/learning/dreamAnalysis.ts +435 -0
  42. package/extension/{waves/dreamReducerWave.ts → learning/dreamReducer.ts} +46 -41
  43. package/extension/{waves → learning}/dreamReport.ts +35 -31
  44. package/extension/learning/harvest.ts +491 -0
  45. package/extension/learning/prose.ts +66 -0
  46. package/extension/learning/routing.ts +79 -0
  47. package/extension/pi/v1/bashScanTimeout.ts +64 -0
  48. package/extension/{doors/prReview.ts → pi/v1/codeReview/automated.ts} +215 -311
  49. package/extension/{doors/prReviewBrowser.ts → pi/v1/codeReview/browser.ts} +53 -33
  50. package/extension/{doors/hunkHandoff.ts → pi/v1/codeReview/checkout.ts} +12 -8
  51. package/extension/{doors/reviewWaveTools.ts → pi/v1/codeReview/reviewWave.ts} +146 -114
  52. package/extension/{doors/stackReviewBrowser.ts → pi/v1/codeReview/stack.ts} +62 -29
  53. package/extension/pi/v1/codeReview/submit.ts +354 -0
  54. package/extension/{doors/prReviewTerminal.ts → pi/v1/codeReview/terminal.ts} +32 -27
  55. package/extension/pi/v1/contextEvidence.ts +80 -0
  56. package/extension/pi/v1/contextInjection.ts +207 -0
  57. package/extension/{doors → pi/v1/delivery}/address.ts +154 -267
  58. package/extension/pi/v1/delivery/ci.ts +570 -0
  59. package/extension/pi/v1/delivery/commitCompact.ts +201 -0
  60. package/extension/pi/v1/delivery/conflictResolverEngine.ts +425 -0
  61. package/extension/{doors → pi/v1/delivery}/land.ts +123 -61
  62. package/extension/pi/v1/delivery/ready.ts +322 -0
  63. package/extension/pi/v1/delivery/stackConflictResolver.ts +172 -0
  64. package/extension/pi/v1/delivery/stackDrive.ts +120 -0
  65. package/extension/pi/v1/delivery/stackLand.ts +223 -0
  66. package/extension/pi/v1/delivery/stackRecover.ts +265 -0
  67. package/extension/pi/v1/delivery/stackStatus.ts +237 -0
  68. package/extension/pi/v1/delivery/stackSync.ts +658 -0
  69. package/extension/pi/v1/delivery/submit.ts +389 -0
  70. package/extension/pi/v1/delivery/submitConflict.ts +186 -0
  71. package/extension/pi/v1/draftReview.ts +431 -0
  72. package/extension/{doors → pi/v1}/draftReviewWaveTools.ts +141 -151
  73. package/extension/pi/v1/gist.ts +794 -0
  74. package/extension/pi/v1/learning/audit.ts +186 -0
  75. package/extension/pi/v1/learning/dream.ts +207 -0
  76. package/extension/{doors/learnFactory.ts → pi/v1/learning/factory.ts} +18 -65
  77. package/extension/{doors/harvestWaveTools.ts → pi/v1/learning/harvest.ts} +46 -100
  78. package/extension/pi/v1/learning/learn.ts +585 -0
  79. package/extension/pi/v1/lifecycleGates.ts +127 -0
  80. package/extension/{factories → pi/v1}/objective.ts +53 -33
  81. package/extension/pi/v1/objectiveAuthoring.ts +672 -0
  82. package/extension/pi/v1/objectiveDreamGate.ts +160 -0
  83. package/extension/{factories/objectivePlan.ts → pi/v1/objectivePlanning.ts} +328 -533
  84. package/extension/pi/v1/objectiveRefinement.ts +1320 -0
  85. package/extension/pi/v1/objectiveReview.ts +451 -0
  86. package/extension/{doors → pi/v1}/objectiveReviewBrowser.ts +259 -172
  87. package/extension/pi/v1/plan.ts +812 -0
  88. package/extension/pi/v1/planReview.ts +820 -0
  89. package/extension/{doors → pi/v1}/planReviewBrowser.ts +228 -152
  90. package/extension/{doors/annotationPush.ts → pi/v1/providers/annotations.ts} +158 -89
  91. package/extension/pi/v1/providers/plannotator.ts +487 -0
  92. package/extension/{doors → pi/v1/providers}/plannotatorHandoff.ts +73 -27
  93. package/extension/pi/v1/providers/selection.ts +43 -0
  94. package/extension/{adapters/planAdapterTombell.ts → pi/v1/providers/tombell.ts} +43 -72
  95. package/extension/pi/v1/review.ts +538 -0
  96. package/extension/pi/v1/reviewOutcome.ts +9 -0
  97. package/extension/pi/v1/scoutWave.ts +318 -0
  98. package/extension/{doors → pi/v1}/selfcheck.ts +4 -4
  99. package/extension/session/branchWorkflowSession.ts +60 -0
  100. package/extension/session/lifecycle.ts +644 -0
  101. package/extension/session/lifecycleGates.ts +64 -0
  102. package/extension/session/saveDestination.ts +87 -0
  103. package/extension/session/workflowSession.ts +971 -0
  104. package/extension/substrate/agentScratch.ts +27 -54
  105. package/extension/substrate/bashScanTimeout.ts +181 -0
  106. package/extension/substrate/bindingDelivery.ts +38 -30
  107. package/extension/substrate/bindings.ts +4 -5
  108. package/extension/substrate/cache.ts +64 -12
  109. package/extension/substrate/childRestrictions.ts +39 -0
  110. package/extension/substrate/coldDoor.ts +17 -1
  111. package/extension/substrate/config.ts +157 -21
  112. package/extension/substrate/git.ts +88 -6
  113. package/extension/substrate/modelVisible.ts +53 -0
  114. package/extension/substrate/prompts.ts +22 -0
  115. package/extension/substrate/registry.ts +2 -0
  116. package/extension/substrate/resolverLease.ts +5 -4
  117. package/extension/substrate/sessionData.ts +85 -152
  118. package/extension/substrate/toolGating.ts +263 -84
  119. package/extension/substrate/unifiedDiff.ts +1 -1
  120. package/extension/substrate/workflowState.ts +178 -163
  121. package/extension/substrate/worktreeResolverLock.ts +261 -0
  122. package/extension/surfaces/surfaces.ts +79 -27
  123. package/extension/waves/adversarialReviewWave.ts +87 -46
  124. package/extension/waves/blockedReports.ts +59 -0
  125. package/extension/waves/draftReviewWave.ts +42 -42
  126. package/extension/waves/laneIdentity.ts +77 -0
  127. package/extension/waves/objectiveExplorerWave.ts +24 -24
  128. package/extension/waves/prReviewWave.ts +89 -77
  129. package/extension/waves/reportWave.ts +438 -578
  130. package/extension/waves/reviewClassifierWave.ts +22 -22
  131. package/extension/waves/rpcAdapter.ts +100 -15
  132. package/extension/waves/scoutWave.ts +192 -0
  133. package/extension/waves/transport.ts +480 -0
  134. package/extension/worker/sdkAdapter.ts +494 -0
  135. package/extension/worker/stageExecution.ts +679 -0
  136. package/extension/workerMain.ts +18 -19
  137. package/package.json +6 -4
  138. package/prompts/_fixtures/live.yaml +43 -18
  139. package/prompts/contexts/adapters/plannotator-gist.md +6 -0
  140. package/prompts/contexts/adapters/plannotator-objective.md +6 -0
  141. package/prompts/contexts/adapters/plannotator-plan.md +8 -1
  142. package/prompts/contexts/adapters/plannotator-refinement.md +22 -0
  143. package/prompts/contexts/objective-refinement.md +17 -0
  144. package/prompts/contexts/read-only.md +1 -1
  145. package/prompts/stages/conflict-resolution-continuation.md +9 -6
  146. package/prompts/stages/conflict-resolution.md +4 -4
  147. package/prompts/stages/objective-plan/guidance.md +2 -2
  148. package/prompts/stages/objective-plan/seed.md +9 -1
  149. package/prompts/stages/objective-reconcile-ready.md +1 -1
  150. package/prompts/stages/objective-reconcile.md +1 -1
  151. package/prompts/stages/objective-refine/seed.md +18 -0
  152. package/prompts/stages/objective-review-browser.md +4 -4
  153. package/prompts/stages/objective-sync.md +1 -1
  154. package/prompts/stages/plan-review-browser.md +4 -4
  155. package/prompts/stages/pr-review-browser/active.md +3 -4
  156. package/prompts/stages/pr-review-browser/foreign.md +3 -4
  157. package/prompts/stages/pr-review-terminal/active.md +3 -3
  158. package/prompts/stages/pr-review-terminal/foreign.md +3 -3
  159. package/prompts/stages/pr-review.md +3 -3
  160. package/prompts/stages/stack-review-browser/stack.md +5 -6
  161. package/shared/README.md +8 -0
  162. package/shared/bindings.yaml +3 -3
  163. package/shared/contracts.md +2601 -506
  164. package/shared/fixtures/issues-table.json +130 -0
  165. package/shared/registry.yaml +13 -0
  166. package/shared/schemas/outputs/objective-node-engagement.schema.json +318 -0
  167. package/shared/schemas/outputs/objective-stack-status.schema.json +6 -1
  168. package/shared/schemas/outputs/pr-review-context.schema.json +54 -9
  169. package/shared/schemas/outputs/pr-review-stack-context.schema.json +196 -0
  170. package/extension/adapters/planAdapterPlannotator.ts +0 -362
  171. package/extension/doors/auditWaveTools.ts +0 -352
  172. package/extension/doors/ciExecutor.ts +0 -756
  173. package/extension/doors/commitCompact.ts +0 -251
  174. package/extension/doors/dreamWaveTools.ts +0 -489
  175. package/extension/doors/learn.ts +0 -668
  176. package/extension/doors/lifecycleGates.ts +0 -207
  177. package/extension/doors/objectiveStack.ts +0 -1543
  178. package/extension/doors/prReviewDynamic.ts +0 -276
  179. package/extension/doors/ready.ts +0 -279
  180. package/extension/doors/submit.ts +0 -373
  181. package/extension/doors/submitPrReview.ts +0 -505
  182. package/extension/factories/gistAuthor.ts +0 -94
  183. package/extension/factories/gistDraft.ts +0 -265
  184. package/extension/factories/gistSave.ts +0 -251
  185. package/extension/factories/implementHere.ts +0 -116
  186. package/extension/factories/objectiveAuthor.ts +0 -98
  187. package/extension/factories/objectiveDraft.ts +0 -466
  188. package/extension/factories/objectiveSave.ts +0 -366
  189. package/extension/factories/planDraft.ts +0 -140
  190. package/extension/factories/planMode.ts +0 -205
  191. package/extension/factories/planReview.ts +0 -1237
  192. package/extension/factories/planSave.ts +0 -604
  193. package/extension/factories/planTitle.ts +0 -141
  194. package/extension/substrate/structuredOutput.ts +0 -202
  195. package/extension/waves/auditWave.ts +0 -312
  196. package/extension/waves/harvestWave.ts +0 -399
  197. package/extension/waves/learnWave.ts +0 -155
  198. package/extension/waves/memoryAdapter.ts +0 -139
  199. package/extension/waves/prReviewDynamicWave.ts +0 -777
  200. package/extension/worker/readOnlySession.ts +0 -294
  201. package/extension/worker/worker.ts +0 -899
  202. package/prompts/stages/pr-review-dynamic.md +0 -7
@@ -1,8 +1,9 @@
1
1
  // The tool-gating primitive (the keystone). Structural read-only enforcement, NOT
2
2
  // prompting. Mirrors pi's authoritative `examples/extensions/plan-mode/` recipe (the
3
3
  // `setActiveTools` allowlist + `tool_call` bash sub-allowlist + `before_agent_start` injection
4
- // (once-only: branch-scan dedup'd on the marker, so a session carries ONE live copy) +
5
- // `context` strip-when-off) and `preset.ts`'s snapshot-then-restore. The gate attaches to the
4
+ // (once-only per SELECTED BRANCH: full-branch-scan dedup'd on the marker — historical, so a
5
+ // copy compaction has summarized out of model context still suppresses; enforcement never rode
6
+ // the prose) + `context` strip-when-off) and `preset.ts`'s snapshot-then-restore. The gate attaches to the
6
7
  // existing `perk:workflow-state.mode` field (`read-only`/`read-write`) — no new registry stage.
7
8
  // Beside the gate lives STAGE_TOOLS: per-stage active-tool scoping for the scoped universe
8
9
  // (perk's OWN registered tools + the enumerated borrowed-package census), keyed off the
@@ -11,7 +12,7 @@
11
12
  //
12
13
  // Substrate only: perk-owned plan mode and the read-only CI executor are the consumers of the
13
14
  // `enter`/`exit` surface; the allowlist-restore is wired into the existing
14
- // `session_start`/`session_tree` rebuild points.
15
+ // `session_start`/`session_tree` rebuild points plus one `resources_discover` re-apply.
15
16
 
16
17
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
17
18
  import { render } from "./prompts.ts";
@@ -83,12 +84,12 @@ export const LINEAR_MUTATING_TOOLS: readonly string[] = [
83
84
  ];
84
85
 
85
86
  /**
86
- * pi-subagents' delegation family. `subagent`/`wait` register at load time; the parent intercom
87
- * pair (`subagent_supervisor`, `intercom`) registers during `session_start` — AFTER perk's sync
88
- * (perk is the first `packages` entry), so it LEAKS past rebuild-point filtering at launch
89
- * (≈830 schema chars accepted, documented, test-pinned). A later `session_tree` re-apply
90
- * filters over the original snapshot (which lacks the late names), so a tree navigation drops
91
- * them the pre-existing snapshot behavior, unchanged. Child-side tools (`contact_supervisor`,
87
+ * pi-subagents' delegation family. `subagent`/`wait` register at load time; the parent supervisor
88
+ * tool `subagent_supervisor` registers during pi-subagents' own `session_start` — AFTER perk's
89
+ * sync (perk is the first `packages` entry) and is admitted by the `resources_discover`
90
+ * re-apply as a late registrant (see `admitLate`): inside the diet at launch, kept where a stage
91
+ * list carries it. `intercom` is the separate pi-intercom bridge's tool name a static census
92
+ * entry, inert unless that package is present. Child-side tools (`contact_supervisor`,
92
93
  * `structured_output`) are out of scope for the STAGE census — spawned children stay
93
94
  * stage-unscoped by design (§8.40 adopt-never-impersonates) — but they DO ride READ_ONLY_TOOLS,
94
95
  * because the read-only gate IS inherited by adopted children (see SUBAGENT_CHILD_TOOLS).
@@ -102,36 +103,31 @@ export const SUBAGENT_TOOLS: readonly string[] = [
102
103
 
103
104
  /**
104
105
  * pi-subagents' CHILD-side engine tools. `structured_output` and `contact_supervisor` register
105
- * only inside spawned child sessions (the env-keyed prompt-runtime registration never in
106
- * parents, so those names are inert in every gated parent session; `setActiveTools` ignores
107
- * unknown names). `subagent_wait` is ALSO registered by the top-level parent extension
108
- * (pi-subagents 0.45.1's `registerWaitTool`) — the accepted widening: a wait-only,
109
- * non-repo-mutating tool active in gated parents. A gated ADOPTED child (mode inherited via the
110
- * `adopt` arm, contracts.md §8.3) must keep them active:
106
+ * inside spawned child sessions through the engine's prompt runtime / native supervisor bridge.
107
+ * Absent tools are inert in gated parents (`setActiveTools` ignores unknown names). A gated
108
+ * ADOPTED child (mode inherited via the `adopt` arm, contracts.md §8.3) must keep them active:
111
109
  * - `structured_output` is the engine-REQUIRED completion call when the launch carries an
112
110
  * `outputSchema` — stripping it makes the child physically unable to finish and fails the
113
111
  * run with `structuredOutputFailed`;
114
- * - `contact_supervisor` is the child→parent intercom door;
115
- * - `subagent_wait` is the fanout-child wait door.
112
+ * - `contact_supervisor` is the child→parent supervisor door.
113
+ * Native wakes need no wait-tool widening in this census.
116
114
  * None mutates the repo (`structured_output` writes only the engine's capture file under
117
115
  * `.pi-subagents/` scratch). Census decision, recorded: these names deliberately join NEITHER
118
116
  * PERK_TOOLS nor BORROWED_TOOLS — the stage-filter universe never sees them because children
119
117
  * are stage-unscoped by design (adopt never impersonates a stage), so gate membership is their
120
118
  * only governance surface.
121
119
  */
122
- export const SUBAGENT_CHILD_TOOLS: readonly string[] = [
123
- "structured_output",
124
- "contact_supervisor",
125
- "subagent_wait",
126
- ];
120
+ export const SUBAGENT_CHILD_TOOLS: readonly string[] = ["structured_output", "contact_supervisor"];
127
121
 
128
122
  /**
129
123
  * @ff-labs/pi-fff's search tools. BOTH mode name-sets are enumerated (static names, inert
130
124
  * when absent — the code_search version-tolerance precedent): warm sessions run pi-fff's
131
- * default tools-and-ui mode (fffind/ffgrep [+ fff-multi-grep when enabled upstream]);
132
- * perk cold launches inject PI_FFF_MODE=override, where FFF registers under the builtin
133
- * names find/grep (already allowlisted/pass-through) plus multi_grep. All register at
134
- * load time. Frecency/history state lives under ~/.pi/agent/fff/ outside the worktree
125
+ * default tools-and-ui mode (fffind/ffgrep [+ fff-multi-grep when enabled upstream]), and
126
+ * perk launches inject PI_FFF_MODE=tools-and-ui (the same additive mode) so pi's builtin
127
+ * find/grep stay host-builtins for pi-subagents' host-tool intersection (an override-mode
128
+ * pi-fff shadows them by name and fails review/scout lanes closed). The override name-set
129
+ * (multi_grep; find/grep already allowlisted/pass-through) stays enumerated for an operator
130
+ * `PI_FFF_MODE=override` opt-in. All register at load time. Frecency/history state lives under ~/.pi/agent/fff/ — outside the worktree
135
131
  * (the fetch_content cache-write precedent), so the read-only bar holds.
136
132
  */
137
133
  export const FFF_SEARCH_TOOLS: readonly string[] = [
@@ -149,12 +145,12 @@ export const FFF_SEARCH_TOOLS: readonly string[] = [
149
145
  * stage filter (fail-open — enumeration here is diet-completeness, not correctness).
150
146
  *
151
147
  * Audit records (the per-package census):
152
- * - Registration timing: every census name registers at load time EXCEPT pi-subagents' parent
153
- * supervisor pair (see SUBAGENT_TOOLS — session_start, leaks past rebuild-point filtering).
148
+ * - Registration timing: every census name registers at load time EXCEPT pi-subagents'
149
+ * `subagent_supervisor` (SUBAGENT_TOOLS — session_start; admitted at `resources_discover`).
154
150
  * - Foreign `setActiveTools` owners: plannotator's phase machinery and @tombell/pi-plan's plan
155
- * mode run their OWN toggles — perk re-applies only at rebuild points, so a foreign toggle
156
- * between rebuilds wins (fail-open direction), and a mid-session rebuild re-installs perk's
157
- * stage set over a foreign restriction. Pre-existing interplay, recorded, not re-engineered.
151
+ * mode run their OWN toggles — perk re-applies only at its reconciliation points (the rebuilds
152
+ * + the startup `resources_discover` re-apply), so a toggle between them wins (fail-open) and
153
+ * every reconciliation re-installs perk's set over it, admitted late tools included (§8.40).
158
154
  * - Zero-tool packages: @tombell/pi-diff (commands only), the footer providers, and the hunk
159
155
  * review CLI (not a Pi package) register nothing — nothing to enumerate.
160
156
  * - Single-governance rule: a name is governed ONCE — it lives in exactly one census. perk
@@ -223,9 +219,9 @@ export const READ_ONLY_TOOLS = [
223
219
  // FFF local search belongs in read-only exploration (the override names find/grep are
224
220
  // already present above; these are the additive tools-and-ui names + multi_grep).
225
221
  ...FFF_SEARCH_TOOLS,
226
- // The delegation carve-in: `subagent`/`wait` (+ the parent supervisor pair, which already
227
- // leaks active into cold-door gated sessions via late registration keeping warm-entered
228
- // gates consistent, and letting the parent answer child `contact_supervisor` asks) stay
222
+ // The delegation carve-in: `subagent`/`wait` (+ pi-subagents' late-registered
223
+ // `subagent_supervisor`, kept by every gate-ON re-apply because it is allowlisted here
224
+ // letting the parent answer child `contact_supervisor` asks) stay
229
225
  // reachable while gated for the other delegation flows (the gated objective-plan guidance now
230
226
  // names the `explore_objective_node` tool below, not a direct spawn). ACCEPTED LENIENCY,
231
227
  // deliberately documented: spawned children are unscoped by design (§8.40
@@ -238,6 +234,11 @@ export const READ_ONLY_TOOLS = [
238
234
  // the already-carved-in SUBAGENT tools (the draft-review-wave precedent) and writes nothing to
239
235
  // the worktree.
240
236
  "explore_objective_node",
237
+ // The scout-wave carve-in: the authoring sessions' launcher — `run_scout_wave` spawns
238
+ // read-only `perk.scout` lanes over the already-carved-in delegation family and writes
239
+ // nothing to the worktree; reachable in every gated stage except `objective-refine` on the
240
+ // `explore_objective_node` precedent (contracts.md §8.70).
241
+ "run_scout_wave",
241
242
  // The child-side carve-in: gated adopt-children must keep the engine's injected tools — see
242
243
  // SUBAGENT_CHILD_TOOLS.
243
244
  ...SUBAGENT_CHILD_TOOLS,
@@ -273,6 +274,46 @@ export const READ_ONLY_TOOLS = [
273
274
  "run_dream_wave",
274
275
  ];
275
276
 
277
+ /**
278
+ * The refinement stage id — the ONE stage whose gate-ON selection differs from READ_ONLY_TOOLS.
279
+ * (Registry vocabulary; the feature module re-declares the same literal as its stage constant.)
280
+ */
281
+ export const REFINE_STAGE_ID = "objective-refine";
282
+
283
+ /**
284
+ * The refinement session's gate-ON selection (contracts.md §8.68): read / research / question
285
+ * / local exploration, the backend-neutral review door, and the ONE refinement draft writer.
286
+ * Deliberately NOT READ_ONLY_TOOLS: no `objective_node` (a refinement never claims), no other
287
+ * draft tools (no plan/objective/gist artifact can be authored or routed from here), no save
288
+ * tools, no delegation spawn surface (children are unscoped by design) — and consequently no
289
+ * SUBAGENT_CHILD_TOOLS either: a child that inherited `stage: objective-refine` would lose
290
+ * `structured_output`/`contact_supervisor` under this allowlist. Unreachable today (no perk wave
291
+ * spawns from a refinement session; the lifecycle test pins `gatedToolsFor(stage)`) — revisit
292
+ * the moment refinement gains any delegation. Same static-name posture as READ_ONLY_TOOLS;
293
+ * `setActiveTools` ignores absent names.
294
+ */
295
+ export const REFINEMENT_READ_ONLY_TOOLS: readonly string[] = [
296
+ "read",
297
+ "grep",
298
+ "find",
299
+ "ls",
300
+ "bash",
301
+ "ask_user_question",
302
+ "plan_review",
303
+ // The objective_refinement_draft carve-out: it writes only the one working-refinement
304
+ // artifact in the session data dir (fixed artifact name, seam-derived path, bound to the
305
+ // session's grounding context); the gate's edit/write/bash blocking is unchanged.
306
+ "objective_refinement_draft",
307
+ ...WEB_RESEARCH_TOOLS,
308
+ ...LINEAR_READ_TOOLS,
309
+ ...FFF_SEARCH_TOOLS,
310
+ ];
311
+
312
+ /** The gate-ON allowlist for a stage: refinement's own selection, else READ_ONLY_TOOLS. */
313
+ export function gatedToolsFor(stage: string | null): readonly string[] {
314
+ return stage === REFINE_STAGE_ID ? REFINEMENT_READ_ONLY_TOOLS : READ_ONLY_TOOLS;
315
+ }
316
+
276
317
  /**
277
318
  * Every tool perk itself registers (contracts.md §8.40). Name-keyed: `setActiveTools` ignores
278
319
  * unknown names, so an absent tool is inert (e.g. a borrowed census name whose package stripped
@@ -291,6 +332,10 @@ export const PERK_TOOLS: readonly string[] = [
291
332
  "objective_draft",
292
333
  "gist_draft",
293
334
  "gist_save",
335
+ // The refinement session's ONE model-facing writer (contracts.md §8.67). Never in
336
+ // READ_ONLY_TOOLS: only the refinement gate-ON selection carries it, so no other gated
337
+ // stage can author a refinement draft.
338
+ "objective_refinement_draft",
294
339
  "learn",
295
340
  "run_learn_wave",
296
341
  "run_audit_wave",
@@ -302,8 +347,8 @@ export const PERK_TOOLS: readonly string[] = [
302
347
  "classify_review_feedback",
303
348
  "finalize_address",
304
349
  "explore_objective_node",
350
+ "run_scout_wave",
305
351
  "run_pr_review_wave",
306
- "run_pr_review_dynamic_wave",
307
352
  "submit_pr_review",
308
353
  "start_review_wave",
309
354
  "collect_review_wave",
@@ -312,6 +357,7 @@ export const PERK_TOOLS: readonly string[] = [
312
357
  "collect_draft_review_wave",
313
358
  "run_ci",
314
359
  "submit",
360
+ "resolve_submit_conflicts",
315
361
  // The stack-review launch-recovery tool (contracts.md §8.4): parameterless — the snapshot
316
362
  // comes only from the `perk objective stack review` launch handoff.
317
363
  "open_stack_review",
@@ -353,6 +399,7 @@ const RESEARCH_TOOLS: readonly string[] = [
353
399
  const WORKTREE_STAGE_TOOLS: readonly string[] = [
354
400
  "ask_user_question",
355
401
  "submit",
402
+ "resolve_submit_conflicts",
356
403
  "ready",
357
404
  "run_ci",
358
405
  "land",
@@ -362,7 +409,6 @@ const WORKTREE_STAGE_TOOLS: readonly string[] = [
362
409
  "finalize_address",
363
410
  "post_pr_review",
364
411
  "run_pr_review_wave",
365
- "run_pr_review_dynamic_wave",
366
412
  "submit_pr_review",
367
413
  // The human review doors' companion tools (/pr-review-terminal, /pr-review-browser): the
368
414
  // review-wave pair + the door-primed annotation push. The plan-stage widening landed via the
@@ -414,6 +460,15 @@ const WORKTREE_STAGE_TOOLS: readonly string[] = [
414
460
  * review arm; the drive-coverage guard forces both the moment the guidance names them).
415
461
  */
416
462
  export const STAGE_TOOLS: Readonly<Record<string, readonly string[]>> = {
463
+ // The isolated refinement stage (contracts.md §8.68): the gate-OFF (defensive) arm scopes the
464
+ // draft writer + the review door + research only — no PR-loop, save, claim or other draft
465
+ // tools. The session normally runs GATED, where REFINEMENT_READ_ONLY_TOOLS is the set.
466
+ [REFINE_STAGE_ID]: [
467
+ "ask_user_question",
468
+ "objective_refinement_draft",
469
+ "plan_review",
470
+ ...RESEARCH_TOOLS,
471
+ ],
417
472
  "gist-author": ["ask_user_question", "gist_draft", "gist_save", ...RESEARCH_TOOLS],
418
473
  "gist-save": ["ask_user_question", "gist_draft", "gist_save", ...RESEARCH_TOOLS],
419
474
  "objective-author": [
@@ -423,6 +478,10 @@ export const STAGE_TOOLS: Readonly<Record<string, readonly string[]>> = {
423
478
  "reconcile_objective",
424
479
  "add_objective_node",
425
480
  "objective_node",
481
+ // The scout launcher rides the three AUTHORING stages (plan / objective-plan /
482
+ // objective-author) and no other — the one name by which this list and objective-save
483
+ // now differ (contracts.md §8.70).
484
+ "run_scout_wave",
426
485
  // The /objective-review-browser companions (gate-OFF coverage: after objectiveApprovalSave
427
486
  // exits the gate mid-flow, late collects/pushes must not dead-end) + plan_review (the door
428
487
  // guidance names it; it routes to the objective review arm here).
@@ -453,6 +512,8 @@ export const STAGE_TOOLS: Readonly<Record<string, readonly string[]>> = {
453
512
  "plan_save",
454
513
  "objective_node",
455
514
  "explore_objective_node",
515
+ // The scout launcher (see the objective-author note).
516
+ "run_scout_wave",
456
517
  "reconcile_objective",
457
518
  "add_objective_node",
458
519
  // The /plan-review-browser companions (gate-OFF coverage: after approvalSave exits the gate
@@ -468,6 +529,8 @@ export const STAGE_TOOLS: Readonly<Record<string, readonly string[]>> = {
468
529
  "plan_draft",
469
530
  "plan_review",
470
531
  "plan_save",
532
+ // The scout launcher (see the objective-author note).
533
+ "run_scout_wave",
471
534
  // The /plan-review-browser companions (see the objective-plan note).
472
535
  "start_draft_review_wave",
473
536
  "collect_draft_review_wave",
@@ -512,13 +575,44 @@ export const STAGE_TOOLS: Readonly<Record<string, readonly string[]>> = {
512
575
  /** The read-only marker / custom-message type injected into context while active. */
513
576
  const MODE_CONTEXT_TYPE = "perk:mode-context";
514
577
  const READ_ONLY_MARKER = "[READ-ONLY MODE]";
578
+ /** The refinement flavor's DISTINCT dedup marker (not a superstring of the default marker, so
579
+ * neither flavor's presence masks the other's dedup scan). */
580
+ const REFINEMENT_READ_ONLY_MARKER = "[READ-ONLY REFINEMENT MODE]";
581
+ const MODE_MARKERS: readonly string[] = [READ_ONLY_MARKER, REFINEMENT_READ_ONLY_MARKER];
515
582
 
516
583
  /** Exported for tests: the injected read-only mode context. (No tool enumeration — gate-ON
517
584
  * already applies READ_ONLY_TOOLS via setActiveTools, so the active tool set IS the list.) */
518
585
  export const READ_ONLY_CONTEXT = render("contexts/read-only.md", {
519
586
  marker: READ_ONLY_MARKER,
587
+ writer: "plan_draft",
588
+ artifact: "working-plan artifact",
520
589
  });
521
590
 
591
+ /** Exported for tests: the refinement flavor — names the ACTUAL sanctioned writer. */
592
+ export const REFINEMENT_READ_ONLY_CONTEXT = render("contexts/read-only.md", {
593
+ marker: REFINEMENT_READ_ONLY_MARKER,
594
+ writer: "objective_refinement_draft",
595
+ artifact: "working-refinement artifact",
596
+ });
597
+
598
+ /** The mode-context flavor for a stage (the refinement session names its own writer). */
599
+ function modeContextFor(stage: string | null): { marker: string; content: string } {
600
+ return stage === REFINE_STAGE_ID
601
+ ? { marker: REFINEMENT_READ_ONLY_MARKER, content: REFINEMENT_READ_ONLY_CONTEXT }
602
+ : { marker: READ_ONLY_MARKER, content: READ_ONLY_CONTEXT };
603
+ }
604
+
605
+ function textCarries(content: unknown, needles: readonly string[]): boolean {
606
+ if (typeof content === "string") return needles.some((n) => content.includes(n));
607
+ if (Array.isArray(content)) {
608
+ return content.some((c) => {
609
+ const part = c as { type?: string; text?: string };
610
+ return part.type === "text" && needles.some((n) => (part.text ?? "").includes(n));
611
+ });
612
+ }
613
+ return false;
614
+ }
615
+
522
616
  // --- pure policy (copied from plan-mode/utils.ts so this primitive is self-contained; perk-owned
523
617
  // so retiring the borrowed pi-plan extension leaves no dangling import) -------
524
618
 
@@ -631,8 +725,19 @@ const SAFE_PATTERNS = [
631
725
  // perk's own read-only objective queries (show/next + their s/n aliases, plus the non-mutating
632
726
  // node-engagement read the objective-plan factory needs). The trailing \b keeps the `n` alias
633
727
  // from matching the mutating `node` subcommand; node-engagement allowed; create/node/reconcile
634
- // stay blocked.
728
+ // stay blocked. node-engagement materializes a present refinement under the run scratch dir —
729
+ // the same accepted leniency as `perk pr review-context` below (the CLI writes its own
730
+ // gitignored scratch file; the destructive veto still blocks `> file` redirects).
635
731
  /^\s*perk\s+(objective|obj)\s+(show|s|next|n|node-engagement)\b/i,
732
+ // Report children need exactly these query forms, not arbitrary PR operations: plan-bound
733
+ // children (`/pr-review`, `/address`) use the `--expected-pr` form; the human-triage doors'
734
+ // adversarial children — and the stack-review routing step — use the foreign `--pr` /
735
+ // `--pr --stack` forms (a read-only parent would otherwise block the doors' children outright).
736
+ // ONE anchored alternation: `--json` last, nothing else; the flagless form stays out (only the
737
+ // write-capable foreground conflict-resolver uses it, outside the gate). The destructive veto
738
+ // still blocks `> file` redirects — the CLI writes its own scratch files.
739
+ /^\s*perk\s+pr\s+review-context\s+(?:--expected-pr\s+[1-9][0-9]*|--pr\s+[1-9][0-9]*(?:\s+--stack)?)\s+--json\s*$/,
740
+ /^\s*perk\s+pr\s+feedback\s+--json\s*$/,
636
741
  // Read-only `gh` queries — the guidance in the managed AGENTS block ("GitHub access goes
637
742
  // through gh") must be followable in read-only sessions. Query-shaped subcommands only;
638
743
  // `gh api` stays blocked (it can POST/PATCH), as do all mutating subcommands (create/edit/
@@ -652,7 +757,7 @@ const SAFE_PATTERNS = [
652
757
  * Known limitation: backslash-escaped quote characters are not handled. This is acceptable — the
653
758
  * whole-string destructive veto in isReadOnlyBashCommand remains the backstop.
654
759
  */
655
- function splitTopLevelSegments(command: string): string[] {
760
+ export function splitTopLevelSegments(command: string): string[] {
656
761
  const segments: string[] = [];
657
762
  let current = "";
658
763
  let quote: '"' | "'" | null = null;
@@ -740,16 +845,37 @@ function isReadOnlyMode(mode: string | undefined): boolean {
740
845
  return mode === "read-only";
741
846
  }
742
847
 
743
- export function registerToolGating(pi: ExtensionAPI): ToolGating {
848
+ /** The engagement record: the host's active starting set (the restore authority — never
849
+ * `getAllTools()`), the registry census at snapshot time, and the late registrants admitted so far. */
850
+ type Engaged = { snapshot: readonly string[]; census: ReadonlySet<string>; admitted: Set<string> };
851
+
852
+ export function registerToolGating(
853
+ pi: ExtensionAPI,
854
+ readOnlyFloor: () => boolean = () => false,
855
+ ): ToolGating {
744
856
  // In-memory gate (mirrors plan-mode's `planModeEnabled`): the authority `tool_call` consults.
745
857
  // Fail-closed — a failed sync never opens this; tool_call blocks on any internal error.
746
858
  let active = false;
747
859
  // The branch-LWW stage id this session is scoped to (null = unscoped). Fail-open by contrast
748
860
  // with `active`: no stage / unknown stage / any lookup miss → no filtering.
749
861
  let stageId: string | null = null;
750
- // Pre-engagement tool snapshot, taken ONCE on the first engagement of either concern (the
751
- // preset.ts discipline, shared by the gate and stage scoping).
752
- let snapshot: string[] | null = null;
862
+ // Taken ONCE on the first engagement of either concern (the preset.ts discipline, shared by the
863
+ // gate and stage scoping); cleared when neither is engaged any more.
864
+ let engaged: Engaged | null = null;
865
+
866
+ function hasFloor(): boolean {
867
+ try {
868
+ return readOnlyFloor();
869
+ } catch {
870
+ // A broken restriction supplier cannot grant authority for this observation.
871
+ return true;
872
+ }
873
+ }
874
+
875
+ const isActive = () => active || hasFloor();
876
+ // The gate-ON allowlist follows the scoped stage: refinement's own selection, else the
877
+ // shared READ_ONLY_TOOLS (recomputed per observation — a late stage sync re-scopes it).
878
+ const gatedToolNames = (): ReadonlySet<string> => new Set(gatedToolsFor(stageId));
753
879
 
754
880
  /**
755
881
  * Recompute + install the active tool set from both concerns (contracts.md §8.40):
@@ -758,10 +884,10 @@ export function registerToolGating(pi: ExtensionAPI): ToolGating {
758
884
  * `/objective-plan` carve-out and recreate the seed/gate contradiction class). "The gate
759
885
  * never widens a stage's set and vice versa" still holds: engaging the gate only ever
760
886
  * narrows, and stage scoping never adds a tool.
761
- * - gate OFF + stage scoped → a SUBTRACTIVE filter over the snapshot: names outside the
762
- * scoped universe (builtins, un-enumerated foreign tools) pass through untouched; scoped
763
- * names (PERK_TOOLS ∪ BORROWED_TOOLS) survive only when the stage's list carries them.
764
- * - neither engaged → restore the snapshot if one exists (a session that never engages gets
887
+ * - gate OFF + stage scoped → a SUBTRACTIVE filter over the baseline (`admitLate`): names
888
+ * outside the scoped universe (builtins, un-enumerated foreign tools) pass through untouched;
889
+ * scoped names (PERK_TOOLS ∪ BORROWED_TOOLS) survive only when the stage's list carries them.
890
+ * - neither engaged → restore the baseline and forget it (a session that never engages gets
765
891
  * ZERO setActiveTools calls — bare warm sessions stay byte-identical).
766
892
  * While engaged the set is re-installed on every sync (tree navigation across mode entries
767
893
  * must recompute correctly).
@@ -770,40 +896,73 @@ export function registerToolGating(pi: ExtensionAPI): ToolGating {
770
896
  // un-enumerated foreign names pass through every stage filter untouched (fail-open).
771
897
  const SCOPED_TOOL_NAMES: ReadonlySet<string> = new Set([...PERK_TOOLS, ...BORROWED_TOOLS]);
772
898
 
899
+ /**
900
+ * The gate-OFF reconciliation baseline `snapshot ∪ admitted`. A tool the census never saw
901
+ * (pi-subagents' `subagent_supervisor` registers in its own `session_start`, after perk's sync)
902
+ * is admitted the first time it is seen active and stays admitted through perk's own filtering
903
+ * (so a navigation back to an admitting stage restores it); an owner that deactivates its late
904
+ * tool before perk ever sees it active is respected; a tool the census saw inactive is never
905
+ * re-activated. Gate-OFF paths only (the gate-ON set is by name — no bookkeeping there).
906
+ */
907
+ function admitLate(e: Engaged): string[] {
908
+ for (const name of pi.getActiveTools()) if (!e.census.has(name)) e.admitted.add(name);
909
+ return [...new Set([...e.snapshot, ...e.admitted])];
910
+ }
911
+
773
912
  function apply(nextActive: boolean, nextStage: string | null): void {
913
+ // Fail-closed: a read-only sync engages the in-memory gate BEFORE the fallible reads/installs.
914
+ if (nextActive) active = true;
915
+ const effective = nextActive || hasFloor();
774
916
  const stageList = nextStage === null ? undefined : STAGE_TOOLS[nextStage];
775
- // First engagement of either concern: take the one snapshot.
776
- if ((nextActive || stageList !== undefined) && snapshot === null) {
777
- snapshot = pi.getActiveTools();
917
+ // First engagement of either concern: ONE literal (a throwing read records no half state).
918
+ if ((effective || stageList !== undefined) && engaged === null) {
919
+ engaged = {
920
+ snapshot: pi.getActiveTools(),
921
+ census: new Set(pi.getAllTools().map((t) => t.name)),
922
+ admitted: new Set(),
923
+ };
778
924
  }
779
- if (nextActive) {
780
- pi.setActiveTools([...READ_ONLY_TOOLS]);
781
- } else if (stageList !== undefined) {
782
- // The snapshot-missing fallback mirrors the restore path below: the FULL configured tool
783
- // set (pi.getAllTools()), never a hardcoded list that would silently drop grep/find/ls
784
- // and perk's custom tools (plan_save/submit/land/learn).
785
- const base = snapshot ?? pi.getAllTools().map((t) => t.name);
786
- pi.setActiveTools(
787
- base.filter((name) => !SCOPED_TOOL_NAMES.has(name) || stageList.includes(name)),
788
- );
789
- } else if (snapshot !== null) {
790
- pi.setActiveTools(snapshot);
791
- snapshot = null;
925
+ if (effective) {
926
+ pi.setActiveTools([...gatedToolsFor(nextStage)]);
927
+ } else if (engaged !== null) {
928
+ const base = admitLate(engaged);
929
+ if (stageList !== undefined) {
930
+ pi.setActiveTools(
931
+ base.filter((name) => !SCOPED_TOOL_NAMES.has(name) || stageList.includes(name)),
932
+ );
933
+ } else {
934
+ pi.setActiveTools(base);
935
+ engaged = null;
936
+ }
792
937
  }
793
938
  active = nextActive;
794
939
  stageId = nextStage;
795
940
  }
796
941
 
797
- // Structural backstop: block writes + non-allowlisted bash while active. Fail-closed on error.
942
+ // Pi fires `resources_discover` after EVERY extension's `session_start` has run (pi-subagents
943
+ // registers `subagent_supervisor` there, after perk's sync) — the one point where startup-late
944
+ // registrants meet the gate/stage diet: re-apply the in-memory mode/stage (no rebuild, no other
945
+ // checkpoint). Idempotent on `reason: "reload"`; a throw is Pi-reported and never opens the gate.
946
+ pi.on("resources_discover", async () => {
947
+ apply(active, stageId);
948
+ });
949
+
950
+ // Enforce the whole allowlist even if toolset narrowing failed or foreign tools registered late.
798
951
  pi.on("tool_call", async (event) => {
799
952
  try {
800
- if (!active) return;
953
+ if (!isActive()) return;
801
954
  if (event.toolName === "edit" || event.toolName === "write") {
802
955
  return {
803
956
  block: true,
804
957
  reason: `perk read-only mode: ${event.toolName} is blocked (file modifications disabled).`,
805
958
  };
806
959
  }
960
+ if (!gatedToolNames().has(event.toolName)) {
961
+ return {
962
+ block: true,
963
+ reason: `perk read-only mode: ${event.toolName} is blocked (tool not allowlisted).`,
964
+ };
965
+ }
807
966
  if (event.toolName === "bash") {
808
967
  const command = String((event.input as { command?: unknown }).command ?? "");
809
968
  if (!isReadOnlyBashCommand(command)) {
@@ -822,33 +981,51 @@ export function registerToolGating(pi: ExtensionAPI): ToolGating {
822
981
 
823
982
  // Inject the hidden read-only mode context while active (display:false → not shown in transcript).
824
983
  pi.on("before_agent_start", async (_event, ctx) => {
825
- if (!active) return;
826
- // Once-only: injected customs persist to the branch, so a live copy suppresses re-injection;
827
- // compaction dropping it makes the scan come up clean and the next turn re-injects.
828
- if (branchCarries(branchOf(ctx), READ_ONLY_MARKER)) return;
984
+ if (!isActive()) return;
985
+ // Once-only per selected branch: injected customs persist to the branch, and the FULL-branch
986
+ // scan (not live model context) dedups a copy compaction summarized away still counts as
987
+ // delivered on this branch history and is NOT re-injected; navigating to a branch that never
988
+ // carried it injects again. Deliberate: this is the structural gate's guidance, and the gate
989
+ // itself (tool_call) enforces regardless of what the model can still read.
990
+ // The flavor follows the scoped stage: the refinement session names its actual writer. The
991
+ // dedup key is the SELECTED flavor's marker, so a session gated under the default flavor
992
+ // that then enters refinement still receives the refinement flavor once.
993
+ const flavor = modeContextFor(stageId);
994
+ try {
995
+ if (branchCarries(branchOf(ctx), flavor.marker)) return;
996
+ } catch {
997
+ // An unreadable branch cannot suppress required read-only guidance.
998
+ }
829
999
  return {
830
- message: { customType: MODE_CONTEXT_TYPE, content: READ_ONLY_CONTEXT, display: false },
1000
+ message: { customType: MODE_CONTEXT_TYPE, content: flavor.content, display: false },
831
1001
  };
832
1002
  });
833
1003
 
834
- // Strip the stale read-only marker from context when the gate is off (so it never lingers).
1004
+ // Gate OFF: strip every stale mode-context flavor (so none lingers). Gate ON: retain ONLY the
1005
+ // current flavor's injected block — a stale `plan_draft`-only block from before a stage
1006
+ // change is dropped from model context (the injected customType only; user content is never
1007
+ // touched while the gate is on).
835
1008
  pi.on("context", async (event) => {
836
- if (active) return;
1009
+ if (isActive()) {
1010
+ const current = modeContextFor(stageId).marker;
1011
+ const stale = (m: unknown): boolean => {
1012
+ const msg = m as { customType?: string; content?: unknown };
1013
+ return (
1014
+ msg.customType === MODE_CONTEXT_TYPE &&
1015
+ textCarries(msg.content, MODE_MARKERS) &&
1016
+ !textCarries(msg.content, [current])
1017
+ );
1018
+ };
1019
+ // No stale flavor → no intervention (the hook stays a no-op for every other message).
1020
+ if (!event.messages.some(stale)) return;
1021
+ return { messages: event.messages.filter((m) => !stale(m)) };
1022
+ }
837
1023
  return {
838
1024
  messages: event.messages.filter((m) => {
839
1025
  const msg = m as { customType?: string; role?: string; content?: unknown };
840
1026
  if (msg.customType === MODE_CONTEXT_TYPE) return false;
841
1027
  if (msg.role !== "user") return true;
842
- const content = msg.content;
843
- if (typeof content === "string") return !content.includes(READ_ONLY_MARKER);
844
- if (Array.isArray(content)) {
845
- return !content.some(
846
- (c) =>
847
- (c as { type?: string; text?: string }).type === "text" &&
848
- ((c as { text?: string }).text ?? "").includes(READ_ONLY_MARKER),
849
- );
850
- }
851
- return true;
1028
+ return !textCarries(msg.content, MODE_MARKERS);
852
1029
  }),
853
1030
  };
854
1031
  });
@@ -862,11 +1039,13 @@ export function registerToolGating(pi: ExtensionAPI): ToolGating {
862
1039
  apply(true, stageId);
863
1040
  },
864
1041
  exit(_ctx?: ExtensionContext): void {
1042
+ if (hasFloor()) {
1043
+ apply(true, stageId);
1044
+ return;
1045
+ }
865
1046
  pi.appendEntry(WORKFLOW_STATE_TYPE, { mode: "read-write" });
866
1047
  apply(false, stageId);
867
1048
  },
868
- isActive(): boolean {
869
- return active;
870
- },
1049
+ isActive,
871
1050
  };
872
1051
  }
@@ -1,5 +1,5 @@
1
1
  // A deliberately strict unified-diff applier for the plannotator "Direct Edits" feedback only
2
- // (`extension/adapters/planAdapterPlannotator.ts` extracts the ```diff fence; the plan arm of
2
+ // (`extension/pi/v1/providers/plannotator.ts` extracts the ```diff fence; the plan arm of
3
3
  // `plan_review` applies it to the exact draft bytes it submitted) — this is NOT a general-purpose
4
4
  // patch tool.
5
5
  //