@opengsd/gsd-pi 1.8.1-dev.897b3c90 → 1.9.0-dev.5c1a3ff2

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 (194) hide show
  1. package/README.md +10 -8
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/GSD-WORKFLOW.md +2 -2
  4. package/dist/resources/extensions/gsd/artifact-verification.js +3 -4
  5. package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -8
  6. package/dist/resources/extensions/gsd/auto-prompts.js +90 -17
  7. package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
  8. package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +4 -0
  9. package/dist/resources/extensions/gsd/auto-worktree-teardown.js +1 -1
  10. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -0
  11. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +31 -5
  12. package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -3
  13. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -24
  14. package/dist/resources/extensions/gsd/constants.js +12 -13
  15. package/dist/resources/extensions/gsd/context-masker.js +97 -21
  16. package/dist/resources/extensions/gsd/docs/preferences-reference.md +22 -0
  17. package/dist/resources/extensions/gsd/doctor-engine-checks.js +3 -6
  18. package/dist/resources/extensions/gsd/git-service.js +19 -7
  19. package/dist/resources/extensions/gsd/guided-flow.js +9 -3
  20. package/dist/resources/extensions/gsd/markdown-renderer.js +2 -12
  21. package/dist/resources/extensions/gsd/md-importer.js +5 -6
  22. package/dist/resources/extensions/gsd/model-cost-table.js +2 -0
  23. package/dist/resources/extensions/gsd/model-router.js +11 -0
  24. package/dist/resources/extensions/gsd/paths.js +51 -10
  25. package/dist/resources/extensions/gsd/planning-subagent-policy.js +37 -0
  26. package/dist/resources/extensions/gsd/planning-subagent-registry.js +25 -0
  27. package/dist/resources/extensions/gsd/preferences-types.js +5 -0
  28. package/dist/resources/extensions/gsd/preferences-validation.js +48 -1
  29. package/dist/resources/extensions/gsd/preferences.js +17 -0
  30. package/dist/resources/extensions/gsd/provider-payload-policy.js +16 -5
  31. package/dist/resources/extensions/gsd/safety/destructive-guard.js +1 -1
  32. package/dist/resources/extensions/gsd/skill-activation.js +17 -3
  33. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
  34. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -5
  35. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +14 -5
  36. package/dist/resources/extensions/gsd/tools/reopen-slice.js +15 -5
  37. package/dist/resources/extensions/gsd/tools/reopen-task.js +16 -10
  38. package/dist/resources/extensions/gsd/unit-context-composer.js +28 -5
  39. package/dist/resources/extensions/gsd/workflow-projections.js +2 -3
  40. package/dist/resources/extensions/gsd/workspace.js +2 -1
  41. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  42. package/dist/web/standalone/.next/BUILD_ID +1 -1
  43. package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
  44. package/dist/web/standalone/.next/build-manifest.json +2 -2
  45. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  46. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/index.html +1 -1
  63. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
  70. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  71. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  73. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  74. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  75. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  76. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  77. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  78. package/integrations/hermes/pyproject.toml +1 -1
  79. package/package.json +6 -6
  80. package/packages/cloud-mcp-gateway/package.json +2 -2
  81. package/packages/contracts/package.json +1 -1
  82. package/packages/daemon/package.json +4 -4
  83. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
  84. package/packages/gsd-agent-core/dist/compaction/compaction.js +36 -4
  85. package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
  86. package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
  87. package/packages/gsd-agent-core/dist/sdk.js +1 -0
  88. package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
  89. package/packages/gsd-agent-core/package.json +5 -5
  90. package/packages/gsd-agent-modes/package.json +7 -7
  91. package/packages/gsd-cloud/package.json +1 -1
  92. package/packages/mcp-server/README.md +7 -2
  93. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  94. package/packages/mcp-server/dist/server.js +5 -5
  95. package/packages/mcp-server/dist/server.js.map +1 -1
  96. package/packages/mcp-server/dist/workflow-tools.d.ts +4 -3
  97. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  98. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  99. package/packages/mcp-server/package.json +4 -4
  100. package/packages/native/package.json +1 -1
  101. package/packages/pi-agent-core/dist/agent.d.ts +5 -1
  102. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  103. package/packages/pi-agent-core/dist/agent.js +2 -0
  104. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  105. package/packages/pi-agent-core/package.json +1 -1
  106. package/packages/pi-ai/dist/models.generated.d.ts +59 -0
  107. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/models.generated.js +53 -0
  109. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  110. package/packages/pi-ai/dist/providers/anthropic.js +6 -1
  111. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  112. package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
  113. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  114. package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
  115. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  116. package/packages/pi-ai/package.json +1 -1
  117. package/packages/pi-coding-agent/dist/core/capability-patches.js +1 -1
  118. package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
  120. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +11 -1
  122. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  124. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  125. package/packages/pi-coding-agent/package.json +7 -7
  126. package/packages/pi-tui/package.json +2 -2
  127. package/packages/rpc-client/package.json +2 -2
  128. package/pkg/package.json +1 -1
  129. package/src/resources/GSD-WORKFLOW.md +2 -2
  130. package/src/resources/extensions/gsd/artifact-verification.ts +3 -8
  131. package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -7
  132. package/src/resources/extensions/gsd/auto-prompts.ts +104 -15
  133. package/src/resources/extensions/gsd/auto-recovery.ts +10 -8
  134. package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +4 -0
  135. package/src/resources/extensions/gsd/auto-worktree-teardown.ts +1 -1
  136. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -0
  137. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -10
  138. package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -3
  139. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +7 -29
  140. package/src/resources/extensions/gsd/constants.ts +14 -13
  141. package/src/resources/extensions/gsd/context-masker.ts +98 -2
  142. package/src/resources/extensions/gsd/docs/preferences-reference.md +22 -0
  143. package/src/resources/extensions/gsd/doctor-engine-checks.ts +3 -5
  144. package/src/resources/extensions/gsd/git-service.ts +19 -6
  145. package/src/resources/extensions/gsd/guided-flow.ts +9 -2
  146. package/src/resources/extensions/gsd/markdown-renderer.ts +2 -14
  147. package/src/resources/extensions/gsd/md-importer.ts +5 -5
  148. package/src/resources/extensions/gsd/model-cost-table.ts +2 -0
  149. package/src/resources/extensions/gsd/model-router.ts +12 -0
  150. package/src/resources/extensions/gsd/paths.ts +58 -10
  151. package/src/resources/extensions/gsd/planning-subagent-policy.ts +58 -0
  152. package/src/resources/extensions/gsd/planning-subagent-registry.ts +31 -0
  153. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  154. package/src/resources/extensions/gsd/preferences-validation.ts +62 -0
  155. package/src/resources/extensions/gsd/preferences.ts +25 -0
  156. package/src/resources/extensions/gsd/provider-payload-policy.ts +18 -4
  157. package/src/resources/extensions/gsd/safety/destructive-guard.ts +1 -1
  158. package/src/resources/extensions/gsd/skill-activation.ts +17 -3
  159. package/src/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
  160. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +7 -7
  161. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +39 -0
  162. package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +55 -0
  163. package/src/resources/extensions/gsd/tests/complete-task.test.ts +76 -1
  164. package/src/resources/extensions/gsd/tests/context-masker.test.ts +204 -8
  165. package/src/resources/extensions/gsd/tests/db-tools-alias-suppression.test.ts +62 -0
  166. package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +33 -0
  167. package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +14 -0
  168. package/src/resources/extensions/gsd/tests/integration/paths.test.ts +22 -4
  169. package/src/resources/extensions/gsd/tests/integration-branch-meta-location.test.ts +62 -0
  170. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +5 -5
  171. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  172. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  173. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +7 -5
  174. package/src/resources/extensions/gsd/tests/preferences.test.ts +84 -0
  175. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +4 -0
  176. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +66 -0
  177. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +39 -0
  178. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  179. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +33 -7
  180. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -16
  181. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +13 -7
  182. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -0
  183. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +39 -0
  184. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +4 -4
  185. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +34 -0
  186. package/src/resources/extensions/gsd/tools/complete-task.ts +20 -5
  187. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +24 -5
  188. package/src/resources/extensions/gsd/tools/reopen-slice.ts +25 -5
  189. package/src/resources/extensions/gsd/tools/reopen-task.ts +23 -9
  190. package/src/resources/extensions/gsd/unit-context-composer.ts +34 -8
  191. package/src/resources/extensions/gsd/workflow-projections.ts +2 -3
  192. package/src/resources/extensions/gsd/workspace.ts +2 -1
  193. /package/dist/web/standalone/.next/static/{TezjdjSHBVRRr54XjAvte → 6YWV7VAPg-jqjfPswrKZb}/_buildManifest.js +0 -0
  194. /package/dist/web/standalone/.next/static/{TezjdjSHBVRRr54XjAvte → 6YWV7VAPg-jqjfPswrKZb}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -28,14 +28,16 @@ See [CHANGELOG.md](./CHANGELOG.md) for release-by-release fixes and [Legacy Rele
28
28
  ## Latest Release Highlights
29
29
 
30
30
  <!-- release-highlights:start -->
31
- Latest release: **v1.8.1**
32
-
33
- - **doctor:** Extract state checks.
34
- - **doctor:** Extract history and workspace checks.
35
- - **issue:** Unregistered-milestone false positive: flat-phase ID extractor derives bare M### from descriptive slug, missing suffixed DB row (M###-abcdef) (#1282).
36
- - **issue:** [Bug] reconcile triggers mass re-projection of all historical PLAN.md files, resetting completed task checkboxes to unchecked (#1279).
37
- - **issue:** [Bug]: `migrateHierarchyToDb` imports a `[sketch]` slice's stub PLAN tasks as real DB rows, flipping planning→executing.
38
- - **issue:** [Bug]: `sketchFlagHandler` clears `is_sketch` on mere PLAN-file existence, including a stub or leftover PLAN.
31
+ Latest release: **v1.9.0**
32
+
33
+ - **gsd:** Add task rework briefs.
34
+ - **web:** Delete unused chart.tsx and recharts.
35
+ - **deps:** Move ajv to devDependencies.
36
+ - **daemon:** Dedupe @anthropic-ai/sdk to ^0.91.1.
37
+ - **deps:** Override hono to >=4.12.25.
38
+ - **gsd:** Batch plan-v2 task queries and skip unchanged graph writes.
39
+ - **gsd:** Open the worker DB once per parallel-monitor tick.
40
+ - **gsd:** Batch parallel-eligibility task queries.
39
41
 
40
42
  <!-- release-highlights:end -->
41
43
 
@@ -1 +1 @@
1
- 4c3ea176cf8f5545
1
+ 1252616a01ee43bc
@@ -367,7 +367,7 @@ When verification finds gaps, include a **Gaps** section with what's missing, im
367
367
  ### Phase 6: Summarize
368
368
 
369
369
  **Purpose:** Record what happened for downstream tasks.
370
- **Produces:** `T##-SUMMARY.md`, and when slice completes, `S##-SUMMARY.md`.
370
+ **Produces:** `S##-T##-SUMMARY.md` in flat-phase projects, and when slice completes, `S##-SUMMARY.md`. Legacy flat `T##-SUMMARY.md` task summaries are still readable.
371
371
 
372
372
  **Task summary format:**
373
373
  ```markdown
@@ -494,7 +494,7 @@ It is NOT the source of truth. It's a convenience dashboard.
494
494
  **Sources of truth:**
495
495
  - `ROADMAP.md` → which slices exist and which are done
496
496
  - `<NN>-<MM>-PLAN.md` → which tasks exist within a slice and what each task must do
497
- - `T##-SUMMARY.md` → what happened during a task
497
+ - `S##-T##-SUMMARY.md` → what happened during a task in flat-phase projects (legacy `T##-SUMMARY.md` is still readable)
498
498
  - `S##-SUMMARY.md` and `M###-SUMMARY.md` → compressed slice and milestone outcomes
499
499
 
500
500
  **Update `STATE.md`** after every significant action:
@@ -9,7 +9,7 @@ import { refreshWorkflowDatabaseFromDisk } from "./db-workspace.js";
9
9
  import { isValidationTerminal } from "./state.js";
10
10
  import { getErrorMessage } from "./error-utils.js";
11
11
  import { logWarning, logError } from "./workflow-logger.js";
12
- import { resolveSlicePath, resolveSliceFile, resolveTasksDir, resolveTaskFiles, relSliceFile, resolveMilestoneFile, clearPathCache, resolveGsdRootFile, } from "./paths.js";
12
+ import { resolveSlicePath, resolveSliceFile, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relSliceFile, resolveMilestoneFile, clearPathCache, resolveGsdRootFile, } from "./paths.js";
13
13
  import { existsSync, readFileSync } from "node:fs";
14
14
  import { execFileSync } from "node:child_process";
15
15
  import { LAYOUT_SEGMENTS } from "./layout-policy.js";
@@ -185,10 +185,9 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
185
185
  const batchIds = batchPart.slice(plusIdx + 1).split(",").filter(Boolean);
186
186
  if (batchIds.length === 0)
187
187
  return false;
188
- const tDir = resolveTasksDir(base, mid, sid) ?? slicePath;
189
- const existingSummaries = new Set(resolveTaskFiles(tDir, "SUMMARY").map((f) => f.replace(/-SUMMARY\.md$/i, "").toUpperCase()));
190
188
  for (const tid of batchIds) {
191
- if (!existingSummaries.has(tid.toUpperCase()))
189
+ const summaryPath = resolveTaskFile(base, mid, sid, tid, "SUMMARY");
190
+ if (!summaryPath || !existsSync(summaryPath))
192
191
  return false;
193
192
  }
194
193
  return true;
@@ -5,7 +5,7 @@
5
5
  // resolveExpectedArtifactPath and diagnoseExpectedArtifact moved here from
6
6
  // auto-recovery.ts (Phase 5 dead-code cleanup). The artifact verification
7
7
  // function was removed entirely — callers now query WorkflowEngine directly.
8
- import { gsdRoot, resolveDir, resolveFile, resolveMilestonePath, resolveMilestoneFile, resolveSliceFile, relMilestoneFile, relSliceFile, buildTaskFileName, resolveSlicePath, resolveTasksDir, dirIsMetaOnlyLegacyMilestone, normalizeRealPath, } from "./paths.js";
8
+ import { gsdRoot, resolveDir, resolveFile, resolveMilestonePath, resolveMilestoneFile, resolveSliceFile, relMilestoneFile, relSliceFile, buildFlatTaskFileName, buildTaskFileName, resolveSlicePath, resolveTasksDir, dirIsMetaOnlyLegacyMilestone, normalizeRealPath, } from "./paths.js";
9
9
  import { milestoneIdToPhaseNum } from "./layout-policy.js";
10
10
  import { parseUnitId } from "./unit-id.js";
11
11
  import { basename, dirname, join, relative } from "node:path";
@@ -79,12 +79,15 @@ function resolveProjectMilestonePath(base, mid) {
79
79
  const dir = resolveDir(milestonesDir, mid);
80
80
  if (!dir)
81
81
  return null;
82
- // git-service.ts creates milestones/<MID>/ for integration-branch metadata
83
- // (<MID>-META.json) even in flat-phase projects. A dir that holds ONLY
84
- // *-META.json files must not be treated as a real legacy milestone dir
85
- // otherwise this early-return resolves CONTEXT/ROADMAP/SUMMARY to the legacy
86
- // path (milestones/<MID>/<MID>-<SUFFIX>.md) before the flat-phase fallback
87
- // can run, trapping the unit in a finalize-retry loop (#852 follow-up).
82
+ // Historically git-service.ts wrote integration-branch metadata into
83
+ // milestones/<MID>/<MID>-META.json, which could create that dir in flat-phase
84
+ // projects and poison layout detection. As of ADR-045 that META lives flat at
85
+ // .gsd/<MID>-META.json and no longer poisons detection. A dir that holds ONLY
86
+ // *-META.json files (a pre-migration on-disk tree) must still not be treated
87
+ // as a real legacy milestone dir otherwise this early-return resolves
88
+ // CONTEXT/ROADMAP/SUMMARY to the legacy path (milestones/<MID>/<MID>-<SUFFIX>.md)
89
+ // before the flat-phase fallback can run, trapping the unit in a
90
+ // finalize-retry loop (#852 follow-up).
88
91
  //
89
92
  // We use dirIsMetaOnlyLegacyMilestone rather than !dirIsContentBearingLegacyMilestone
90
93
  // so that an EMPTY dir (a new milestone before any content is written) is NOT
@@ -203,7 +206,10 @@ export function resolveExpectedArtifactPath(unitType, unitId, base) {
203
206
  const summaryDir = isLegacySlice
204
207
  ? (resolveTasksDir(base, mid, sid) ?? slicePath)
205
208
  : slicePath;
206
- return join(summaryDir, buildTaskFileName(tid, "SUMMARY"));
209
+ const fileName = isLegacySlice
210
+ ? buildTaskFileName(tid, "SUMMARY")
211
+ : buildFlatTaskFileName(sid, tid, "SUMMARY");
212
+ return join(summaryDir, fileName);
207
213
  }
208
214
  case "complete-slice": {
209
215
  return resolveSliceArtifactPath(base, mid, sid, "SUMMARY");
@@ -10,12 +10,12 @@
10
10
  import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection } from "./files.js";
11
11
  import { extractVerdict } from "./verdict-parser.js";
12
12
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
13
- import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, relTaskFile, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
13
+ import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, taskIdFromTaskFileName, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, relTaskFile, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
14
14
  import { resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
15
15
  import { createRepositoryRegistryFromPreferences } from "./repository-registry.js";
16
16
  import { isContextModeEnabled } from "./preferences-types.js";
17
17
  import { parseRoadmap } from "./parsers-legacy.js";
18
- import { join, relative, sep } from "node:path";
18
+ import { join, basename, relative, sep } from "node:path";
19
19
  import { existsSync } from "node:fs";
20
20
  import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
21
21
  import { getBlockingReworkFindingsForTask, getGateResults, getPendingGatesForTurn } from "./gsd-db.js";
@@ -62,13 +62,29 @@ function resolvePromptBudgets() {
62
62
  const prefs = loadEffectiveGSDPreferences();
63
63
  const sessionWindow = prefs?.preferences.context_window_override;
64
64
  const windowTokens = resolveExecutorContextWindow(undefined, prefs?.preferences, sessionWindow);
65
- return computeBudgets(windowTokens);
65
+ return computeBudgets(windowTokens, resolveExecutionProvider(prefs?.preferences));
66
66
  }
67
67
  catch (e) {
68
68
  logWarning("prompt", `resolvePromptBudgets failed: ${e.message}`);
69
69
  return computeBudgets(200_000);
70
70
  }
71
71
  }
72
+ /**
73
+ * Best-effort provider for the chars/token ratio: the executor's configured
74
+ * model profile is the only provider source reachable here (the runtime
75
+ * `sessionProvider` that reaches `formatExecutorConstraints` isn't threaded to
76
+ * `capPreamble`/summary callers). Undefined when no explicit provider is set —
77
+ * `computeBudgets` then uses the 4.0 default, i.e. today's behavior.
78
+ */
79
+ function resolveExecutionProvider(prefs) {
80
+ const exec = prefs?.models?.execution;
81
+ if (exec && typeof exec === "object" && "provider" in exec) {
82
+ const provider = exec.provider;
83
+ if (typeof provider === "string" && provider)
84
+ return provider;
85
+ }
86
+ return undefined;
87
+ }
72
88
  /**
73
89
  * Character budget for dependency/prior slice summaries injected into dispatch
74
90
  * prompts. Scales with the executor's configured context window (issue #4435).
@@ -200,7 +216,7 @@ function formatCloseoutReviewInstructions(validationContent, validationRel, curr
200
216
  `No passing validation artifact was found at \`${validationRel}\`. Use the full closeout review path before completion.`,
201
217
  ].join("\n");
202
218
  }
203
- function capPreamble(preamble) {
219
+ export function capPreamble(preamble) {
204
220
  // Cap inlined context at min(static ceiling, scaled inline budget).
205
221
  // The ceiling bounds repeated auto prompt payloads; the scaled
206
222
  // budget tightens the cap for small-window users whose true safe limit is
@@ -210,6 +226,35 @@ function capPreamble(preamble) {
210
226
  return preamble;
211
227
  return truncateAtSectionBoundary(preamble, budget).content;
212
228
  }
229
+ /**
230
+ * Cap the execute-task inline blocks against one shared budget in priority
231
+ * order. Unlike `capPreamble` (single concatenated preamble), execute-task
232
+ * feeds these blocks into separate, non-contiguous template placeholders, so
233
+ * they can't be joined and re-split — the budget is spent block-by-block.
234
+ *
235
+ * The task plan is the authoritative execution contract: it is never truncated
236
+ * here (it consumes budget first, whole). Remaining budget flows to the slice
237
+ * excerpt, then templates, each truncated at section boundaries so the lowest-
238
+ * priority context drops whole trailing sections first. When the task plan
239
+ * alone meets or exceeds the budget, the trailing blocks are dropped rather
240
+ * than the task plan being cut.
241
+ */
242
+ function capExecuteTaskInlineBlocks(taskPlan, slicePlan, templates, budgetChars) {
243
+ let remaining = budgetChars - taskPlan.length;
244
+ if (remaining <= 0) {
245
+ return { taskPlan, slicePlan: "", templates: "" };
246
+ }
247
+ const cappedSlice = slicePlan.length <= remaining
248
+ ? slicePlan
249
+ : truncateAtSectionBoundary(slicePlan, remaining).content;
250
+ remaining -= cappedSlice.length;
251
+ const cappedTemplates = remaining <= 0
252
+ ? ""
253
+ : templates.length <= remaining
254
+ ? templates
255
+ : truncateAtSectionBoundary(templates, remaining).content;
256
+ return { taskPlan, slicePlan: cappedSlice, templates: cappedTemplates };
257
+ }
213
258
  function trackPromptContext(entries, key, mode, body, reason) {
214
259
  entries.push({
215
260
  key,
@@ -253,7 +298,7 @@ function renderContextModeBlockForPrompt(unitType, base, renderMode = "standalon
253
298
  return `${contextMode}\n\n## Context Snapshot\nSource: \`.gsd/last-snapshot.md\`\n\n${snapshot.trimEnd()}`;
254
299
  }
255
300
  function prependContextModeToBlock(unitType, base, block, renderMode = "standalone") {
256
- const toolSurface = composeToolSurfaceInstructions(unitType, { renderMode });
301
+ const toolSurface = composeToolSurfaceInstructions(unitType, { renderMode, basePath: base });
257
302
  const contextMode = renderContextModeBlockForPrompt(unitType, base, renderMode);
258
303
  const guidance = [toolSurface, contextMode].filter(Boolean).join("\n\n");
259
304
  if (!guidance)
@@ -310,7 +355,7 @@ function formatExecutorConstraints(sessionContextWindow, modelRegistry, sessionP
310
355
  // so DEFAULT_CONTEXT_WINDOW stays the single source of truth.
311
356
  windowTokens = resolveExecutorContextWindow(undefined, undefined, sessionContextWindow, sessionProvider);
312
357
  }
313
- const budgets = computeBudgets(windowTokens);
358
+ const budgets = computeBudgets(windowTokens, sessionProvider);
314
359
  const { min, max } = budgets.taskCountRange;
315
360
  const execWindowK = Math.round(windowTokens / 1000);
316
361
  const perTaskBudgetK = Math.round(budgets.inlineContextBudgetChars / 1000);
@@ -1245,6 +1290,13 @@ function resolveTaskSummariesLocation(base, mid, sid) {
1245
1290
  return { dir: tDir, relPrefix: `${sRel}/tasks` };
1246
1291
  return { dir: slicePath, relPrefix: sRel };
1247
1292
  }
1293
+ function summaryFileBelongsToSlice(fileName, base, mid, sid) {
1294
+ const tid = taskIdFromTaskFileName(fileName, "SUMMARY");
1295
+ if (!tid)
1296
+ return false;
1297
+ const resolved = resolveTaskFile(base, mid, sid, tid, "SUMMARY");
1298
+ return resolved !== null && basename(resolved) === fileName;
1299
+ }
1248
1300
  export async function getPriorTaskSummaryPaths(mid, sid, currentTid, base) {
1249
1301
  const loc = resolveTaskSummariesLocation(base, mid, sid);
1250
1302
  if (!loc)
@@ -1253,7 +1305,12 @@ export async function getPriorTaskSummaryPaths(mid, sid, currentTid, base) {
1253
1305
  const currentNum = parseInt(currentTid.replace(/^T/, ""), 10);
1254
1306
  return summaryFiles
1255
1307
  .filter(f => {
1256
- const num = parseInt(f.replace(/^T/, ""), 10);
1308
+ if (!summaryFileBelongsToSlice(f, base, mid, sid))
1309
+ return false;
1310
+ const tid = taskIdFromTaskFileName(f, "SUMMARY");
1311
+ if (!tid)
1312
+ return false;
1313
+ const num = parseInt(tid.replace(/^T/, ""), 10);
1257
1314
  return num < currentNum;
1258
1315
  })
1259
1316
  .map(f => `${loc.relPrefix}/${f}`);
@@ -1280,8 +1337,11 @@ export async function getDependencyTaskSummaryPaths(mid, sid, currentTid, depend
1280
1337
  const depSet = new Set(dependsOn.map((d) => d.toUpperCase()));
1281
1338
  return summaryFiles
1282
1339
  .filter((f) => {
1283
- // Extract task ID from filename: "T02-SUMMARY.md" → "T02"
1284
- const tid = f.replace(/-SUMMARY\.md$/i, "").toUpperCase();
1340
+ if (!summaryFileBelongsToSlice(f, base, mid, sid))
1341
+ return false;
1342
+ const tid = taskIdFromTaskFileName(f, "SUMMARY");
1343
+ if (!tid)
1344
+ return false;
1285
1345
  return depSet.has(tid);
1286
1346
  })
1287
1347
  .map((f) => `${loc.relPrefix}/${f}`);
@@ -2307,7 +2367,7 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
2307
2367
  // Compute verification budget for the executor's context window (issue #707)
2308
2368
  const prefs = loadEffectiveGSDPreferences();
2309
2369
  const contextWindow = resolveExecutorContextWindow(opts.modelRegistry, prefs?.preferences, opts.sessionContextWindow, opts.sessionProvider);
2310
- const budgets = computeBudgets(contextWindow);
2370
+ const budgets = computeBudgets(contextWindow, opts.sessionProvider);
2311
2371
  const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
2312
2372
  // Truncate carry-forward section when it exceeds 40% of inline context budget.
2313
2373
  const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
@@ -2409,10 +2469,19 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
2409
2469
  }
2410
2470
  },
2411
2471
  });
2412
- const taskPlanInline = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "task-plan");
2413
- const slicePlanExcerpt = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "slice-plan");
2472
+ const rawTaskPlanInline = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "task-plan");
2473
+ const rawSlicePlanExcerpt = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "slice-plan");
2414
2474
  const contractedCarryForward = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "prior-task-summaries");
2415
- const contractedTemplates = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "templates");
2475
+ const rawContractedTemplates = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "templates");
2476
+ // Cap the static inline blocks against one shared budget so a small-window
2477
+ // executor can't overflow (the sibling builders cap via `capPreamble`;
2478
+ // execute-task's blocks feed separate template slots, so they share a budget
2479
+ // block-by-block). Carry-forward already has its own truncation above.
2480
+ const inlineBudget = Math.min(MAX_PREAMBLE_CHARS, budgets.inlineContextBudgetChars);
2481
+ const { taskPlan: taskPlanInline, slicePlan: slicePlanExcerpt, templates: contractedTemplates } = capExecuteTaskInlineBlocks(rawTaskPlanInline, rawSlicePlanExcerpt, rawContractedTemplates, inlineBudget);
2482
+ const inlineDropped = (rawTaskPlanInline.length + rawSlicePlanExcerpt.length + rawContractedTemplates.length)
2483
+ - (taskPlanInline.length + slicePlanExcerpt.length + contractedTemplates.length);
2484
+ trackPromptContext(contextTelemetry, "inline-cap", inlineDropped > 0 ? "excerpt" : "skipped", null, inlineDropped > 0 ? `dropped ${inlineDropped} chars (budget ${inlineBudget})` : "within budget");
2416
2485
  const onDemandResult = renderExecuteTaskOnDemandContext(base, mid, sid, contractedContext.onDemand);
2417
2486
  const onDemandContext = onDemandResult.text;
2418
2487
  trackPromptContext(contextTelemetry, "slice-research", onDemandContext ? "on-demand" : "skipped", onDemandContext, onDemandContext ? undefined : onDemandResult.skipReason ?? undefined);
@@ -2497,12 +2566,14 @@ export async function buildCompleteSlicePrompt(mid, midTitle, sid, sTitle, base,
2497
2566
  trackPromptContext(contextTelemetry, "prior-task-summaries", "skipped", null, "missing tasks dir");
2498
2567
  return null;
2499
2568
  }
2500
- const summaryFiles = resolveTaskFiles(loc.dir, "SUMMARY").sort();
2569
+ const summaryFiles = resolveTaskFiles(loc.dir, "SUMMARY")
2570
+ .filter((file) => summaryFileBelongsToSlice(file, base, mid, sid))
2571
+ .sort();
2501
2572
  const blocks = [];
2502
2573
  for (const file of summaryFiles) {
2503
2574
  const absPath = join(loc.dir, file);
2504
2575
  const relPath = `${loc.relPrefix}/${file}`;
2505
- const taskId = file.replace(/-SUMMARY\.md$/i, "");
2576
+ const taskId = taskIdFromTaskFileName(file, "SUMMARY") ?? file.replace(/-SUMMARY\.md$/i, "");
2506
2577
  blocks.push(await buildTaskSummaryExcerpt(absPath, relPath, taskId));
2507
2578
  }
2508
2579
  const body = blocks.length > 0 ? blocks.join("\n\n---\n\n") : null;
@@ -3072,7 +3143,9 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
3072
3143
  let blockerTaskId = "";
3073
3144
  const summaryLoc = resolveTaskSummariesLocation(base, mid, sid);
3074
3145
  if (summaryLoc) {
3075
- const summaryFiles = resolveTaskFiles(summaryLoc.dir, "SUMMARY").sort();
3146
+ const summaryFiles = resolveTaskFiles(summaryLoc.dir, "SUMMARY")
3147
+ .filter((file) => summaryFileBelongsToSlice(file, base, mid, sid))
3148
+ .sort();
3076
3149
  for (const file of summaryFiles) {
3077
3150
  const absPath = join(summaryLoc.dir, file);
3078
3151
  const content = await loadFile(absPath);
@@ -3081,7 +3154,7 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
3081
3154
  const summary = parseSummary(content);
3082
3155
  const relPath = `${summaryLoc.relPrefix}/${file}`;
3083
3156
  if (summary.frontmatter.blocker_discovered) {
3084
- blockerTaskId = summary.frontmatter.id || file.replace(/-SUMMARY\.md$/i, "");
3157
+ blockerTaskId = summary.frontmatter.id || taskIdFromTaskFileName(file, "SUMMARY") || file.replace(/-SUMMARY\.md$/i, "");
3085
3158
  inlined.push(await buildTaskSummaryExcerpt(absPath, relPath, blockerTaskId, { blocker: true }));
3086
3159
  }
3087
3160
  }
@@ -17,7 +17,7 @@ import { refreshWorkflowDatabaseFromDisk } from "./db-workspace.js";
17
17
  import { getErrorMessage } from "./error-utils.js";
18
18
  import { logWarning } from "./workflow-logger.js";
19
19
  import { isClosedStatus, isInactiveStatus } from "./status-guards.js";
20
- import { resolveSlicePath, resolveTasksDir, resolveTaskFiles, relMilestoneFile, relSliceFile, clearPathCache, } from "./paths.js";
20
+ import { resolveSlicePath, resolveTaskFile, relMilestoneFile, relSliceFile, clearPathCache, } from "./paths.js";
21
21
  import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
22
22
  import { dirname } from "node:path";
23
23
  import { resolveExpectedArtifactPath, diagnoseExpectedArtifact, } from "./auto-artifact-paths.js";
@@ -227,10 +227,15 @@ export function writeReactiveExecuteBlocker(unitId, base, reason) {
227
227
  const slicePath = resolveSlicePath(base, mid, sid);
228
228
  if (!slicePath)
229
229
  return null;
230
- const tasksDir = resolveTasksDir(base, mid, sid) ?? slicePath;
231
- const existingSummaries = new Set(resolveTaskFiles(tasksDir, "SUMMARY").map((f) => f.replace(/-SUMMARY\.md$/i, "").toUpperCase()));
232
- const summaryPresent = batchIds.filter((tid) => existingSummaries.has(tid.toUpperCase()));
233
- const summaryMissing = batchIds.filter((tid) => !existingSummaries.has(tid.toUpperCase()));
230
+ // Resolve each batch task's SUMMARY with slice-qualified paths so flat-phase
231
+ // slices that reuse a task id (e.g. two T03s in one phase) don't collide: a
232
+ // sibling slice's summary must never make this slice's task look complete.
233
+ const hasSummary = (tid) => {
234
+ const summaryPath = resolveTaskFile(base, mid, sid, tid, "SUMMARY");
235
+ return summaryPath !== null && existsSync(summaryPath);
236
+ };
237
+ const summaryPresent = batchIds.filter((tid) => hasSummary(tid));
238
+ const summaryMissing = batchIds.filter((tid) => !hasSummary(tid));
234
239
  const completedTaskIds = [];
235
240
  const skippedTaskIds = [];
236
241
  const unchangedTaskIds = [];
@@ -8,6 +8,7 @@ import { execFileSync } from "node:child_process";
8
8
  import { existsSync, realpathSync, unlinkSync } from "node:fs";
9
9
  import { isAbsolute, join, relative } from "node:path";
10
10
  import { gsdRoot } from "./paths.js";
11
+ import { milestoneMetaPath } from "./git-service.js";
11
12
  import { logWarning } from "./workflow-logger.js";
12
13
  function isSamePath(a, b) {
13
14
  try {
@@ -73,6 +74,9 @@ export function clearProjectRootStateFiles(basePath, milestoneId) {
73
74
  // worth removing on teardown.
74
75
  const transientFiles = [
75
76
  join(gsdDir, "STATE.md"),
77
+ // Integration-branch META now lives flat at .gsd/<MID>-META.json (ADR-045).
78
+ milestoneMetaPath(basePath, milestoneId),
79
+ // Legacy location — still cleaned for pre-migration trees.
76
80
  join(gsdDir, "milestones", milestoneId, `${milestoneId}-META.json`),
77
81
  ];
78
82
  for (const file of transientFiles) {
@@ -45,7 +45,7 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
45
45
  throw new GSDError(GSD_IO_ERROR, `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`);
46
46
  }
47
47
  // Mirror cleanup steps from mergeMilestoneToMain abort path:
48
- // 1. Remove transient state files (STATE.md, auto.lock, {MID}-META.json).
48
+ // 1. Remove transient state files (STATE.md, auto.lock, .gsd/{MID}-META.json).
49
49
  // Non-fatal — must not block teardown.
50
50
  try {
51
51
  clearProjectRootStateFiles(originalBasePath, milestoneId);
@@ -46,6 +46,8 @@ function registerAlias(pi, toolDef, aliasName, canonicalName) {
46
46
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- toolDef shape matches ToolDefinition but varies by schema
47
47
  function registerWorkflowTool(pi, toolDef) {
48
48
  pi.registerTool(toolDef);
49
+ if (process.env.GSD_ADVERTISE_TOOL_ALIASES !== "1")
50
+ return; // canonical-only model surface (see plan 035)
49
51
  for (const alias of aliasesForWorkflowTool(toolDef.name)) {
50
52
  registerAlias(pi, toolDef, alias, toolDef.name);
51
53
  }
@@ -22,13 +22,14 @@ import { saveActivityLog } from "../activity-log.js";
22
22
  import { recordToolCall as safetyRecordToolCall, recordToolResult as safetyRecordToolResult, saveEvidenceToDisk } from "../safety/evidence-collector.js";
23
23
  import { parseUnitId } from "../unit-id.js";
24
24
  import { classifyCommand } from "../safety/destructive-guard.js";
25
- import { confirmDestructiveCommand, consumeDestructiveConfirmation, isDestructiveConfirmGateId, requestDestructiveConfirmation, } from "../safety/destructive-confirmation.js";
25
+ import { confirmDestructiveCommand, consumeDestructiveConfirmation, isDestructiveConfirmGateId, peekPendingDestructiveCommand, requestDestructiveConfirmation, resetDestructiveConfirmation, } from "../safety/destructive-confirmation.js";
26
26
  import { logWarning as safetyLogWarning, setStderrLoggingEnabled } from "../workflow-logger.js";
27
27
  import { isUnitCloseoutTool, runInteractiveUnitCloseout } from "../unit-closeout.js";
28
28
  import { installNotifyInterceptor } from "./notify-interceptor.js";
29
29
  import { initNotificationStore } from "../notification-store.js";
30
30
  import { initNotificationWidget } from "../notification-widget.js";
31
31
  import { notifyPreferenceDiagnostics } from "../preferences-diagnostics.js";
32
+ import { resolveEffectivePlanningToolsPolicy } from "../planning-subagent-policy.js";
32
33
  import { resolveWorktreeProjectRoot } from "../worktree-root.js";
33
34
  import { extractSubagentAgentClasses } from "./subagent-input.js";
34
35
  import { approvalGateIdForUnit, evaluateAskUserQuestionsRound, formatUnansweredConsentQuestionMessage, isExplicitApprovalResponse, messageHasPendingAskUserQuestionsTool, shouldPauseForQuestion, } from "../consent-question.js";
@@ -136,6 +137,7 @@ function suppressWelcomeHeader(ctx) {
136
137
  * are bounded — cleared on activation, session boundaries, and verification.
137
138
  */
138
139
  const deferredApprovalGates = new Map();
140
+ const deferredDestructiveConfirmationPauses = new Set();
139
141
  export const MINIMAL_GSD_TOOL_NAMES = [
140
142
  "gsd_exec",
141
143
  "gsd_exec_search",
@@ -456,6 +458,21 @@ function clearDeferredApprovalGate(basePath) {
456
458
  deferredApprovalGates.delete(basePath);
457
459
  }
458
460
  }
461
+ function deferDestructiveConfirmationPause(basePath) {
462
+ deferredDestructiveConfirmationPauses.add(basePath);
463
+ }
464
+ function clearDeferredDestructiveConfirmationPause(basePath) {
465
+ if (!basePath) {
466
+ deferredDestructiveConfirmationPauses.clear();
467
+ }
468
+ else {
469
+ deferredDestructiveConfirmationPauses.delete(basePath);
470
+ }
471
+ }
472
+ function isDestructiveConfirmationBlocking(basePath) {
473
+ return deferredDestructiveConfirmationPauses.has(basePath)
474
+ && Boolean(peekPendingDestructiveCommand(basePath));
475
+ }
459
476
  function deferApprovalGate(gateId, basePath) {
460
477
  // Verified-on-disk wins (same adapter policy as activation/re-arm): if the
461
478
  // workflow MCP child already verified this gate, deferring would block
@@ -798,6 +815,7 @@ export function registerHooks(pi, ecosystemHandlers) {
798
815
  await applyToolCallLoopGuardConfig(basePath);
799
816
  approvalQuestionAbortInFlight = false;
800
817
  clearDeferredApprovalGate();
818
+ clearDeferredDestructiveConfirmationPause();
801
819
  await resetAskUserQuestionsTurnCache();
802
820
  await syncServiceTierStatus(ctx);
803
821
  await applyDisabledModelProviderPolicy(ctx);
@@ -883,6 +901,7 @@ export function registerHooks(pi, ecosystemHandlers) {
883
901
  resetToolCallLoopGuard();
884
902
  await applyToolCallLoopGuardConfig(basePath);
885
903
  clearDeferredApprovalGate();
904
+ clearDeferredDestructiveConfirmationPause();
886
905
  await resetAskUserQuestionsTurnCache();
887
906
  clearDiscussionFlowState(basePath);
888
907
  // /clear or /new destroys the conversation holding a discuss interview, so
@@ -933,6 +952,7 @@ export function registerHooks(pi, ecosystemHandlers) {
933
952
  }
934
953
  }
935
954
  clearDeferredApprovalGate(beforeAgentBasePath);
955
+ clearDeferredDestructiveConfirmationPause(beforeAgentBasePath);
936
956
  // session_start can fire before the active provider has settled. By
937
957
  // before_agent_start, Claude Code CLI sessions should get the same
938
958
  // project MCP config that /gsd mcp init would write.
@@ -1010,7 +1030,10 @@ export function registerHooks(pi, ecosystemHandlers) {
1010
1030
  }
1011
1031
  finally {
1012
1032
  activateDeferredApprovalGate(agentEndBasePath);
1033
+ const destructiveConfirmationBlocking = isDestructiveConfirmationBlocking(agentEndBasePath);
1034
+ clearDeferredDestructiveConfirmationPause(agentEndBasePath);
1013
1035
  await maybePauseAutoForApprovalGate(ctx, pi, isApprovalGateBlocking(agentEndBasePath), "Depth confirmation is waiting for your answer — pausing auto-mode.");
1036
+ await maybePauseAutoForApprovalGate(ctx, pi, destructiveConfirmationBlocking, "Destructive-command confirmation is waiting for your answer — pausing auto-mode.");
1014
1037
  }
1015
1038
  });
1016
1039
  pi.on("message_end", async (event) => {
@@ -1276,6 +1299,7 @@ export function registerHooks(pi, ecosystemHandlers) {
1276
1299
  const activeUnitType = dash.currentUnit?.type ?? guidedUnit?.unitType;
1277
1300
  if (activeUnitType) {
1278
1301
  const manifest = resolveManifest(activeUnitType);
1302
+ const planningBasePath = dash.basePath || guidedUnit?.basePath || discussionBasePath;
1279
1303
  let planningInput = "";
1280
1304
  let agentClasses;
1281
1305
  if (isToolCallEventType("write", event)) {
@@ -1291,7 +1315,7 @@ export function registerHooks(pi, ecosystemHandlers) {
1291
1315
  // Subagent inputs use { agent }, { tasks: [{ agent }] }, or { chain: [{ agent }] }.
1292
1316
  agentClasses = extractSubagentAgentClasses(event.input);
1293
1317
  }
1294
- const planningGuard = shouldBlockPlanningUnit(event.toolName, planningInput, dash.basePath || guidedUnit?.basePath || discussionBasePath, activeUnitType, manifest?.tools, agentClasses, event.input, dash.currentUnit?.id);
1318
+ const planningGuard = shouldBlockPlanningUnit(event.toolName, planningInput, planningBasePath, activeUnitType, resolveEffectivePlanningToolsPolicy(activeUnitType, manifest?.tools, planningBasePath), agentClasses, event.input, dash.currentUnit?.id);
1295
1319
  if (planningGuard.block)
1296
1320
  return planningGuard;
1297
1321
  }
@@ -1373,6 +1397,7 @@ export function registerHooks(pi, ecosystemHandlers) {
1373
1397
  // Record the command as pending so an affirmative answer to a
1374
1398
  // destructive_confirm gate (handled in tool_result) can confirm it.
1375
1399
  requestDestructiveConfirmation(command, guardBasePath);
1400
+ deferDestructiveConfirmationPause(guardBasePath);
1376
1401
  const reason = [
1377
1402
  "HARD BLOCK: destructive Bash command requires explicit human confirmation.",
1378
1403
  `Detected: ${classification.labels.join(", ")}`,
@@ -1383,9 +1408,6 @@ export function registerHooks(pi, ecosystemHandlers) {
1383
1408
  safetyLogWarning("safety", `destructive command: ${classification.labels.join(", ")}`, {
1384
1409
  command: String(command).slice(0, 200),
1385
1410
  });
1386
- if (ctx) {
1387
- await maybePauseAutoForApprovalGate(ctx, pi, isAutoActive(), "Destructive-command confirmation is waiting for your answer — pausing auto-mode.");
1388
- }
1389
1411
  return { block: true, reason };
1390
1412
  }
1391
1413
  }
@@ -1541,6 +1563,10 @@ export function registerHooks(pi, ecosystemHandlers) {
1541
1563
  if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
1542
1564
  confirmDestructiveCommand(basePath);
1543
1565
  }
1566
+ else {
1567
+ resetDestructiveConfirmation(basePath);
1568
+ }
1569
+ clearDeferredDestructiveConfirmationPause(basePath);
1544
1570
  break;
1545
1571
  }
1546
1572
  }
@@ -18,6 +18,13 @@ import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile,
18
18
  import { toPosixPath } from "../../shared/mod.js";
19
19
  import { autoEnableCmuxPreferences } from "../commands-cmux.js";
20
20
  import { gsdHome } from "../gsd-home.js";
21
+ import { GSD_CONTEXT_MESSAGE_SENTINEL } from "../constants.js";
22
+ // Single source of truth lives in ../constants.js; re-exported here because
23
+ // buildContextMessage() stamps this marker on every context injection and the
24
+ // provider payload policy (filterSupersededContextInjections in
25
+ // context-masker.ts) matches it to dedupe. Adding a new context customType?
26
+ // Ensure buildContextMessage still prepends this sentinel.
27
+ export { GSD_CONTEXT_MESSAGE_SENTINEL };
21
28
  const DEFAULT_CONTEXT_MESSAGE_MAX_CHARS = 4_000;
22
29
  const DEFAULT_KNOWLEDGE_MAX_CHARS = 12_000;
23
30
  const DEFAULT_CODEBASE_MAX_CHARS = 8_000;
@@ -385,17 +392,17 @@ export function buildContextMessage(opts) {
385
392
  const contextCharLimit = getContextMessageCharLimit();
386
393
  const memoryContent = markMemoryContextSupplied(opts.memoryBlock.trim());
387
394
  if (opts.injection) {
388
- const content = limitContextMessageContent(memoryContent ? `${memoryContent}\n\n${opts.injection}` : opts.injection, contextCharLimit);
395
+ const content = limitContextMessageContent(`${GSD_CONTEXT_MESSAGE_SENTINEL}\n${memoryContent ? `${memoryContent}\n\n${opts.injection}` : opts.injection}`, contextCharLimit);
389
396
  return { customType: "gsd-guided-context", content, display: false };
390
397
  }
391
398
  if (opts.forensicsInjection) {
392
- const content = limitContextMessageContent(memoryContent ? `${memoryContent}\n\n${opts.forensicsInjection}` : opts.forensicsInjection, contextCharLimit);
399
+ const content = limitContextMessageContent(`${GSD_CONTEXT_MESSAGE_SENTINEL}\n${memoryContent ? `${memoryContent}\n\n${opts.forensicsInjection}` : opts.forensicsInjection}`, contextCharLimit);
393
400
  return { customType: "gsd-forensics", content, display: false };
394
401
  }
395
402
  if (memoryContent) {
396
403
  return {
397
404
  customType: "gsd-memory",
398
- content: limitContextMessageContent(memoryContent, contextCharLimit),
405
+ content: limitContextMessageContent(`${GSD_CONTEXT_MESSAGE_SENTINEL}\n${memoryContent}`, contextCharLimit),
399
406
  display: false,
400
407
  };
401
408
  }
@@ -7,6 +7,7 @@ import { canonicalToolName } from "../engine-hook-contract.js";
7
7
  import { loadJsonFileOrNull } from "../json-persistence.js";
8
8
  import { getIsolationMode } from "../preferences.js";
9
9
  import { compileSubagentPermissionContract } from "../unit-context-manifest.js";
10
+ import { allowedPlanningDispatchAgentsList, isReadOnlyPlanningDispatchAgent, } from "../planning-subagent-registry.js";
10
11
  import { logWarning } from "../workflow-logger.js";
11
12
  import { acquireSyncLock, releaseSyncLock } from "../sync-lock.js";
12
13
  import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
@@ -981,29 +982,8 @@ export function shouldBlockQueueExecutionInSnapshot(snapshot, toolName, input, q
981
982
  // guards.
982
983
  const PLANNING_WRITE_TOOLS = new Set(["write", "edit", "multi_edit", "notebook_edit"]);
983
984
  const PLANNING_SUBAGENT_TOOLS = new Set(["subagent", "task"]);
984
- /**
985
- * Canonical registry for agents that planning-dispatch may consider. Unit
986
- * manifests still declare per-unit subsets via ToolsPolicy.allowedSubagents.
987
- */
988
- const PLANNING_DISPATCH_AGENT_REGISTRY = {
989
- mnemo: { readOnlySpecialist: true },
990
- scout: { readOnlySpecialist: true },
991
- planner: { readOnlySpecialist: true },
992
- reviewer: { readOnlySpecialist: true },
993
- security: { readOnlySpecialist: true },
994
- tester: { readOnlySpecialist: true },
995
- };
996
- export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set(Object.entries(PLANNING_DISPATCH_AGENT_REGISTRY)
997
- .filter(([, metadata]) => metadata.readOnlySpecialist)
998
- .map(([agentId]) => agentId));
985
+ export { ALLOWED_PLANNING_DISPATCH_AGENTS } from "../planning-subagent-registry.js";
999
986
  let warnedMissingControlledDispatchAgentClasses = false;
1000
- function isReadOnlySpecialist(agentId) {
1001
- const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId];
1002
- return metadata?.readOnlySpecialist === true;
1003
- }
1004
- function allowedPlanningDispatchAgentsList() {
1005
- return [...ALLOWED_PLANNING_DISPATCH_AGENTS].join(", ");
1006
- }
1007
987
  function allowsControlledSubagentDispatch(policy) {
1008
988
  return ((policy.mode === "planning-dispatch" || policy.mode === "verification" || policy.mode === "workflow-only") &&
1009
989
  Array.isArray(policy.allowedSubagents));
@@ -1151,13 +1131,13 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
1151
1131
  if (requested.length === 0) {
1152
1132
  return { block: false };
1153
1133
  }
1154
- const globallyDisallowed = requested.find(a => !isReadOnlySpecialist(a));
1134
+ const globallyDisallowed = requested.find(a => !isReadOnlyPlanningDispatchAgent(a));
1155
1135
  if (globallyDisallowed) {
1156
1136
  return planningBlock(unitType, policy.mode, `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`);
1157
1137
  }
1158
1138
  const disallowedByPolicy = requested.find(a => !allowed.has(a));
1159
1139
  if (disallowedByPolicy) {
1160
- return planningBlock(unitType, policy.mode, `subagent dispatch of "${disallowedByPolicy}" not permitted by ToolsPolicy.allowedSubagents; permitted agents for this unit: ${allowedSubagents.join(", ")}`);
1140
+ return planningBlock(unitType, policy.mode, `subagent dispatch of "${disallowedByPolicy}" not permitted by unit allowlist (ToolsPolicy.allowedSubagents/planning_subagents); permitted agents for this unit: ${allowedSubagents.join(", ")}`);
1161
1141
  }
1162
1142
  return { block: false };
1163
1143
  }