@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
@@ -17,6 +17,7 @@ import { getGateIdsForTurn } from "./gate-registry.js";
17
17
  import {
18
18
  KNOWN_PREFERENCE_KEYS,
19
19
  KNOWN_UNIT_LABELS,
20
+ CONFIGURABLE_PLANNING_SUBAGENT_UNITS,
20
21
  GSD_MODEL_PHASE_KEYS,
21
22
 
22
23
  SKILL_ACTIONS,
@@ -26,6 +27,10 @@ import {
26
27
  type GSDSkillRule,
27
28
  type GSDThinkingLevel,
28
29
  } from "./preferences-types.js";
30
+ import {
31
+ ALLOWED_PLANNING_DISPATCH_AGENTS,
32
+ allowedPlanningDispatchAgentsList,
33
+ } from "./planning-subagent-registry.js";
29
34
 
30
35
  const VALID_TOKEN_PROFILES = new Set<TokenProfile>(["budget", "balanced", "quality", "burn-max"]);
31
36
  const VALID_THINKING_LEVELS = new Set<GSDThinkingLevel>([
@@ -747,6 +752,63 @@ export function validatePreferences(preferences: GSDPreferences): {
747
752
  }
748
753
  }
749
754
 
755
+ // ─── Planning Subagents ─────────────────────────────────────────────────
756
+ if (preferences.planning_subagents !== undefined) {
757
+ if (
758
+ !preferences.planning_subagents ||
759
+ typeof preferences.planning_subagents !== "object" ||
760
+ Array.isArray(preferences.planning_subagents)
761
+ ) {
762
+ errors.push("planning_subagents must be an object keyed by planning unit type");
763
+ } else {
764
+ const validUnits = new Set<string>(CONFIGURABLE_PLANNING_SUBAGENT_UNITS);
765
+ const validPlanningSubagents: NonNullable<GSDPreferences["planning_subagents"]> = {};
766
+
767
+ for (const [unitType, rawConfig] of Object.entries(preferences.planning_subagents as Record<string, unknown>)) {
768
+ if (!validUnits.has(unitType)) {
769
+ errors.push(
770
+ `planning_subagents unknown or unsupported unit "${unitType}" — supported units: ` +
771
+ `${CONFIGURABLE_PLANNING_SUBAGENT_UNITS.join(", ")}`,
772
+ );
773
+ continue;
774
+ }
775
+ if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
776
+ errors.push(`planning_subagents.${unitType} must be an object with an allowed array`);
777
+ continue;
778
+ }
779
+
780
+ const allowed = normalizeStringArray((rawConfig as Record<string, unknown>).allowed);
781
+ if (allowed.length === 0) {
782
+ errors.push(`planning_subagents.${unitType}.allowed must include at least one read-only planning specialist`);
783
+ continue;
784
+ }
785
+
786
+ const sanitizedAllowed: string[] = [];
787
+ for (const agentId of allowed) {
788
+ if (!ALLOWED_PLANNING_DISPATCH_AGENTS.has(agentId)) {
789
+ errors.push(
790
+ `planning_subagents.${unitType}.allowed includes "${agentId}", which is not classified as a ` +
791
+ `read-only planning specialist. Allowed agents: ${allowedPlanningDispatchAgentsList()}`,
792
+ );
793
+ continue;
794
+ }
795
+ sanitizedAllowed.push(agentId);
796
+ }
797
+
798
+ const dedupedAllowed = Array.from(new Set(sanitizedAllowed));
799
+ if (dedupedAllowed.length > 0) {
800
+ validPlanningSubagents[unitType as keyof NonNullable<GSDPreferences["planning_subagents"]>] = {
801
+ allowed: dedupedAllowed,
802
+ };
803
+ }
804
+ }
805
+
806
+ if (Object.keys(validPlanningSubagents).length > 0) {
807
+ validated.planning_subagents = validPlanningSubagents;
808
+ }
809
+ }
810
+ }
811
+
750
812
  // ─── Dynamic Routing ─────────────────────────────────────────────────
751
813
  if (preferences.dynamic_routing !== undefined) {
752
814
  if (typeof preferences.dynamic_routing === "object" && preferences.dynamic_routing !== null) {
@@ -817,6 +817,7 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
817
817
  : undefined,
818
818
  post_unit_hooks: mergePostUnitHooks(base.post_unit_hooks, override.post_unit_hooks),
819
819
  pre_dispatch_hooks: mergePreDispatchHooks(base.pre_dispatch_hooks, override.pre_dispatch_hooks),
820
+ planning_subagents: mergePlanningSubagents(base.planning_subagents, override.planning_subagents),
820
821
  dynamic_routing: (base.dynamic_routing || override.dynamic_routing)
821
822
  ? { ...(base.dynamic_routing ?? {}), ...(override.dynamic_routing ?? {}) } as DynamicRoutingConfig
822
823
  : undefined,
@@ -943,6 +944,30 @@ function mergePreDispatchHooks(
943
944
  return merged.length > 0 ? merged : undefined;
944
945
  }
945
946
 
947
+ function mergePlanningSubagents(
948
+ base?: GSDPreferences["planning_subagents"],
949
+ override?: GSDPreferences["planning_subagents"],
950
+ ): GSDPreferences["planning_subagents"] | undefined {
951
+ if (!base && !override) return undefined;
952
+ const merged: NonNullable<GSDPreferences["planning_subagents"]> = {};
953
+ const unitTypes = new Set([
954
+ ...Object.keys(base ?? {}),
955
+ ...Object.keys(override ?? {}),
956
+ ]);
957
+
958
+ for (const unitType of unitTypes) {
959
+ const allowed = mergeStringLists(
960
+ base?.[unitType as keyof NonNullable<GSDPreferences["planning_subagents"]>]?.allowed,
961
+ override?.[unitType as keyof NonNullable<GSDPreferences["planning_subagents"]>]?.allowed,
962
+ );
963
+ if (allowed?.length) {
964
+ merged[unitType as keyof NonNullable<GSDPreferences["planning_subagents"]>] = { allowed };
965
+ }
966
+ }
967
+
968
+ return Object.keys(merged).length > 0 ? merged : undefined;
969
+ }
970
+
946
971
  // ─── System Prompt Rendering ──────────────────────────────────────────────────
947
972
 
948
973
  export function renderPreferencesForSystemPrompt(
@@ -2,10 +2,12 @@
2
2
  * Provider Payload Policy - ordered shaping of provider request payloads.
3
3
  *
4
4
  * The order is intentional:
5
- * 1. observation budgeting masks old tool results in auto-mode,
6
- * 2. display truncation caps tool-result text for every mode,
7
- * 3. the protected Source Context Block is appended after truncation,
8
- * 4. supported models receive the configured service tier.
5
+ * 1. superseded GSD context injections (memory/guided/forensics) are removed,
6
+ * keeping only the latest, for every mode,
7
+ * 2. observation budgeting masks old tool results in auto-mode,
8
+ * 3. display truncation caps tool-result text for every mode,
9
+ * 4. the protected Source Context Block is appended after truncation,
10
+ * 5. supported models receive the configured service tier.
9
11
  */
10
12
 
11
13
  import type { ContextManagementConfig } from "./preferences-types.js";
@@ -14,6 +16,8 @@ import type { ServiceTierSetting } from "./service-tier.js";
14
16
  import {
15
17
  createObservationMask,
16
18
  createResponsesInputObservationMask,
19
+ filterSupersededContextInjections,
20
+ filterSupersededResponsesContextInjections,
17
21
  truncateContextResultMessages,
18
22
  truncateResponsesInputResultItems,
19
23
  } from "./context-masker.js";
@@ -80,10 +84,20 @@ function applyContextManagement(
80
84
  deps: ProviderPayloadPolicyDeps,
81
85
  ): void {
82
86
  const config = deps.loadContextManagementConfig();
87
+ applyContextInjectionFilter(payload);
83
88
  applyObservationBudget(payload, config, deps.isAutoActive());
84
89
  applyDisplayTruncation(payload, config);
85
90
  }
86
91
 
92
+ function applyContextInjectionFilter(payload: Record<string, unknown>): void {
93
+ if (Array.isArray(payload.messages)) {
94
+ payload.messages = filterSupersededContextInjections(payload.messages as MessagePayload);
95
+ }
96
+ if (Array.isArray(payload.input)) {
97
+ payload.input = filterSupersededResponsesContextInjections(payload.input as ResponsesInputPayload);
98
+ }
99
+ }
100
+
87
101
  function applyObservationBudget(
88
102
  payload: Record<string, unknown>,
89
103
  config: ContextManagementConfig | undefined,
@@ -14,7 +14,7 @@ interface DestructivePattern {
14
14
  }
15
15
 
16
16
  const DESTRUCTIVE_PATTERNS: readonly DestructivePattern[] = [
17
- { pattern: /\brm\s+(-[^\s]*[rfRF][^\s]*\s+|.*\s+-[^\s]*[rfRF])/, label: "recursive delete" },
17
+ { pattern: /\brm\s+((?:-(?!-)[^\s]*[rR][^\s]*|--recursive)\s+|.*\s+(?:-(?!-)[^\s]*[rR][^\s]*|--recursive)(?:\s|$))/, label: "recursive delete" },
18
18
  { pattern: /\bgit\s+push\s+.*--force/, label: "force push" },
19
19
  { pattern: /\bgit\s+push\s+-f\b/, label: "force push" },
20
20
  { pattern: /\bgit\s+reset\s+--hard/, label: "hard reset" },
@@ -131,10 +131,23 @@ function resolvePreferredSkillNames(
131
131
  * to prevent prompt injection via crafted directory names. */
132
132
  const SAFE_SKILL_NAME = /^[a-z0-9][a-z0-9-]*$/;
133
133
 
134
- function formatSkillActivationBlock(skillNames: string[]): string {
134
+ function escapeXml(str: string): string {
135
+ return str
136
+ .replace(/&/g, "&amp;")
137
+ .replace(/</g, "&lt;")
138
+ .replace(/>/g, "&gt;")
139
+ .replace(/"/g, "&quot;")
140
+ .replace(/'/g, "&apos;");
141
+ }
142
+
143
+ function formatSkillActivationBlock(skillNames: string[], skillPaths: Map<string, string>): string {
135
144
  const safe = skillNames.filter(name => SAFE_SKILL_NAME.test(name));
136
145
  if (safe.length === 0) return "";
137
- const reads = safe.map(name => `Read the installed '${name}' skill file from <available_skills>`).join(". ");
146
+ const reads = safe.map(name => {
147
+ const path = skillPaths.get(name);
148
+ if (path) return `Read the skill file at \`${escapeXml(path)}\``;
149
+ return `Find '${name}' in <available_skills>, copy its <location> value, and pass that exact path to read`;
150
+ }).join(". ");
138
151
  return `<skill_activation>${reads}.</skill_activation>`;
139
152
  }
140
153
 
@@ -203,6 +216,7 @@ export function buildSkillActivationBlock(params: {
203
216
  const visibleSkills = loaded;
204
217
  const manifestScopedSkills = filterSkillsByManifest(visibleSkills, params.unitType);
205
218
  const installedNames = new Set(visibleSkills.map(skill => normalizeSkillReference(skill.name)));
219
+ const installedSkillPaths = new Map(visibleSkills.map(skill => [normalizeSkillReference(skill.name), skill.filePath]));
206
220
  warnIfManifestHasMissingSkills(params.unitType, installedNames);
207
221
  const avoided = new Set(resolvePreferenceSkillNames(prefs?.avoid_skills ?? [], params.base));
208
222
  const matched = new Set<string>();
@@ -252,7 +266,7 @@ export function buildSkillActivationBlock(params: {
252
266
  const ordered = [...matched]
253
267
  .filter(name => installedNames.has(name) && !avoided.has(name))
254
268
  .sort();
255
- const activationBlock = formatSkillActivationBlock(ordered);
269
+ const activationBlock = formatSkillActivationBlock(ordered, installedSkillPaths);
256
270
 
257
271
  // Omit recommendations when the system catalog is manifest-scoped for this
258
272
  // unit — skill names are already listed in <available_skills>.
@@ -92,6 +92,11 @@ remote_questions:
92
92
  uat_dispatch:
93
93
  post_unit_hooks: []
94
94
  pre_dispatch_hooks: []
95
+ # planning_subagents:
96
+ # plan-milestone:
97
+ # allowed: [scout, planner, security]
98
+ # plan-slice:
99
+ # allowed: [scout, planner, reviewer, security]
95
100
  # language:
96
101
  # experimental:
97
102
  # rtk: false
@@ -442,11 +442,11 @@ test("bare milestone id still falls back to a lone team-suffix projection (#1195
442
442
  // ── #1208: flat-phase execute-task summaries resolve at the phase root ────────
443
443
  //
444
444
  // In flat-phase layout task summaries are written beside the plan files
445
- // (phases/<phase>/T##-SUMMARY.md), NOT under a tasks/ subdir. A tasks/ dir may
446
- // still exist to hold auxiliary task-scoped gate artifacts (e.g. T01-VERIFY.json).
445
+ // (phases/<phase>/S##-T##-SUMMARY.md), NOT under a tasks/ subdir. A tasks/ dir
446
+ // may still exist to hold auxiliary task-scoped gate artifacts (e.g. T01-VERIFY.json).
447
447
  // Before the fix, the mere existence of that tasks/ dir redirected summary
448
- // verification into tasks/T##-SUMMARY.md — which never exists — trapping auto-mode
449
- // in a false verification retry that eventually paused after a successful task.
448
+ // verification into tasks/S##-T##-SUMMARY.md — which never exists — trapping
449
+ // auto-mode in a false verification retry after a successful task.
450
450
 
451
451
  test("flat-phase execute-task summary resolves at phase root when no tasks/ dir exists (#1208)", () => {
452
452
  const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-flat-task-summary-")));
@@ -461,7 +461,7 @@ test("flat-phase execute-task summary resolves at phase root when no tasks/ dir
461
461
 
462
462
  assert.equal(
463
463
  resolveExpectedArtifactPath("execute-task", "M049/S03/T02", root),
464
- join(phaseDir, "T02-SUMMARY.md"),
464
+ join(phaseDir, "S03-T02-SUMMARY.md"),
465
465
  );
466
466
  } finally {
467
467
  _clearGsdRootCache();
@@ -489,12 +489,12 @@ test("flat-phase execute-task summary resolves at phase root even when tasks/ di
489
489
  const resolved = resolveExpectedArtifactPath("execute-task", "M049/S03/T02", root);
490
490
  assert.equal(
491
491
  resolved,
492
- join(phaseDir, "T02-SUMMARY.md"),
492
+ join(phaseDir, "S03-T02-SUMMARY.md"),
493
493
  "flat-phase task summary must resolve at the phase root, not tasks/",
494
494
  );
495
495
  assert.notEqual(
496
496
  resolved,
497
- join(tasksDir, "T02-SUMMARY.md"),
497
+ join(tasksDir, "S03-T02-SUMMARY.md"),
498
498
  "a tasks/ dir with gate artifacts must NOT redirect summary resolution into tasks/",
499
499
  );
500
500
  } finally {
@@ -1218,6 +1218,10 @@ test("hasImplementationArtifacts finds integration implementation-only commits w
1218
1218
 
1219
1219
  execFileSync("git", ["checkout", "-b", "milestone/M001"], { cwd: base, stdio: "ignore" });
1220
1220
  writeIntegrationBranch(base, "M001", "main");
1221
+ // ADR-045: writeIntegrationBranch no longer creates milestones/<MID>/ as a
1222
+ // side effect (META is now flat at .gsd/<MID>-META.json), so scaffold the
1223
+ // legacy summary dir explicitly.
1224
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
1221
1225
  writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
1222
1226
  execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
1223
1227
  execFileSync("git", ["commit", "-m", "chore: auto-commit after complete-milestone\n\nGSD-Unit: M001"], { cwd: base, stdio: "ignore" });
@@ -2063,6 +2067,41 @@ test("#1088: writeReactiveExecuteBlocker preserves deferred batch task statuses"
2063
2067
  }
2064
2068
  });
2065
2069
 
2070
+ test("#1343: writeReactiveExecuteBlocker uses slice-qualified summaries in flat-phase (no cross-slice collision)", () => {
2071
+ const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
2072
+ try {
2073
+ // Flat-phase layout: slices S01 and S02 share the phase dir and reuse task
2074
+ // id T03. Only the sibling slice S01 produced a summary.
2075
+ const phaseDir = join(base, ".gsd", "phases", "01-test");
2076
+ mkdirSync(phaseDir, { recursive: true });
2077
+ writeFileSync(join(phaseDir, "01-01-PLAN.md"), "# S01: First\n", "utf-8");
2078
+ writeFileSync(join(phaseDir, "01-02-PLAN.md"), "# S02: Second\n", "utf-8");
2079
+ writeFileSync(join(phaseDir, "S01-T03-SUMMARY.md"), "# S01 T03 Summary\n", "utf-8");
2080
+
2081
+ openDatabase(join(base, ".gsd", "gsd.db"));
2082
+ insertMilestone({ id: "M001", title: "Milestone", status: "active" });
2083
+ insertSlice({ id: "S01", milestoneId: "M001", title: "First", status: "pending" });
2084
+ insertSlice({ id: "S02", milestoneId: "M001", title: "Second", status: "pending" });
2085
+ insertTask({ id: "T03", milestoneId: "M001", sliceId: "S02", title: "Shared id", status: "pending" });
2086
+
2087
+ const recovery = writeReactiveExecuteBlocker(
2088
+ "M001/S02/reactive+T03",
2089
+ base,
2090
+ "verification retries exhausted",
2091
+ );
2092
+
2093
+ assert.ok(recovery, "recovery should run with DB available");
2094
+ // S02 never wrote S02-T03-SUMMARY.md; the sibling S01-T03-SUMMARY.md must
2095
+ // NOT make S02/T03 look complete. It has to be skipped.
2096
+ assert.deepEqual(recovery!.completedTaskIds, []);
2097
+ assert.deepEqual(recovery!.skippedTaskIds, ["T03"]);
2098
+ assert.equal(getTask("M001", "S02", "T03")?.status, "skipped");
2099
+ } finally {
2100
+ closeDatabase();
2101
+ cleanup(base);
2102
+ }
2103
+ });
2104
+
2066
2105
  test("#4414: verifyExpectedArtifact parallel-research succeeds when all research-ready slices have RESEARCH", () => {
2067
2106
  const base = makeTmpBase();
2068
2107
  try {
@@ -143,3 +143,58 @@ test("successful shell result clears source context before provider injection",
143
143
  assert.equal(payload.messages.length, 1);
144
144
  assert.doesNotMatch(payload.messages[0].content[0].text, /Source Context Block/);
145
145
  });
146
+
147
+ test("before_provider_request keeps only the latest GSD context injection across a 5-turn session", async (t) => {
148
+ const dir = mkdtempSync(join(tmpdir(), "gsd-before-provider-injections-"));
149
+ const gsdHome = join(dir, "home");
150
+ const project = join(dir, "project");
151
+ const previousCwd = process.cwd();
152
+ const previousGsdHome = process.env.GSD_HOME;
153
+
154
+ mkdirSync(join(project, ".gsd"), { recursive: true });
155
+ mkdirSync(gsdHome, { recursive: true });
156
+ process.env.GSD_HOME = gsdHome;
157
+ process.chdir(project);
158
+ _setAutoActiveForTest(false);
159
+
160
+ t.after(() => {
161
+ _setAutoActiveForTest(false);
162
+ process.chdir(previousCwd);
163
+ if (previousGsdHome === undefined) delete process.env.GSD_HOME;
164
+ else process.env.GSD_HOME = previousGsdHome;
165
+ rmSync(dir, { recursive: true, force: true });
166
+ });
167
+
168
+ const beforeProviderRequest = requireHook(createHookHandlers(), "before_provider_request");
169
+
170
+ const sentinel = "[GSD Context Injection]";
171
+ const contextInjection = (turn: number) => ({
172
+ role: "user",
173
+ content: [{ type: "text", text: `${sentinel}\n[MEMORY] turn=${turn}` }],
174
+ });
175
+
176
+ const payload = {
177
+ messages: [
178
+ { role: "user", content: [{ type: "text", text: "turn 1" }] },
179
+ contextInjection(1),
180
+ { role: "assistant", content: [{ type: "text", text: "r1" }] },
181
+ { role: "user", content: [{ type: "text", text: "turn 2" }] },
182
+ contextInjection(2),
183
+ { role: "assistant", content: [{ type: "text", text: "r2" }] },
184
+ { role: "user", content: [{ type: "text", text: "turn 3" }] },
185
+ contextInjection(3),
186
+ { role: "assistant", content: [{ type: "text", text: "r3" }] },
187
+ { role: "user", content: [{ type: "text", text: "turn 4" }] },
188
+ contextInjection(4),
189
+ { role: "assistant", content: [{ type: "text", text: "r4" }] },
190
+ { role: "user", content: [{ type: "text", text: "turn 5" }] },
191
+ contextInjection(5),
192
+ ],
193
+ };
194
+
195
+ await beforeProviderRequest({ payload });
196
+
197
+ const survivors = (payload.messages as any[]).filter((m) => (m.content?.[0]?.text ?? "").startsWith(sentinel));
198
+ assert.equal(survivors.length, 1);
199
+ assert.match(survivors[0].content[0].text, /turn=5/);
200
+ });
@@ -23,6 +23,7 @@ import {
23
23
  SCHEMA_VERSION,
24
24
  } from '../gsd-db.ts';
25
25
  import { handleCompleteTask } from '../tools/complete-task.ts';
26
+ import { resolveTaskFile } from '../paths.ts';
26
27
 
27
28
  const { assertEq, assertTrue, assertMatch, report } = createTestContext();
28
29
 
@@ -381,6 +382,80 @@ console.log('\n=== complete-task: handler happy path ===');
381
382
  cleanup(dbPath);
382
383
  }
383
384
 
385
+ // ═══════════════════════════════════════════════════════════════════════════
386
+ // complete-task: Flat-phase duplicate task IDs are slice-qualified
387
+ // ═══════════════════════════════════════════════════════════════════════════
388
+
389
+ console.log('\n=== complete-task: flat-phase duplicate task IDs are slice-qualified ===');
390
+ {
391
+ const dbPath = tempDbPath();
392
+ openDatabase(dbPath);
393
+
394
+ const basePath = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-flat-task-summary-'));
395
+ const phaseDir = path.join(basePath, '.gsd', 'phases', '01-test');
396
+ fs.mkdirSync(phaseDir, { recursive: true });
397
+ fs.writeFileSync(path.join(phaseDir, '01-01-PLAN.md'), `# S01: First Slice
398
+
399
+ ## Tasks
400
+
401
+ - [ ] **T03: Shared task id** \`est:30m\`
402
+ - Do: Complete first slice task
403
+ - Verify: Run first check
404
+ `);
405
+ fs.writeFileSync(path.join(phaseDir, '01-02-PLAN.md'), `# S02: Second Slice
406
+
407
+ ## Tasks
408
+
409
+ - [ ] **T03: Shared task id** \`est:30m\`
410
+ - Do: Complete second slice task
411
+ - Verify: Run second check
412
+ `);
413
+
414
+ insertMilestone({ id: 'M001', title: 'Test Milestone' });
415
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First Slice', sequence: 1 });
416
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second Slice', sequence: 2 });
417
+
418
+ const first = await handleCompleteTask({
419
+ ...makeValidParams(),
420
+ taskId: 'T03',
421
+ sliceId: 'S01',
422
+ oneLiner: 'Completed first slice T03',
423
+ }, basePath);
424
+ const second = await handleCompleteTask({
425
+ ...makeValidParams(),
426
+ taskId: 'T03',
427
+ sliceId: 'S02',
428
+ oneLiner: 'Completed second slice T03',
429
+ }, basePath);
430
+
431
+ assertTrue(!('error' in first), 'first duplicate task ID completion should succeed');
432
+ assertTrue(!('error' in second), 'second duplicate task ID completion should succeed');
433
+ if (!('error' in first) && !('error' in second)) {
434
+ const firstSummary = path.join(phaseDir, 'S01-T03-SUMMARY.md');
435
+ const secondSummary = path.join(phaseDir, 'S02-T03-SUMMARY.md');
436
+ const legacySummary = path.join(phaseDir, 'T03-SUMMARY.md');
437
+
438
+ assertEq(first.summaryPath, firstSummary, 'S01/T03 should write a slice-qualified summary path');
439
+ assertEq(second.summaryPath, secondSummary, 'S02/T03 should write a slice-qualified summary path');
440
+ assertTrue(fs.existsSync(firstSummary), 'S01/T03 summary should exist');
441
+ assertTrue(fs.existsSync(secondSummary), 'S02/T03 summary should exist');
442
+ assertTrue(!fs.existsSync(legacySummary), 'flat-phase completion should not create legacy T03-SUMMARY.md');
443
+
444
+ const firstContent = fs.readFileSync(firstSummary, 'utf-8');
445
+ const secondContent = fs.readFileSync(secondSummary, 'utf-8');
446
+ assertMatch(firstContent, /parent: S01/, 'S01/T03 summary should keep parent S01');
447
+ assertMatch(secondContent, /parent: S02/, 'S02/T03 summary should keep parent S02');
448
+ assertEq(resolveTaskFile(basePath, 'M001', 'S02', 'T03', 'SUMMARY'), secondSummary, 'resolver should prefer S02-specific summary');
449
+
450
+ const legacyFallback = path.join(phaseDir, 'T99-SUMMARY.md');
451
+ fs.writeFileSync(legacyFallback, '# Legacy task summary\n');
452
+ assertEq(resolveTaskFile(basePath, 'M001', 'S09', 'T99', 'SUMMARY'), legacyFallback, 'resolver should fall back to old flat summary names');
453
+ }
454
+
455
+ cleanupDir(basePath);
456
+ cleanup(dbPath);
457
+ }
458
+
384
459
  // ═══════════════════════════════════════════════════════════════════════════
385
460
  // complete-task: Projection failure rolls DB completion back
386
461
  // ═══════════════════════════════════════════════════════════════════════════
@@ -416,7 +491,7 @@ console.log('\n=== complete-task: projection failure rolls DB completion back ==
416
491
  ).all();
417
492
  assertEq(evRows.length, 0, 'verification evidence should be deleted when projection rollback runs');
418
493
 
419
- const summaryPath = path.join(path.dirname(planPath), 'T01-SUMMARY.md');
494
+ const summaryPath = path.join(path.dirname(planPath), 'S01-T01-SUMMARY.md');
420
495
  assertTrue(!fs.existsSync(summaryPath), 'SUMMARY.md should be removed so disk state stays pending');
421
496
 
422
497
  cleanupDir(basePath);