@opengsd/gsd-pi 1.3.0-dev.72e3af2a → 1.3.0-dev.738160b9

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 (213) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +17 -4
  3. package/dist/headless-answers.js +1 -1
  4. package/dist/headless-ui.d.ts +1 -1
  5. package/dist/headless-ui.js +1 -1
  6. package/dist/headless.js +1 -1
  7. package/dist/resources/.managed-resources-content-hash +1 -1
  8. package/dist/resources/extensions/claude-code-cli/models.js +9 -0
  9. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  10. package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +1 -1
  11. package/dist/resources/extensions/gsd/auto-budget.js +5 -1
  12. package/dist/resources/extensions/gsd/auto-dispatch.js +14 -1
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
  14. package/dist/resources/extensions/gsd/auto-prompts.js +3 -3
  15. package/dist/resources/extensions/gsd/auto-recovery.js +3 -3
  16. package/dist/resources/extensions/gsd/auto-verification.js +5 -2
  17. package/dist/resources/extensions/gsd/auto.js +19 -10
  18. package/dist/resources/extensions/gsd/bootstrap/core-session-tools.js +1 -0
  19. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +5 -4
  20. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  21. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +31 -2
  22. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +32 -4
  23. package/dist/resources/extensions/gsd/closeout-consistency-gate.js +3 -1
  24. package/dist/resources/extensions/gsd/commands/handlers/auto.js +1 -1
  25. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  26. package/dist/resources/extensions/gsd/commands-usage.js +1 -1
  27. package/dist/resources/extensions/gsd/db/engine.js +4 -1
  28. package/dist/resources/extensions/gsd/db-memory-fts-schema.js +34 -0
  29. package/dist/resources/extensions/gsd/delegation-policy.js +1 -1
  30. package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  31. package/dist/resources/extensions/gsd/doctor-engine-checks.js +192 -4
  32. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +3 -0
  33. package/dist/resources/extensions/gsd/exec-sandbox.js +8 -2
  34. package/dist/resources/extensions/gsd/flat-phase-migration.js +45 -0
  35. package/dist/resources/extensions/gsd/git-service.js +57 -0
  36. package/dist/resources/extensions/gsd/gitignore.js +4 -1
  37. package/dist/resources/extensions/gsd/json-persistence.js +20 -0
  38. package/dist/resources/extensions/gsd/knowledge-backfill.js +18 -11
  39. package/dist/resources/extensions/gsd/memory-backfill.js +28 -21
  40. package/dist/resources/extensions/gsd/metrics.js +4 -4
  41. package/dist/resources/extensions/gsd/migrate-external.js +11 -2
  42. package/dist/resources/extensions/gsd/orphan-stash-audit.js +92 -16
  43. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  44. package/dist/resources/extensions/gsd/prompts/plan-slice.md +3 -3
  45. package/dist/resources/extensions/gsd/root-write-leak-guard.js +7 -1
  46. package/dist/resources/extensions/gsd/safety/destructive-guard.js +8 -1
  47. package/dist/resources/extensions/gsd/safety/evidence-collector.js +16 -1
  48. package/dist/resources/extensions/gsd/safety/file-change-validator.js +5 -2
  49. package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -2
  50. package/dist/resources/extensions/gsd/tools/plan-slice.js +59 -44
  51. package/dist/resources/extensions/gsd/tools/plan-task.js +28 -5
  52. package/dist/resources/extensions/gsd/undo.js +43 -6
  53. package/dist/resources/extensions/gsd/unit-registry.js +4 -2
  54. package/dist/resources/extensions/gsd/verification-gate.js +7 -0
  55. package/dist/resources/extensions/gsd/workflow-manifest.js +126 -13
  56. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  57. package/dist/web/standalone/.next/BUILD_ID +1 -1
  58. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  59. package/dist/web/standalone/.next/build-manifest.json +2 -2
  60. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  61. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.html +1 -1
  78. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  85. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  86. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  88. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  89. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  90. package/package.json +1 -1
  91. package/packages/cloud-mcp-gateway/package.json +2 -2
  92. package/packages/contracts/package.json +1 -1
  93. package/packages/daemon/package.json +4 -4
  94. package/packages/gsd-agent-core/package.json +5 -5
  95. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.d.ts +2 -0
  96. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  97. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +23 -5
  98. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
  99. package/packages/gsd-agent-modes/package.json +7 -7
  100. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  101. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  102. package/packages/mcp-server/package.json +4 -4
  103. package/packages/native/package.json +1 -1
  104. package/packages/pi-agent-core/dist/agent-loop.js +3 -2
  105. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  106. package/packages/pi-agent-core/package.json +1 -1
  107. package/packages/pi-ai/dist/models.generated.d.ts +46 -0
  108. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  109. package/packages/pi-ai/dist/models.generated.js +38 -0
  110. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  111. package/packages/pi-ai/package.json +1 -1
  112. package/packages/pi-coding-agent/package.json +7 -7
  113. package/packages/pi-tui/package.json +2 -2
  114. package/packages/rpc-client/package.json +2 -2
  115. package/pkg/package.json +1 -1
  116. package/src/resources/extensions/claude-code-cli/models.ts +9 -0
  117. package/src/resources/extensions/claude-code-cli/tests/index.test.ts +14 -0
  118. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  119. package/src/resources/extensions/gsd/auto/session.ts +9 -0
  120. package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +3 -1
  121. package/src/resources/extensions/gsd/auto-budget.ts +12 -1
  122. package/src/resources/extensions/gsd/auto-dispatch.ts +14 -1
  123. package/src/resources/extensions/gsd/auto-post-unit.ts +1 -1
  124. package/src/resources/extensions/gsd/auto-prompts.ts +3 -3
  125. package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
  126. package/src/resources/extensions/gsd/auto-verification.ts +5 -1
  127. package/src/resources/extensions/gsd/auto.ts +23 -8
  128. package/src/resources/extensions/gsd/bootstrap/core-session-tools.ts +1 -0
  129. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +5 -4
  130. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  131. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +41 -2
  132. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +34 -4
  133. package/src/resources/extensions/gsd/closeout-consistency-gate.ts +2 -1
  134. package/src/resources/extensions/gsd/commands/handlers/auto.ts +1 -1
  135. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  136. package/src/resources/extensions/gsd/commands-usage.ts +1 -1
  137. package/src/resources/extensions/gsd/db/engine.ts +8 -1
  138. package/src/resources/extensions/gsd/db-memory-fts-schema.ts +45 -0
  139. package/src/resources/extensions/gsd/delegation-policy.ts +1 -1
  140. package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  141. package/src/resources/extensions/gsd/doctor-engine-checks.ts +233 -5
  142. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +3 -0
  143. package/src/resources/extensions/gsd/doctor-types.ts +3 -0
  144. package/src/resources/extensions/gsd/exec-sandbox.ts +7 -2
  145. package/src/resources/extensions/gsd/flat-phase-migration.ts +44 -0
  146. package/src/resources/extensions/gsd/git-service.ts +64 -0
  147. package/src/resources/extensions/gsd/gitignore.ts +4 -1
  148. package/src/resources/extensions/gsd/json-persistence.ts +20 -0
  149. package/src/resources/extensions/gsd/knowledge-backfill.ts +16 -10
  150. package/src/resources/extensions/gsd/memory-backfill.ts +26 -20
  151. package/src/resources/extensions/gsd/metrics.ts +4 -4
  152. package/src/resources/extensions/gsd/migrate-external.ts +8 -2
  153. package/src/resources/extensions/gsd/orphan-stash-audit.ts +108 -20
  154. package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  155. package/src/resources/extensions/gsd/prompts/plan-slice.md +3 -3
  156. package/src/resources/extensions/gsd/root-write-leak-guard.ts +13 -1
  157. package/src/resources/extensions/gsd/safety/destructive-guard.ts +9 -1
  158. package/src/resources/extensions/gsd/safety/evidence-collector.ts +16 -1
  159. package/src/resources/extensions/gsd/safety/file-change-validator.ts +8 -2
  160. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +4 -3
  161. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +14 -5
  162. package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +36 -0
  163. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +182 -4
  164. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +35 -0
  165. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +30 -0
  166. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +28 -0
  167. package/src/resources/extensions/gsd/tests/complete-slice-reopen-handoff.test.ts +69 -0
  168. package/src/resources/extensions/gsd/tests/db-memory-fts-schema.test.ts +58 -3
  169. package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +51 -0
  170. package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +44 -0
  171. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +143 -0
  172. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +239 -2
  173. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +16 -0
  174. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +40 -0
  175. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +37 -2
  176. package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +4 -0
  177. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +11 -2
  178. package/src/resources/extensions/gsd/tests/json-persistence-atomic.test.ts +47 -0
  179. package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +35 -0
  180. package/src/resources/extensions/gsd/tests/memory-store.test.ts +48 -0
  181. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +31 -1
  182. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +6 -0
  183. package/src/resources/extensions/gsd/tests/metrics.test.ts +1 -0
  184. package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +57 -0
  185. package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +91 -7
  186. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +2 -0
  187. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +10 -0
  188. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +103 -4
  189. package/src/resources/extensions/gsd/tests/plan-task.test.ts +44 -1
  190. package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +4 -2
  191. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +58 -0
  192. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -6
  193. package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +103 -0
  194. package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +20 -1
  195. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +8 -3
  196. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +62 -5
  197. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +6 -3
  198. package/src/resources/extensions/gsd/tests/undo.test.ts +74 -0
  199. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +38 -0
  200. package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +69 -4
  201. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +96 -15
  202. package/src/resources/extensions/gsd/tests/workflow-mcp-preflight.test.ts +1 -0
  203. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +8 -4
  204. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +6 -5
  205. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
  206. package/src/resources/extensions/gsd/tools/plan-slice.ts +60 -47
  207. package/src/resources/extensions/gsd/tools/plan-task.ts +30 -5
  208. package/src/resources/extensions/gsd/undo.ts +47 -6
  209. package/src/resources/extensions/gsd/unit-registry.ts +4 -2
  210. package/src/resources/extensions/gsd/verification-gate.ts +8 -0
  211. package/src/resources/extensions/gsd/workflow-manifest.ts +141 -7
  212. /package/dist/web/standalone/.next/static/{O7xDYXO0r4zFhIzY1hrWV → 2A_WqqCS-EoYZxN9_v3d2}/_buildManifest.js +0 -0
  213. /package/dist/web/standalone/.next/static/{O7xDYXO0r4zFhIzY1hrWV → 2A_WqqCS-EoYZxN9_v3d2}/_ssgManifest.js +0 -0
@@ -1,14 +1,16 @@
1
1
  import { clearParseCache } from "../files.js";
2
2
  import { isClosedStatus } from "../status-guards.js";
3
3
  import { isNonEmptyString, validateStringArray } from "../validation.js";
4
- import { transaction, getSlice, getTask, insertTask, upsertTaskPlanning } from "../gsd-db.js";
4
+ import { getGateIdsForTurn } from "../gate-registry.js";
5
+ import { transaction, getSlice, getTask, insertTask, upsertTaskPlanning, insertGateRow, setSliceSketchFlag } from "../gsd-db.js";
5
6
  import { invalidateStateCache } from "../state.js";
6
7
  import { renderTaskPlanFromDb, renderPlanFromDb } from "../markdown-renderer.js";
7
- import { resolveSliceFile, resolveTasksDir } from "../paths.js";
8
+ import { resolveTasksDir } from "../paths.js";
8
9
  import { flushWorkflowProjections } from "../projection-flush.js";
9
10
  import { writeManifest } from "../workflow-manifest.js";
10
11
  import { appendEvent } from "../workflow-events.js";
11
12
  import { logWarning } from "../workflow-logger.js";
13
+ import { loadEffectiveGSDPreferences } from "../preferences.js";
12
14
  import { validatePathOnlyPlanningFields, validatePlanningPathScope } from "../planning-path-scope.js";
13
15
  function validateParams(params) {
14
16
  if (!isNonEmptyString(params?.milestoneId))
@@ -35,6 +37,12 @@ function validateParams(params) {
35
37
  expectedOutput: validateStringArray(params.expectedOutput, "expectedOutput"),
36
38
  };
37
39
  }
40
+ function resolveTaskGates(basePath) {
41
+ const loaded = loadEffectiveGSDPreferences(basePath);
42
+ if (loaded?.preferences?.gate_evaluation?.task_gates === false)
43
+ return [];
44
+ return [...getGateIdsForTurn("execute-task")];
45
+ }
38
46
  export async function handlePlanTask(rawParams, basePath) {
39
47
  let params;
40
48
  try {
@@ -57,6 +65,14 @@ export async function handlePlanTask(rawParams, basePath) {
57
65
  if (pathScopeError) {
58
66
  return { error: `validation failed: ${pathScopeError}` };
59
67
  }
68
+ let taskGates;
69
+ try {
70
+ taskGates = resolveTaskGates(basePath);
71
+ }
72
+ catch (err) {
73
+ const message = err instanceof Error ? err.message : String(err);
74
+ return { error: `validation failed: ${message}` };
75
+ }
60
76
  // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
61
77
  // Guards must be inside the transaction so the state they check cannot
62
78
  // change between the read and the write (#2723).
@@ -97,6 +113,9 @@ export async function handlePlanTask(rawParams, basePath) {
97
113
  observabilityImpact: params.observabilityImpact ?? "",
98
114
  fullPlanMd: params.fullPlanMd,
99
115
  });
116
+ for (const gid of taskGates) {
117
+ insertGateRow({ milestoneId: params.milestoneId, sliceId: params.sliceId, gateId: gid, scope: "task", taskId: params.taskId });
118
+ }
100
119
  });
101
120
  }
102
121
  catch (err) {
@@ -110,17 +129,21 @@ export async function handlePlanTask(rawParams, basePath) {
110
129
  // Flat-phase: tasks live as checkboxes in the slice plan's <tasks> block,
111
130
  // not as standalone TID-PLAN.md files. Re-render the slice plan so the
112
131
  // new/updated task appears in the plan file that gsd-core reads.
113
- // Guard: resolveTasksDir is null in flat-phase (no tasks/ subdir exists);
114
- // only sync if a slice plan already exists (guards pre-plan-slice calls).
132
+ // Guard: resolveTasksDir is null in flat-phase (no tasks/ subdir exists).
133
+ let slicePlanSynced = false;
115
134
  try {
116
135
  const tDir = resolveTasksDir(basePath, params.milestoneId, params.sliceId);
117
- if (!tDir && resolveSliceFile(basePath, params.milestoneId, params.sliceId, "PLAN")) {
136
+ if (!tDir) {
118
137
  await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);
138
+ slicePlanSynced = true;
119
139
  }
120
140
  }
121
141
  catch (syncErr) {
122
142
  logWarning("tool", `plan-task: slice-plan sync failed: ${syncErr.message}`);
123
143
  }
144
+ if (slicePlanSynced) {
145
+ setSliceSketchFlag(params.milestoneId, params.sliceId, false);
146
+ }
124
147
  invalidateStateCache();
125
148
  clearParseCache();
126
149
  // ── Post-mutation hook: projections, manifest, event log ─────────────
@@ -13,6 +13,7 @@ import { gsdRoot, resolveTasksDir, resolveSlicePath, resolveSliceFile, resolveTa
13
13
  import { sendDesktopNotification } from "./notifications.js";
14
14
  import { getTask, getSlice, getSliceTasks, updateTaskStatus, resetSliceCascade } from "./gsd-db.js";
15
15
  import { renderPlanCheckboxes, renderRoadmapCheckboxes } from "./markdown-renderer.js";
16
+ import { UNIT_REGISTRY } from "./unit-registry.js";
16
17
  /**
17
18
  * Undo the last completed unit: revert git commits,
18
19
  * delete summary artifacts, and uncheck the task in PLAN.
@@ -35,15 +36,19 @@ export async function handleUndo(args, ctx, _pi, basePath) {
35
36
  ctx.ui.notify("Nothing to undo — no activity logs found.", "info");
36
37
  return;
37
38
  }
38
- // Extract unit type and ID from the most recent activity log filename
39
- // Format: <seq>-<unitType>-<unitId>.jsonl
40
- const match = files[0].match(/^\d+-(.+?)-(.+)\.jsonl$/);
41
- if (!match) {
39
+ // Extract unit type and ID from the most recent activity log filename.
40
+ // Both the unit type and the unit ID may contain hyphens, so anchor on the
41
+ // known unit-type vocabulary instead of guessing the unit-ID shape: a regex
42
+ // tuned to milestone-shaped IDs rejects project-level units whose IDs are
43
+ // symbolic (e.g. discuss-project uses PROJECT, workflow-preferences uses
44
+ // WORKFLOW-PREFS).
45
+ const parsed = parseActivityLogFilename(files[0]);
46
+ if (!parsed) {
42
47
  ctx.ui.notify("Nothing to undo — could not parse latest activity log.", "warning");
43
48
  return;
44
49
  }
45
- const unitType = match[1];
46
- const unitId = match[2].replace(/-/g, "/");
50
+ const unitType = parsed.unitType;
51
+ const unitId = parsed.unitId.replace(/-/g, "/");
47
52
  if (!force) {
48
53
  ctx.ui.notify(`Will undo: ${unitType} (${unitId})\n` +
49
54
  `This will:\n` +
@@ -87,6 +92,11 @@ export async function handleUndo(args, ctx, _pi, basePath) {
87
92
  if (unitType === "execute-task" && task !== undefined && slice !== undefined) {
88
93
  const [mid, sid, tid] = [milestone, slice, task];
89
94
  planUpdated = uncheckTaskInPlan(basePath, mid, sid, tid);
95
+ if (getTask(mid, sid, tid)) {
96
+ updateTaskStatus(mid, sid, tid, "pending");
97
+ await renderPlanCheckboxes(basePath, mid, sid);
98
+ planUpdated = true;
99
+ }
90
100
  }
91
101
  // 3. Try to revert git commits from activity log
92
102
  let commitsReverted = 0;
@@ -318,6 +328,33 @@ export async function handleResetSlice(args, ctx, _pi, basePath) {
318
328
  ctx.ui.notify(results.join("\n"), "success");
319
329
  }
320
330
  // ─── Helpers ──────────────────────────────────────────────────────────────────
331
+ // Known unit types sorted longest-first so a more specific type (e.g.
332
+ // "execute-task-simple") matches before a prefix of it ("execute-task") when
333
+ // splitting "<seq>-<unitType>-<unitId>.jsonl".
334
+ const UNIT_TYPES_BY_LENGTH_DESC = Object.keys(UNIT_REGISTRY).sort((a, b) => b.length - a.length);
335
+ /**
336
+ * Parse an activity-log filename of the form `<seq>-<unitType>-<unitId>.jsonl`
337
+ * (the format written by activity-log.ts). Both the unit type and the unit ID
338
+ * may contain hyphens, so we anchor on the known unit-type vocabulary rather
339
+ * than guessing the ID shape. This keeps non-milestone IDs (e.g. PROJECT,
340
+ * WORKFLOW-PREFS) parseable. Returns null when the name has no sequence prefix
341
+ * or does not start with a recognised unit type.
342
+ */
343
+ export function parseActivityLogFilename(filename) {
344
+ const seqMatch = filename.match(/^\d+-(.+)\.jsonl$/);
345
+ if (!seqMatch)
346
+ return null;
347
+ const rest = seqMatch[1];
348
+ for (const unitType of UNIT_TYPES_BY_LENGTH_DESC) {
349
+ const prefix = `${unitType}-`;
350
+ if (rest.startsWith(prefix)) {
351
+ const unitId = rest.slice(prefix.length);
352
+ if (unitId.length > 0)
353
+ return { unitType, unitId };
354
+ }
355
+ }
356
+ return null;
357
+ }
321
358
  export function uncheckTaskInPlan(basePath, mid, sid, tid) {
322
359
  const slicePath = resolveSlicePath(basePath, mid, sid);
323
360
  if (!slicePath)
@@ -70,10 +70,11 @@ export const UNIT_REGISTRY = {
70
70
  "gsd_milestone_status",
71
71
  "gsd_plan_milestone",
72
72
  "gsd_plan_slice",
73
+ "gsd_plan_task",
73
74
  "gsd_decision_save",
74
75
  "gsd_requirement_update",
75
76
  ],
76
- requiredWorkflowTools: ["gsd_milestone_status", "gsd_plan_milestone", "gsd_plan_slice"],
77
+ requiredWorkflowTools: ["gsd_milestone_status", "gsd_plan_milestone", "gsd_plan_slice", "gsd_plan_task"],
77
78
  },
78
79
  },
79
80
  "discuss-milestone": {
@@ -172,13 +173,14 @@ export const UNIT_REGISTRY = {
172
173
  allowedGsdTools: [
173
174
  "gsd_milestone_status",
174
175
  "gsd_plan_slice",
176
+ "gsd_plan_task",
175
177
  "gsd_reassess_roadmap",
176
178
  "gsd_decision_save",
177
179
  "gsd_exec",
178
180
  "gsd_exec_search",
179
181
  "gsd_resume",
180
182
  ],
181
- requiredWorkflowTools: ["gsd_plan_slice", "gsd_reassess_roadmap"],
183
+ requiredWorkflowTools: ["gsd_plan_slice", "gsd_plan_task", "gsd_reassess_roadmap"],
182
184
  },
183
185
  },
184
186
  "refine-slice": {
@@ -201,6 +201,13 @@ export function formatFailureContext(result) {
201
201
  }
202
202
  return header + body;
203
203
  }
204
+ export function formatFailureSignature(result) {
205
+ return result.checks
206
+ .filter((check) => check.exitCode !== 0)
207
+ .map((check) => `${check.command.trim()}#${check.exitCode}`)
208
+ .sort()
209
+ .join("\n");
210
+ }
204
211
  // ─── Gate Execution ─────────────────────────────────────────────────────────
205
212
  /** Characters that indicate shell control syntax when unquoted in a command string. */
206
213
  const UNQUOTED_SHELL_CONTROL_CHARS = new Set([";", "<", ">"]);
@@ -1,11 +1,12 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: State manifest snapshot and restore orchestration for GSD workflow data.
3
3
  import { _getAdapter, readTransaction, restoreManifest, } from "./gsd-db.js";
4
- import { atomicWriteSync } from "./atomic-write.js";
4
+ import { atomicWriteAsync, atomicWriteSync } from "./atomic-write.js";
5
5
  import { getAllDecisionsFromMemories } from "./context-store.js";
6
6
  import { backfillDecisionsToMemories } from "./memory-backfill.js";
7
7
  import { invalidateAllCaches } from "./cache.js";
8
- import { readFileSync, existsSync, mkdirSync } from "node:fs";
8
+ import { logWarning } from "./workflow-logger.js";
9
+ import { readFileSync, existsSync } from "node:fs";
9
10
  import { isAbsolute, join, relative, resolve } from "node:path";
10
11
  // ─── helpers ─────────────────────────────────────────────────────────────
11
12
  function requireDb() {
@@ -257,22 +258,131 @@ export function snapshotState() {
257
258
  return result;
258
259
  });
259
260
  }
260
- // ─── restore ─────────────────────────────────────────────────────────────
261
- //
262
- // The actual restore() implementation lives in gsd-db.ts (single-writer
263
- // invariant). This module only orchestrates reading the manifest file
264
- // and handing it to the writer.
265
- // ─── writeManifest ───────────────────────────────────────────────────────
261
+ const manifestWrites = new Map();
262
+ let processTeardownFlushInstalled = false;
263
+ let beforeExitFlushStarted = false;
264
+ function manifestWriteKey(basePath) {
265
+ return resolve(basePath);
266
+ }
267
+ function manifestFilePath(basePath) {
268
+ return join(resolve(basePath), ".gsd", "state-manifest.json");
269
+ }
270
+ function describeError(err) {
271
+ return err instanceof Error ? err.message : String(err);
272
+ }
273
+ function logManifestWriteFailure(filePath, err) {
274
+ logWarning("manifest", `state manifest write failed: ${describeError(err)}`, { file: filePath });
275
+ }
276
+ async function drainManifestWrites(key, state) {
277
+ let lastWriteError = null;
278
+ try {
279
+ while (state.latestJson !== null && state.syncFlushJson === null) {
280
+ const json = state.latestJson;
281
+ state.latestJson = null;
282
+ state.inFlightJson = json;
283
+ try {
284
+ await atomicWriteAsync(state.filePath, json);
285
+ if (state.syncFlushJson !== null) {
286
+ atomicWriteSync(state.filePath, state.syncFlushJson);
287
+ break;
288
+ }
289
+ lastWriteError = null;
290
+ }
291
+ catch (err) {
292
+ lastWriteError = err;
293
+ logManifestWriteFailure(state.filePath, err);
294
+ }
295
+ finally {
296
+ state.inFlightJson = null;
297
+ }
298
+ }
299
+ }
300
+ finally {
301
+ state.active = null;
302
+ if (state.latestJson === null && state.inFlightJson === null) {
303
+ manifestWrites.delete(key);
304
+ }
305
+ }
306
+ if (lastWriteError !== null && state.syncFlushJson === null) {
307
+ throw lastWriteError;
308
+ }
309
+ }
310
+ function enqueueManifestWrite(basePath, json) {
311
+ const key = manifestWriteKey(basePath);
312
+ let state = manifestWrites.get(key);
313
+ if (!state) {
314
+ state = {
315
+ filePath: manifestFilePath(basePath),
316
+ latestJson: null,
317
+ inFlightJson: null,
318
+ syncFlushJson: null,
319
+ active: null,
320
+ };
321
+ manifestWrites.set(key, state);
322
+ }
323
+ state.latestJson = json;
324
+ if (!state.active) {
325
+ const active = drainManifestWrites(key, state);
326
+ void active.catch(() => { });
327
+ state.active = active;
328
+ }
329
+ }
266
330
  /**
267
- * Write current DB state to .gsd/state-manifest.json via atomicWriteSync.
331
+ * Queue current DB state for an async atomic write to .gsd/state-manifest.json.
268
332
  * Uses JSON.stringify with 2-space indent for git three-way merge friendliness.
269
333
  */
270
334
  export function writeManifest(basePath) {
271
335
  const manifest = snapshotState();
272
336
  const json = JSON.stringify(manifest, null, 2);
273
- const dir = join(basePath, ".gsd");
274
- mkdirSync(dir, { recursive: true });
275
- atomicWriteSync(join(dir, "state-manifest.json"), json);
337
+ enqueueManifestWrite(basePath, json);
338
+ }
339
+ async function flushManifestByKey(key) {
340
+ for (;;) {
341
+ const active = manifestWrites.get(key)?.active;
342
+ if (!active)
343
+ return;
344
+ await active;
345
+ }
346
+ }
347
+ /**
348
+ * Wait for any queued manifest write for this base path to become durable.
349
+ */
350
+ export async function flushManifest(basePath) {
351
+ await flushManifestByKey(manifestWriteKey(basePath));
352
+ }
353
+ export async function flushAllManifests() {
354
+ await Promise.all(Array.from(manifestWrites.keys(), key => flushManifestByKey(key)));
355
+ }
356
+ function flushAllManifestsSync() {
357
+ for (const [key, state] of manifestWrites) {
358
+ const json = state.latestJson ?? state.inFlightJson;
359
+ if (json === null)
360
+ continue;
361
+ try {
362
+ atomicWriteSync(state.filePath, json);
363
+ state.latestJson = null;
364
+ state.inFlightJson = null;
365
+ state.syncFlushJson = json;
366
+ manifestWrites.delete(key);
367
+ }
368
+ catch (err) {
369
+ logManifestWriteFailure(state.filePath, err);
370
+ }
371
+ }
372
+ }
373
+ export function installManifestFlushOnProcessTeardown() {
374
+ if (processTeardownFlushInstalled)
375
+ return;
376
+ processTeardownFlushInstalled = true;
377
+ process.once("beforeExit", () => {
378
+ if (beforeExitFlushStarted)
379
+ return;
380
+ beforeExitFlushStarted = true;
381
+ void flushAllManifests();
382
+ });
383
+ process.once("exit", () => {
384
+ flushAllManifestsSync();
385
+ });
276
386
  }
277
387
  // ─── readManifest ────────────────────────────────────────────────────────
278
388
  /**
@@ -317,7 +427,10 @@ function restoreArtifactProjections(basePath, manifest) {
317
427
  if (manifest.artifacts === undefined)
318
428
  return;
319
429
  for (const artifact of manifest.artifacts) {
320
- atomicWriteSync(artifactProjectionPath(basePath, artifact.path), artifact.full_content ?? "");
430
+ const content = artifact.full_content ?? "";
431
+ if (content === "")
432
+ continue;
433
+ atomicWriteSync(artifactProjectionPath(basePath, artifact.path), content);
321
434
  }
322
435
  }
323
436
  // ─── bootstrapFromManifest ──────────────────────────────────────────────