gsd-pi 2.79.0 → 2.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +94 -47
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/contracts.js +1 -0
  4. package/dist/resources/extensions/gsd/auto/orchestrator.js +146 -0
  5. package/dist/resources/extensions/gsd/auto/phases.js +61 -7
  6. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  7. package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +2 -0
  9. package/dist/resources/extensions/gsd/auto-prompts.js +52 -29
  10. package/dist/resources/extensions/gsd/auto-recovery.js +63 -55
  11. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
  12. package/dist/resources/extensions/gsd/auto-start.js +3 -2
  13. package/dist/resources/extensions/gsd/auto.js +159 -2
  14. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  15. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
  16. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +41 -45
  17. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +8 -8
  18. package/dist/resources/extensions/gsd/commands/context.js +1 -1
  19. package/dist/resources/extensions/gsd/gsd-db.js +34 -1
  20. package/dist/resources/extensions/gsd/guided-flow.js +40 -0
  21. package/dist/resources/extensions/gsd/paths.js +5 -1
  22. package/dist/resources/extensions/gsd/post-execution-checks.js +25 -6
  23. package/dist/resources/extensions/gsd/preferences-types.js +20 -2
  24. package/dist/resources/extensions/gsd/preferences-validation.js +3 -3
  25. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +82 -2
  26. package/dist/resources/extensions/gsd/unit-context-composer.js +32 -0
  27. package/dist/resources/extensions/gsd/unit-context-manifest.js +21 -0
  28. package/dist/resources/extensions/gsd/uok/audit.js +23 -9
  29. package/dist/resources/extensions/gsd/uok/contracts.js +69 -1
  30. package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +3 -0
  31. package/dist/resources/extensions/gsd/uok/loop-adapter.js +48 -33
  32. package/dist/resources/extensions/gsd/uok/timeline.js +125 -0
  33. package/dist/resources/extensions/shared/gsd-phase-state.js +45 -3
  34. package/dist/resources/extensions/shared/interview-ui.js +15 -4
  35. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  36. package/dist/web/standalone/.next/BUILD_ID +1 -1
  37. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  38. package/dist/web/standalone/.next/build-manifest.json +2 -2
  39. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.html +1 -1
  56. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  63. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  65. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  66. package/package.json +1 -1
  67. package/packages/daemon/package.json +2 -2
  68. package/packages/mcp-server/dist/workflow-tools.d.ts +1 -1
  69. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  70. package/packages/mcp-server/dist/workflow-tools.js +53 -0
  71. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  72. package/packages/mcp-server/package.json +2 -2
  73. package/packages/mcp-server/src/workflow-tools.test.ts +129 -2
  74. package/packages/mcp-server/src/workflow-tools.ts +81 -0
  75. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  76. package/packages/native/package.json +1 -1
  77. package/packages/pi-agent-core/package.json +1 -1
  78. package/packages/pi-ai/package.json +1 -1
  79. package/packages/pi-coding-agent/package.json +1 -1
  80. package/packages/pi-tui/package.json +1 -1
  81. package/packages/rpc-client/package.json +1 -1
  82. package/pkg/package.json +1 -1
  83. package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
  84. package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
  85. package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
  86. package/src/resources/extensions/gsd/auto/phases.ts +88 -9
  87. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  88. package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -2
  89. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -0
  90. package/src/resources/extensions/gsd/auto-prompts.ts +106 -28
  91. package/src/resources/extensions/gsd/auto-recovery.ts +59 -53
  92. package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
  93. package/src/resources/extensions/gsd/auto-start.ts +3 -2
  94. package/src/resources/extensions/gsd/auto.ts +167 -1
  95. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +14 -1
  96. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
  97. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +49 -46
  98. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-shouldblock-basepath.test.ts +97 -0
  99. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +8 -4
  100. package/src/resources/extensions/gsd/commands/context.ts +1 -1
  101. package/src/resources/extensions/gsd/gsd-db.ts +35 -1
  102. package/src/resources/extensions/gsd/guided-flow.ts +47 -0
  103. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  104. package/src/resources/extensions/gsd/paths.ts +6 -1
  105. package/src/resources/extensions/gsd/post-execution-checks.ts +31 -6
  106. package/src/resources/extensions/gsd/preferences-types.ts +23 -4
  107. package/src/resources/extensions/gsd/preferences-validation.ts +3 -3
  108. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +32 -0
  109. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
  110. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +108 -1
  111. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
  112. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
  113. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -2
  114. package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +203 -0
  115. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +148 -0
  116. package/src/resources/extensions/gsd/tests/current-directory-root-homedir-fallback.test.ts +63 -0
  117. package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +42 -0
  118. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +63 -2
  119. package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +109 -0
  120. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +95 -0
  121. package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +14 -0
  122. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
  123. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
  124. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +8 -0
  125. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
  126. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
  127. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -0
  128. package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +3 -0
  129. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +85 -0
  130. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +2 -0
  131. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +59 -0
  132. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +38 -0
  133. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +32 -0
  134. package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +109 -1
  135. package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +98 -0
  136. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +132 -3
  137. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +3 -0
  138. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +84 -1
  139. package/src/resources/extensions/gsd/unit-context-composer.ts +49 -0
  140. package/src/resources/extensions/gsd/unit-context-manifest.ts +34 -0
  141. package/src/resources/extensions/gsd/uok/audit.ts +25 -9
  142. package/src/resources/extensions/gsd/uok/contracts.ts +105 -0
  143. package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +4 -0
  144. package/src/resources/extensions/gsd/uok/loop-adapter.ts +60 -45
  145. package/src/resources/extensions/gsd/uok/timeline.ts +158 -0
  146. package/src/resources/extensions/shared/gsd-phase-state.ts +56 -3
  147. package/src/resources/extensions/shared/interview-ui.ts +18 -5
  148. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +43 -1
  149. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
  150. /package/dist/web/standalone/.next/static/{J-CU-p_sp45CJHT3R9TJS → V-3Ehy4B24f9FCGiLPWIM}/_buildManifest.js +0 -0
  151. /package/dist/web/standalone/.next/static/{J-CU-p_sp45CJHT3R9TJS → V-3Ehy4B24f9FCGiLPWIM}/_ssgManifest.js +0 -0
@@ -96,12 +96,25 @@ export function resolveImportPath(importPath, sourceFile, basePath) {
96
96
  if (existsSync(directPath)) {
97
97
  return { exists: true, resolvedPath: directPath };
98
98
  }
99
- // Only .js/.jsx/.mjs/.cjs imports legitimately fall through for the TS
100
- // ESM convention (.js .ts). Any other explicit extension (.css, .json,
101
- // .svg, images, fonts, .ts, .tsx, …) must stay unresolved when the direct
102
- // path is missing — otherwise a stray `./missing.css.ts` could shadow a
103
- // genuinely missing `./missing.css` import.
104
- if (![".js", ".jsx", ".mjs", ".cjs"].includes(explicitExt)) {
99
+ // Known concrete extensions that should NOT fall through to code-shadow
100
+ // probing when missing. This preserves the "missing.css must stay missing"
101
+ // guarantee while still allowing dotted module stems like ./route.server
102
+ // to resolve as ./route.server.ts.
103
+ const nonFallbackExtensions = new Set([
104
+ ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
105
+ ".json", ".css", ".scss", ".sass", ".less", ".styl",
106
+ ".svg", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".ico", ".bmp",
107
+ ".woff", ".woff2", ".ttf", ".otf", ".eot",
108
+ ]);
109
+ const runtimeFallbackExtensions = new Set([".js", ".jsx", ".mjs", ".cjs"]);
110
+ const dottedStemFallbackExtensions = new Set([".server", ".client", ".webhook"]);
111
+ if (explicitExt !== "" &&
112
+ !runtimeFallbackExtensions.has(explicitExt) &&
113
+ !nonFallbackExtensions.has(explicitExt) &&
114
+ !dottedStemFallbackExtensions.has(explicitExt)) {
115
+ return { exists: false, resolvedPath: null };
116
+ }
117
+ if (nonFallbackExtensions.has(explicitExt) && !runtimeFallbackExtensions.has(explicitExt)) {
105
118
  return { exists: false, resolvedPath: null };
106
119
  }
107
120
  }
@@ -163,6 +176,12 @@ export function checkImportResolution(taskRow, _priorTasks, basePath) {
163
176
  }
164
177
  const imports = extractRelativeImports(source);
165
178
  for (const { importPath, lineNum } of imports) {
179
+ // React Router generated +types modules may not exist on disk during
180
+ // post-exec checks (generated during framework build). Don't block task
181
+ // completion on these imports.
182
+ if (/^\.{1,2}\/\+types\//.test(importPath)) {
183
+ continue;
184
+ }
166
185
  const resolution = resolveImportPath(importPath, file, basePath);
167
186
  if (!resolution.exists) {
168
187
  results.push({
@@ -100,8 +100,26 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
100
100
  "context_mode",
101
101
  "planning_depth",
102
102
  ]);
103
- /** Canonical list of all dispatch unit types. */
104
- export const KNOWN_UNIT_TYPES = [
103
+ /**
104
+ * Broad union of every recognized unit-type *label* used across the codebase.
105
+ *
106
+ * This intentionally covers more than the manifest-tracked dispatch units in
107
+ * `unit-context-manifest.ts:KNOWN_UNIT_TYPES`. Examples that live here but not
108
+ * in the manifest:
109
+ * - `discuss-slice` — dispatched by `guided-flow.ts` rather than auto-mode;
110
+ * composer falls through to default behavior via `resolveManifest()` null path.
111
+ * - `worktree-merge` — used as a model-routing case, prompt-template name, and
112
+ * commit-message label, not as an LLM-dispatched unit.
113
+ *
114
+ * Used by `preferences-validation.ts` to validate user-provided unit-type
115
+ * references in preferences (model overrides, skill rules, etc.) — preferences
116
+ * may legitimately reference any label, including non-dispatched ones.
117
+ *
118
+ * The manifest-strict subset lives in `unit-context-manifest.ts:KNOWN_UNIT_TYPES`
119
+ * and is enforced 1:1 against `UNIT_MANIFESTS` by the parity test in
120
+ * `tests/unit-context-manifest.test.ts`.
121
+ */
122
+ export const KNOWN_UNIT_LABELS = [
105
123
  "research-milestone", "plan-milestone", "research-slice", "plan-slice", "refine-slice",
106
124
  "execute-task", "reactive-execute", "gate-evaluate", "complete-slice", "replan-slice", "reassess-roadmap",
107
125
  "run-uat", "complete-milestone", "validate-milestone", "rewrite-docs",
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { VALID_BRANCH_NAME } from "./git-service.js";
9
9
  import { normalizeStringArray } from "../shared/format-utils.js";
10
- import { KNOWN_PREFERENCE_KEYS, KNOWN_UNIT_TYPES, SKILL_ACTIONS, } from "./preferences-types.js";
10
+ import { KNOWN_PREFERENCE_KEYS, KNOWN_UNIT_LABELS, SKILL_ACTIONS, } from "./preferences-types.js";
11
11
  const VALID_TOKEN_PROFILES = new Set(["budget", "balanced", "quality", "burn-max"]);
12
12
  const VALID_UOK_TURN_ACTIONS = new Set([
13
13
  "commit",
@@ -432,7 +432,7 @@ export function validatePreferences(preferences) {
432
432
  if (preferences.post_unit_hooks && Array.isArray(preferences.post_unit_hooks)) {
433
433
  const validHooks = [];
434
434
  const seenNames = new Set();
435
- const knownUnitTypes = new Set(KNOWN_UNIT_TYPES);
435
+ const knownUnitTypes = new Set(KNOWN_UNIT_LABELS);
436
436
  for (const hook of preferences.post_unit_hooks) {
437
437
  if (!hook || typeof hook !== "object") {
438
438
  errors.push("post_unit_hooks entry must be an object");
@@ -493,7 +493,7 @@ export function validatePreferences(preferences) {
493
493
  if (preferences.pre_dispatch_hooks && Array.isArray(preferences.pre_dispatch_hooks)) {
494
494
  const validPreHooks = [];
495
495
  const seenPreNames = new Set();
496
- const knownUnitTypes = new Set(KNOWN_UNIT_TYPES);
496
+ const knownUnitTypes = new Set(KNOWN_UNIT_LABELS);
497
497
  const validActions = new Set(["modify", "skip", "replace"]);
498
498
  for (const hook of preferences.pre_dispatch_hooks) {
499
499
  if (!hook || typeof hook !== "object") {
@@ -1,7 +1,7 @@
1
1
  import { ensureDbOpen } from "../bootstrap/dynamic-tools.js";
2
2
  import { sanitizeCompleteMilestoneParams } from "../bootstrap/sanitize-complete-milestone.js";
3
3
  import { loadWriteGateSnapshot, shouldBlockContextArtifactSaveInSnapshot, shouldBlockRootArtifactSaveInSnapshot } from "../bootstrap/write-gate.js";
4
- import { getActiveRequirements, getMilestone, getSliceStatusSummary, getSliceTaskCounts, readTransaction, saveGateResult, } from "../gsd-db.js";
4
+ import { getActiveRequirements, insertMilestone, getMilestone, getSliceStatusSummary, getSliceTaskCounts, readTransaction, saveGateResult, } from "../gsd-db.js";
5
5
  import { GATE_REGISTRY } from "../gate-registry.js";
6
6
  import { generateRequirementsMd, saveArtifactToDb } from "../db-writer.js";
7
7
  import { resolveMilestoneFile, resolveSliceFile } from "../paths.js";
@@ -17,6 +17,7 @@ import { handleValidateMilestone } from "./validate-milestone.js";
17
17
  import { logError, logWarning } from "../workflow-logger.js";
18
18
  import { invalidateStateCache } from "../state.js";
19
19
  import { loadEffectiveGSDPreferences } from "../preferences.js";
20
+ import { parseProject } from "../schemas/parsers.js";
20
21
  export const SUPPORTED_SUMMARY_ARTIFACT_TYPES = [
21
22
  "SUMMARY",
22
23
  "RESEARCH",
@@ -37,6 +38,19 @@ function isRootSummaryArtifactType(artifactType) {
37
38
  artifactType === "REQUIREMENTS" ||
38
39
  artifactType === "REQUIREMENTS-DRAFT";
39
40
  }
41
+ function registerProjectMilestoneSequence(content) {
42
+ const parsed = parseProject(content);
43
+ const registered = [];
44
+ for (const milestone of parsed.milestones) {
45
+ insertMilestone({
46
+ id: milestone.id,
47
+ title: milestone.title,
48
+ status: milestone.done ? "complete" : "queued",
49
+ });
50
+ registered.push(milestone.id);
51
+ }
52
+ return registered;
53
+ }
40
54
  export async function executeSummarySave(params, basePath = process.cwd()) {
41
55
  const dbAvailable = await ensureDbOpen(basePath);
42
56
  if (!dbAvailable) {
@@ -126,6 +140,66 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
126
140
  slice_id: isRootArtifact ? undefined : params.slice_id,
127
141
  task_id: isRootArtifact ? undefined : params.task_id,
128
142
  }, basePath);
143
+ let registeredMilestones = [];
144
+ if (params.artifact_type === "PROJECT") {
145
+ try {
146
+ registeredMilestones = registerProjectMilestoneSequence(contentToSave);
147
+ if (registeredMilestones.length > 0)
148
+ invalidateStateCache();
149
+ }
150
+ catch (err) {
151
+ const msg = err instanceof Error ? err.message : String(err);
152
+ logError("tool", `gsd_summary_save: PROJECT artifact persisted but milestone registration threw: ${msg}`, {
153
+ tool: "gsd_summary_save",
154
+ error: String(err),
155
+ stack: err instanceof Error ? err.stack ?? "" : "",
156
+ });
157
+ // PROJECT.md was persisted by saveArtifactToDb above; the artifacts row
158
+ // changed even though no milestones registered. Invalidate so subsequent
159
+ // /gsd reads see the persisted artifact instead of the pre-save cache.
160
+ invalidateStateCache();
161
+ return {
162
+ content: [{
163
+ type: "text",
164
+ text: `Error: PROJECT.md was saved to ${relativePath} but milestone registration failed: ${msg}. ` +
165
+ `The DB has no milestone rows for this project, so /gsd will report "No Active Milestone". ` +
166
+ `Re-call gsd_summary_save(PROJECT) once the underlying error is resolved — INSERT OR IGNORE makes registration idempotent.`,
167
+ }],
168
+ details: {
169
+ operation: "save_summary",
170
+ path: relativePath,
171
+ artifact_type: params.artifact_type,
172
+ error: "milestone_registration_threw",
173
+ registration_error: msg,
174
+ },
175
+ isError: true,
176
+ };
177
+ }
178
+ if (registeredMilestones.length === 0) {
179
+ logError("tool", `gsd_summary_save: PROJECT.md saved to ${relativePath} but parsed zero milestones — registration produced no DB rows`, {
180
+ tool: "gsd_summary_save",
181
+ });
182
+ // PROJECT.md was persisted; invalidate so subsequent reads see the new
183
+ // artifacts row even though no milestones registered.
184
+ invalidateStateCache();
185
+ return {
186
+ content: [{
187
+ type: "text",
188
+ text: `Error: PROJECT.md was saved to ${relativePath} but contains zero parseable milestone lines, ` +
189
+ `so no milestones were registered in the DB. /gsd will report "No Active Milestone". ` +
190
+ `Rewrite PROJECT.md so the "Milestone Sequence" section uses canonical lines: ` +
191
+ `\`- [ ] M001: <Title> — <One-liner>\` (em-dash, double-dash \`--\`, or single-dash \`-\` separator), then re-call gsd_summary_save(PROJECT).`,
192
+ }],
193
+ details: {
194
+ operation: "save_summary",
195
+ path: relativePath,
196
+ artifact_type: params.artifact_type,
197
+ error: "milestone_registration_empty_parse",
198
+ },
199
+ isError: true,
200
+ };
201
+ }
202
+ }
129
203
  if (params.artifact_type === "CONTEXT" && !params.task_id) {
130
204
  try {
131
205
  const draftFile = params.slice_id
@@ -140,7 +214,13 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
140
214
  }
141
215
  return {
142
216
  content: [{ type: "text", text: `Saved ${params.artifact_type} artifact to ${relativePath}` }],
143
- details: { operation: "save_summary", path: relativePath, artifact_type: params.artifact_type, content_source: contentSource },
217
+ details: {
218
+ operation: "save_summary",
219
+ path: relativePath,
220
+ artifact_type: params.artifact_type,
221
+ content_source: contentSource,
222
+ ...(registeredMilestones.length > 0 ? { registeredMilestones } : {}),
223
+ },
144
224
  };
145
225
  }
146
226
  catch (err) {
@@ -71,6 +71,38 @@ export function manifestBudgetChars(unitType) {
71
71
  const manifest = resolveManifest(unitType);
72
72
  return manifest ? manifest.maxSystemPromptChars : null;
73
73
  }
74
+ const CONTEXT_MODE_LANE_LABELS = {
75
+ interview: "interview",
76
+ research: "research",
77
+ planning: "planning",
78
+ execution: "execution",
79
+ verification: "verification",
80
+ orchestration: "orchestration",
81
+ docs: "documentation",
82
+ };
83
+ const CONTEXT_MODE_GUIDANCE = "Use `gsd_exec` for noisy scans, builds, and tests so full output stays out of prompt context; call `gsd_exec_search` before repeating prior runs; call `gsd_resume` after compaction or resume to recover stored execution context.";
84
+ /**
85
+ * Render the Context Mode instruction lane for a unit type. Unknown unit
86
+ * types, disabled config, and explicit `contextMode: "none"` all omit the
87
+ * block so callers can prefix this safely without extra branching.
88
+ */
89
+ export function composeContextModeInstructions(unitType, opts) {
90
+ if (!opts.enabled)
91
+ return "";
92
+ const manifest = resolveManifest(unitType);
93
+ if (!manifest || manifest.contextMode === "none")
94
+ return "";
95
+ const lane = CONTEXT_MODE_LANE_LABELS[manifest.contextMode];
96
+ if (opts.renderMode === "nested") {
97
+ return `Context Mode (${lane} lane): ${CONTEXT_MODE_GUIDANCE}`;
98
+ }
99
+ return [
100
+ "## Context Mode",
101
+ "",
102
+ `Lane: **${lane} lane**.`,
103
+ CONTEXT_MODE_GUIDANCE,
104
+ ].join("\n");
105
+ }
74
106
  const SECTION_SEPARATOR = "\n\n---\n\n";
75
107
  /**
76
108
  * Compose all manifest-declared context for a unit type using the v2
@@ -137,6 +137,7 @@ export const UNIT_MANIFESTS = {
137
137
  memory: "prompt-relevant",
138
138
  codebaseMap: true,
139
139
  preferences: "active-only",
140
+ contextMode: "research",
140
141
  tools: TOOLS_PLANNING,
141
142
  artifacts: {
142
143
  // Phase 3 migration (#4782): matches today's actual
@@ -153,6 +154,7 @@ export const UNIT_MANIFESTS = {
153
154
  memory: "prompt-relevant",
154
155
  codebaseMap: true,
155
156
  preferences: "active-only",
157
+ contextMode: "planning",
156
158
  tools: TOOLS_PLANNING,
157
159
  artifacts: {
158
160
  inline: ["project", "requirements", "decisions", "milestone-research", "templates"],
@@ -167,6 +169,7 @@ export const UNIT_MANIFESTS = {
167
169
  memory: "prompt-relevant",
168
170
  codebaseMap: true,
169
171
  preferences: "active-only",
172
+ contextMode: "interview",
170
173
  tools: TOOLS_PLANNING,
171
174
  artifacts: {
172
175
  inline: ["project", "requirements", "decisions", "milestone-context", "templates"],
@@ -181,6 +184,7 @@ export const UNIT_MANIFESTS = {
181
184
  memory: "prompt-relevant",
182
185
  codebaseMap: false,
183
186
  preferences: "active-only",
187
+ contextMode: "verification",
184
188
  // planning-dispatch: validation is a verification-fan-out unit. It reads
185
189
  // the milestone surface and dispatches reviewer/security/tester subagents
186
190
  // to report findings without touching user source. Mirrors
@@ -199,6 +203,7 @@ export const UNIT_MANIFESTS = {
199
203
  memory: "prompt-relevant",
200
204
  codebaseMap: false,
201
205
  preferences: "active-only",
206
+ contextMode: "verification",
202
207
  // planning-dispatch: completion is a high-leverage place to fan out to
203
208
  // reviewer / security / tester subagents. They read the diff and report
204
209
  // findings; they do not write user source. Write isolation to .gsd/ is
@@ -221,6 +226,7 @@ export const UNIT_MANIFESTS = {
221
226
  memory: "prompt-relevant",
222
227
  codebaseMap: true,
223
228
  preferences: "active-only",
229
+ contextMode: "research",
224
230
  tools: TOOLS_PLANNING,
225
231
  artifacts: {
226
232
  inline: ["roadmap", "milestone-research", "dependency-summaries", "templates"],
@@ -235,6 +241,7 @@ export const UNIT_MANIFESTS = {
235
241
  memory: "prompt-relevant",
236
242
  codebaseMap: true,
237
243
  preferences: "active-only",
244
+ contextMode: "planning",
238
245
  // planning-dispatch: allows subagent dispatch so the planner can fan out
239
246
  // to scout for codebase recon and to planner/decompose-style specialists
240
247
  // for sub-decomposition. Write-isolation to .gsd/ is preserved.
@@ -252,6 +259,7 @@ export const UNIT_MANIFESTS = {
252
259
  memory: "prompt-relevant",
253
260
  codebaseMap: true,
254
261
  preferences: "active-only",
262
+ contextMode: "planning",
255
263
  // See plan-slice — same rationale: dispatch to scout/planner-style
256
264
  // specialists during refinement is materially better than re-doing recon
257
265
  // inline.
@@ -269,6 +277,7 @@ export const UNIT_MANIFESTS = {
269
277
  memory: "prompt-relevant",
270
278
  codebaseMap: true,
271
279
  preferences: "active-only",
280
+ contextMode: "planning",
272
281
  tools: TOOLS_PLANNING,
273
282
  artifacts: {
274
283
  inline: ["slice-plan", "slice-research", "dependency-summaries", "prior-task-summaries", "templates"],
@@ -283,6 +292,7 @@ export const UNIT_MANIFESTS = {
283
292
  memory: "prompt-relevant",
284
293
  codebaseMap: false,
285
294
  preferences: "active-only",
295
+ contextMode: "verification",
286
296
  // See complete-milestone — same rationale: dispatch to reviewer / security /
287
297
  // tester subagents to fan out review work without bloating this unit's
288
298
  // context.
@@ -304,6 +314,7 @@ export const UNIT_MANIFESTS = {
304
314
  memory: "critical-only",
305
315
  codebaseMap: false,
306
316
  preferences: "none",
317
+ contextMode: "planning",
307
318
  tools: TOOLS_PLANNING,
308
319
  artifacts: {
309
320
  // Phase 2 pilot (#4782): manifest now matches today's actual
@@ -322,6 +333,7 @@ export const UNIT_MANIFESTS = {
322
333
  memory: "prompt-relevant",
323
334
  codebaseMap: true,
324
335
  preferences: "active-only",
336
+ contextMode: "execution",
325
337
  tools: TOOLS_ALL,
326
338
  artifacts: {
327
339
  inline: ["task-plan", "slice-plan", "prior-task-summaries", "templates"],
@@ -336,6 +348,7 @@ export const UNIT_MANIFESTS = {
336
348
  memory: "prompt-relevant",
337
349
  codebaseMap: true,
338
350
  preferences: "active-only",
351
+ contextMode: "execution",
339
352
  tools: TOOLS_ALL,
340
353
  artifacts: {
341
354
  inline: ["slice-plan", "prior-task-summaries", "templates"],
@@ -351,6 +364,7 @@ export const UNIT_MANIFESTS = {
351
364
  memory: "critical-only",
352
365
  codebaseMap: false,
353
366
  preferences: "active-only",
367
+ contextMode: "verification",
354
368
  tools: TOOLS_PLANNING,
355
369
  artifacts: {
356
370
  // Phase 3 migration (#4782): manifest matches today's actual
@@ -369,6 +383,7 @@ export const UNIT_MANIFESTS = {
369
383
  memory: "critical-only",
370
384
  codebaseMap: false,
371
385
  preferences: "active-only",
386
+ contextMode: "verification",
372
387
  tools: TOOLS_PLANNING,
373
388
  artifacts: {
374
389
  inline: ["slice-plan", "prior-task-summaries"],
@@ -383,6 +398,7 @@ export const UNIT_MANIFESTS = {
383
398
  memory: "prompt-relevant",
384
399
  codebaseMap: true,
385
400
  preferences: "active-only",
401
+ contextMode: "docs",
386
402
  tools: TOOLS_DOCS,
387
403
  artifacts: {
388
404
  inline: ["project", "requirements", "decisions", "templates"],
@@ -401,6 +417,7 @@ export const UNIT_MANIFESTS = {
401
417
  memory: "none",
402
418
  codebaseMap: false,
403
419
  preferences: "none",
420
+ contextMode: "none",
404
421
  tools: TOOLS_PLANNING,
405
422
  artifacts: {
406
423
  inline: [],
@@ -418,6 +435,7 @@ export const UNIT_MANIFESTS = {
418
435
  memory: "prompt-relevant",
419
436
  codebaseMap: true,
420
437
  preferences: "active-only",
438
+ contextMode: "interview",
421
439
  tools: TOOLS_PLANNING,
422
440
  artifacts: {
423
441
  inline: ["templates"],
@@ -434,6 +452,7 @@ export const UNIT_MANIFESTS = {
434
452
  memory: "prompt-relevant",
435
453
  codebaseMap: true,
436
454
  preferences: "active-only",
455
+ contextMode: "interview",
437
456
  tools: TOOLS_PLANNING,
438
457
  artifacts: {
439
458
  inline: ["project", "templates"],
@@ -450,6 +469,7 @@ export const UNIT_MANIFESTS = {
450
469
  memory: "none",
451
470
  codebaseMap: false,
452
471
  preferences: "none",
472
+ contextMode: "none",
453
473
  tools: TOOLS_PLANNING,
454
474
  artifacts: {
455
475
  inline: [],
@@ -468,6 +488,7 @@ export const UNIT_MANIFESTS = {
468
488
  memory: "prompt-relevant",
469
489
  codebaseMap: true,
470
490
  preferences: "active-only",
491
+ contextMode: "research",
471
492
  tools: { mode: "planning-dispatch", allowedSubagents: ["scout"] },
472
493
  artifacts: {
473
494
  inline: ["project", "requirements", "templates"],
@@ -1,3 +1,4 @@
1
+ // GSD2 UOK Audit Events and DB-First Projection Writes
1
2
  import { appendFileSync, closeSync, existsSync, mkdirSync, openSync } from "node:fs";
2
3
  import { join } from "node:path";
3
4
  import { randomUUID } from "node:crypto";
@@ -5,6 +6,7 @@ import { isStaleWrite } from "../auto/turn-epoch.js";
5
6
  import { withFileLockSync } from "../file-lock.js";
6
7
  import { gsdRoot } from "../paths.js";
7
8
  import { isDbAvailable, insertAuditEvent } from "../gsd-db.js";
9
+ import { CURRENT_UOK_CONTRACT_VERSION, validateAuditEvent } from "./contracts.js";
8
10
  function auditLogPath(basePath) {
9
11
  return join(gsdRoot(basePath), "audit", "events.jsonl");
10
12
  }
@@ -13,6 +15,7 @@ function ensureAuditDir(basePath) {
13
15
  }
14
16
  export function buildAuditEnvelope(args) {
15
17
  return {
18
+ version: CURRENT_UOK_CONTRACT_VERSION,
16
19
  eventId: randomUUID(),
17
20
  traceId: args.traceId,
18
21
  turnId: args.turnId,
@@ -27,6 +30,25 @@ export function emitUokAuditEvent(basePath, event) {
27
30
  // Drop writes from a turn superseded by timeout recovery / cancellation.
28
31
  if (isStaleWrite("uok-audit"))
29
32
  return;
33
+ const validation = validateAuditEvent(event);
34
+ if (!validation.ok) {
35
+ throw new Error(`Invalid UOK audit event: ${validation.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
36
+ }
37
+ const canonical = validation.value;
38
+ if (isDbAvailable()) {
39
+ try {
40
+ insertAuditEvent({
41
+ ...canonical,
42
+ payload: {
43
+ ...canonical.payload,
44
+ contractVersion: canonical.version ?? CURRENT_UOK_CONTRACT_VERSION,
45
+ },
46
+ });
47
+ }
48
+ catch (err) {
49
+ throw new Error(`DB authoritative audit write failed: ${err instanceof Error ? err.message : String(err)}`);
50
+ }
51
+ }
30
52
  try {
31
53
  ensureAuditDir(basePath);
32
54
  const path = auditLogPath(basePath);
@@ -39,18 +61,10 @@ export function emitUokAuditEvent(basePath, event) {
39
61
  // POSIX O_APPEND atomicity still protects small line writes, so skipping
40
62
  // the lock rather than stalling orchestration is the correct tradeoff.
41
63
  withFileLockSync(path, () => {
42
- appendFileSync(path, `${JSON.stringify(event)}\n`, "utf-8");
64
+ appendFileSync(path, `${JSON.stringify(canonical)}\n`, "utf-8");
43
65
  }, { onLocked: "skip" });
44
66
  }
45
67
  catch {
46
68
  // Best-effort: audit writes must never break orchestration.
47
69
  }
48
- if (!isDbAvailable())
49
- return;
50
- try {
51
- insertAuditEvent(event);
52
- }
53
- catch {
54
- // Projection failures are non-fatal while legacy readers are still active.
55
- }
56
70
  }
@@ -1 +1,69 @@
1
- export {};
1
+ // GSD2 UOK Contract Types and Versioning
2
+ export const CURRENT_UOK_CONTRACT_VERSION = "1";
3
+ function isRecord(value) {
4
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5
+ }
6
+ function normalizeVersion(value) {
7
+ return value === CURRENT_UOK_CONTRACT_VERSION ? CURRENT_UOK_CONTRACT_VERSION : "0";
8
+ }
9
+ function requireString(value, key, issues) {
10
+ if (typeof value[key] !== "string" || value[key] === "") {
11
+ issues.push({ path: key, message: `${key} must be a non-empty string` });
12
+ }
13
+ }
14
+ function requireRecord(value, key, issues) {
15
+ if (!isRecord(value[key])) {
16
+ issues.push({ path: key, message: `${key} must be an object` });
17
+ }
18
+ }
19
+ export function normalizeTurnResult(value) {
20
+ return { ...value, version: normalizeVersion(value.version) };
21
+ }
22
+ export function normalizeDispatchEnvelope(value) {
23
+ return { ...value, version: normalizeVersion(value.version) };
24
+ }
25
+ export function normalizeAuditEvent(value) {
26
+ return { ...value, version: normalizeVersion(value.version) };
27
+ }
28
+ export function validateTurnResult(value) {
29
+ const normalized = normalizeTurnResult(value);
30
+ const record = normalized;
31
+ const issues = [];
32
+ requireString(record, "traceId", issues);
33
+ requireString(record, "turnId", issues);
34
+ if (!Number.isInteger(record.iteration)) {
35
+ issues.push({ path: "iteration", message: "iteration must be an integer" });
36
+ }
37
+ requireString(record, "status", issues);
38
+ requireString(record, "failureClass", issues);
39
+ if (!Array.isArray(record.phaseResults)) {
40
+ issues.push({ path: "phaseResults", message: "phaseResults must be an array" });
41
+ }
42
+ requireString(record, "startedAt", issues);
43
+ requireString(record, "finishedAt", issues);
44
+ return { ok: issues.length === 0, value: normalized, issues };
45
+ }
46
+ export function validateDispatchEnvelope(value) {
47
+ const normalized = normalizeDispatchEnvelope(value);
48
+ const record = normalized;
49
+ const issues = [];
50
+ requireString(record, "action", issues);
51
+ requireRecord(record, "reason", issues);
52
+ if (isRecord(record.reason)) {
53
+ requireString(record.reason, "reasonCode", issues);
54
+ requireString(record.reason, "summary", issues);
55
+ }
56
+ return { ok: issues.length === 0, value: normalized, issues };
57
+ }
58
+ export function validateAuditEvent(value) {
59
+ const normalized = normalizeAuditEvent(value);
60
+ const record = normalized;
61
+ const issues = [];
62
+ requireString(record, "eventId", issues);
63
+ requireString(record, "traceId", issues);
64
+ requireString(record, "category", issues);
65
+ requireString(record, "type", issues);
66
+ requireString(record, "ts", issues);
67
+ requireRecord(record, "payload", issues);
68
+ return { ok: issues.length === 0, value: normalized, issues };
69
+ }
@@ -1,5 +1,8 @@
1
+ // GSD2 UOK Dispatch Envelope Builder
2
+ import { CURRENT_UOK_CONTRACT_VERSION } from "./contracts.js";
1
3
  export function buildDispatchEnvelope(input) {
2
4
  return {
5
+ version: CURRENT_UOK_CONTRACT_VERSION,
3
6
  action: input.action,
4
7
  nodeKind: input.node?.kind,
5
8
  unitType: input.unitType,