@opengsd/gsd-pi 1.1.1-dev.9f86580 → 1.1.1-dev.b2556262
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.
- package/dist/headless-recover.js +56 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/index.js +39 -22
- package/dist/resources/extensions/browser-tools/state.js +12 -0
- package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
- package/dist/resources/extensions/browser-tools/utils.js +3 -3
- package/dist/resources/extensions/gsd/auto/loop.js +4 -2
- package/dist/resources/extensions/gsd/auto/phases.js +43 -10
- package/dist/resources/extensions/gsd/auto/session.js +20 -1
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +72 -12
- package/dist/resources/extensions/gsd/auto-model-selection.js +128 -9
- package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +24 -19
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +1 -1
- package/dist/resources/extensions/gsd/auto.js +14 -11
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +172 -65
- package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
- package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
- package/dist/resources/extensions/gsd/db-writer.js +35 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/dist/resources/extensions/gsd/gsd-db.js +480 -172
- package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
- package/dist/resources/extensions/gsd/md-importer.js +38 -3
- package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
- package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
- package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
- package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
- package/dist/resources/extensions/gsd/preferences-models.js +110 -43
- package/dist/resources/extensions/gsd/preferences-types.js +13 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
- package/dist/resources/extensions/gsd/preferences.js +4 -1
- package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
- package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
- package/dist/resources/extensions/gsd/source-observations.js +306 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
- package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
- package/dist/resources/extensions/gsd/state.js +7 -3
- package/dist/resources/extensions/gsd/tool-contract.js +14 -0
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +1 -9
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -6
- package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
- package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +57 -429
- package/dist/resources/extensions/gsd/uat-policy.js +130 -0
- package/dist/resources/extensions/gsd/uat-run.js +414 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
- package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
- package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
- package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
- package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
- package/dist/resources/extensions/subagent/agents.js +1 -0
- package/dist/resources/extensions/subagent/index.js +27 -12
- package/dist/resources/extensions/subagent/launch.js +7 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/package.json +4 -4
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/dist/native.js +22 -0
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +30 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +23 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +25 -24
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
- package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/utils.d.ts +11 -0
- package/packages/pi-tui/dist/utils.d.ts.map +1 -1
- package/packages/pi-tui/dist/utils.js +119 -6
- package/packages/pi-tui/dist/utils.js.map +1 -1
- package/packages/pi-tui/package.json +2 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/themes.js +1 -1
- package/pkg/dist/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +39 -22
- package/src/resources/extensions/browser-tools/state.ts +13 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
- package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
- package/src/resources/extensions/browser-tools/utils.ts +3 -3
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +4 -2
- package/src/resources/extensions/gsd/auto/phases.ts +42 -10
- package/src/resources/extensions/gsd/auto/session.ts +22 -1
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +85 -12
- package/src/resources/extensions/gsd/auto-model-selection.ts +164 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +23 -20
- package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
- package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
- package/src/resources/extensions/gsd/auto-start.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +13 -10
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +225 -72
- package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
- package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
- package/src/resources/extensions/gsd/db-writer.ts +38 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/src/resources/extensions/gsd/gsd-db.ts +564 -186
- package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
- package/src/resources/extensions/gsd/md-importer.ts +49 -2
- package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
- package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
- package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
- package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
- package/src/resources/extensions/gsd/preferences-models.ts +112 -43
- package/src/resources/extensions/gsd/preferences-types.ts +39 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
- package/src/resources/extensions/gsd/preferences.ts +5 -0
- package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
- package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
- package/src/resources/extensions/gsd/source-observations.ts +402 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
- package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
- package/src/resources/extensions/gsd/state.ts +7 -4
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +16 -2
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
- package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +73 -6
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
- package/src/resources/extensions/gsd/tool-contract.ts +28 -0
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +1 -11
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -6
- package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
- package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +66 -526
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/uat-policy.ts +191 -0
- package/src/resources/extensions/gsd/uat-run.ts +550 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
- package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
- package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
- package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
- package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
- package/src/resources/extensions/subagent/agents.ts +4 -0
- package/src/resources/extensions/subagent/index.ts +28 -3
- package/src/resources/extensions/subagent/launch.ts +8 -0
- package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
- /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
|
@@ -207,6 +207,7 @@ test("runFinalize merges a verified complete-milestone immediately and only once
|
|
|
207
207
|
const startedAt = Date.now();
|
|
208
208
|
let lifecycleMergeCalls = 0;
|
|
209
209
|
let resolverMergeCalls = 0;
|
|
210
|
+
const stopAutoCalls: Array<{ reason?: string; options?: unknown }> = [];
|
|
210
211
|
s.basePath = base;
|
|
211
212
|
s.originalBasePath = base;
|
|
212
213
|
s.currentMilestoneId = "M001";
|
|
@@ -219,6 +220,9 @@ test("runFinalize merges a verified complete-milestone immediately and only once
|
|
|
219
220
|
const result = await runFinalizeWithDeps(s, {
|
|
220
221
|
preflightCleanRoot: () => ({ stashPushed: false }),
|
|
221
222
|
postflightPopStash: () => ({ needsManualRecovery: false }),
|
|
223
|
+
stopAuto: async (_ctx: unknown, _pi: unknown, reason?: string, options?: unknown) => {
|
|
224
|
+
stopAutoCalls.push({ reason, options });
|
|
225
|
+
},
|
|
222
226
|
resolver: {
|
|
223
227
|
mergeAndExit() {
|
|
224
228
|
resolverMergeCalls++;
|
|
@@ -232,10 +236,19 @@ test("runFinalize merges a verified complete-milestone immediately and only once
|
|
|
232
236
|
},
|
|
233
237
|
});
|
|
234
238
|
|
|
235
|
-
assert.equal(result.action, "
|
|
239
|
+
assert.equal(result.action, "break");
|
|
240
|
+
assert.equal(result.reason, "milestone-complete");
|
|
236
241
|
assert.equal(lifecycleMergeCalls, 1);
|
|
237
242
|
assert.equal(resolverMergeCalls, 0);
|
|
238
243
|
assert.equal(s.milestoneMergedInPhases, true);
|
|
244
|
+
assert.equal(stopAutoCalls.length, 1);
|
|
245
|
+
assert.equal(stopAutoCalls[0]?.reason, "Milestone M001 complete");
|
|
246
|
+
assert.deepEqual(stopAutoCalls[0]?.options, {
|
|
247
|
+
completionWidget: {
|
|
248
|
+
milestoneId: "M001",
|
|
249
|
+
milestoneTitle: "Milestone",
|
|
250
|
+
},
|
|
251
|
+
});
|
|
239
252
|
|
|
240
253
|
s.currentUnit = {
|
|
241
254
|
type: "complete-milestone",
|
|
@@ -245,6 +258,9 @@ test("runFinalize merges a verified complete-milestone immediately and only once
|
|
|
245
258
|
const second = await runFinalizeWithDeps(s, {
|
|
246
259
|
preflightCleanRoot: () => ({ stashPushed: false }),
|
|
247
260
|
postflightPopStash: () => ({ needsManualRecovery: false }),
|
|
261
|
+
stopAuto: async (_ctx: unknown, _pi: unknown, reason?: string, options?: unknown) => {
|
|
262
|
+
stopAutoCalls.push({ reason, options });
|
|
263
|
+
},
|
|
248
264
|
resolver: {
|
|
249
265
|
mergeAndExit() {
|
|
250
266
|
resolverMergeCalls++;
|
|
@@ -258,9 +274,11 @@ test("runFinalize merges a verified complete-milestone immediately and only once
|
|
|
258
274
|
},
|
|
259
275
|
});
|
|
260
276
|
|
|
261
|
-
assert.equal(second.action, "
|
|
277
|
+
assert.equal(second.action, "break");
|
|
278
|
+
assert.equal(second.reason, "milestone-complete");
|
|
262
279
|
assert.equal(lifecycleMergeCalls, 1);
|
|
263
280
|
assert.equal(resolverMergeCalls, 0);
|
|
281
|
+
assert.equal(stopAutoCalls.length, 2);
|
|
264
282
|
});
|
|
265
283
|
|
|
266
284
|
test("runFinalize does not render next-phase handoff for complete-milestone", async (t) => {
|
|
@@ -302,7 +320,7 @@ test("runFinalize does not render next-phase handoff for complete-milestone", as
|
|
|
302
320
|
},
|
|
303
321
|
);
|
|
304
322
|
|
|
305
|
-
assert.equal(result.action, "
|
|
323
|
+
assert.equal(result.action, "break");
|
|
306
324
|
assert.equal(
|
|
307
325
|
widgetCalls.some(([key]) => key === "gsd-outcome"),
|
|
308
326
|
false,
|
|
@@ -310,6 +328,60 @@ test("runFinalize does not render next-phase handoff for complete-milestone", as
|
|
|
310
328
|
);
|
|
311
329
|
});
|
|
312
330
|
|
|
331
|
+
test("runFinalize clears gsd-step and gsd-progress before stopAuto on complete-milestone", async (t) => {
|
|
332
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-finalize-stale-widget-"));
|
|
333
|
+
t.after(() => {
|
|
334
|
+
rmSync(base, { recursive: true, force: true });
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const s = new AutoSession();
|
|
338
|
+
s.basePath = base;
|
|
339
|
+
s.originalBasePath = base;
|
|
340
|
+
s.currentMilestoneId = "M001";
|
|
341
|
+
s.currentUnit = {
|
|
342
|
+
type: "complete-milestone",
|
|
343
|
+
id: "M001",
|
|
344
|
+
startedAt: Date.now(),
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const statusCalls: Array<[string, unknown]> = [];
|
|
348
|
+
const widgetCalls: Array<[string, unknown]> = [];
|
|
349
|
+
|
|
350
|
+
await runFinalizeWithDeps(
|
|
351
|
+
s,
|
|
352
|
+
{
|
|
353
|
+
preflightCleanRoot: () => ({ stashPushed: false }),
|
|
354
|
+
postflightPopStash: () => ({ needsManualRecovery: false }),
|
|
355
|
+
lifecycle: {
|
|
356
|
+
exitMilestone() {
|
|
357
|
+
return { ok: true, merged: true, codeFilesChanged: false };
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
hasUI: true,
|
|
363
|
+
ui: {
|
|
364
|
+
notify() {},
|
|
365
|
+
setStatus(key: string, value: unknown) {
|
|
366
|
+
statusCalls.push([key, value]);
|
|
367
|
+
},
|
|
368
|
+
setWidget(key: string, value: unknown) {
|
|
369
|
+
widgetCalls.push([key, value]);
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
assert.ok(
|
|
376
|
+
statusCalls.some(([key, val]) => key === "gsd-step" && val === undefined),
|
|
377
|
+
"gsd-step status should be cleared before stopAuto",
|
|
378
|
+
);
|
|
379
|
+
assert.ok(
|
|
380
|
+
widgetCalls.some(([key, val]) => key === "gsd-progress" && val === undefined),
|
|
381
|
+
"gsd-progress widget should be cleared before stopAuto",
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
|
|
313
385
|
test("runFinalize stops before merge when an isolated unit leaks app files into project root", async (t) => {
|
|
314
386
|
const root = mkdtempSync(join(tmpdir(), "gsd-root-leak-root-"));
|
|
315
387
|
const worktree = join(root, ".gsd", "worktrees", "M001");
|
|
@@ -9,7 +9,7 @@ import { randomUUID } from "node:crypto";
|
|
|
9
9
|
|
|
10
10
|
import { verifyExpectedArtifact, hasImplementationArtifacts, resolveExpectedArtifactPath, diagnoseExpectedArtifact, diagnoseWorktreeIntegrityFailure, buildLoopRemediationSteps, writeBlockerPlaceholder, refreshRecoveryDbForArtifact, writeReactiveExecuteBlocker } from "../auto-recovery.ts";
|
|
11
11
|
import { resolveMilestoneFile } from "../paths.ts";
|
|
12
|
-
import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertGateRow, insertTask, insertAssessment, getMilestone, getMilestoneCommitAttributionShas, getTask } from "../gsd-db.ts";
|
|
12
|
+
import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertGateRow, insertTask, insertAssessment, getMilestone, getMilestoneCommitAttributionShas, getTask, saveGateResult } from "../gsd-db.ts";
|
|
13
13
|
import { readEvents } from "../workflow-events.ts";
|
|
14
14
|
import { clearParseCache } from "../files.ts";
|
|
15
15
|
import { parseRoadmap } from "../parsers-legacy.ts";
|
|
@@ -1613,6 +1613,27 @@ test("verifyExpectedArtifact checks pending gate-evaluate artifacts without ESM
|
|
|
1613
1613
|
assert.equal(verified, false, "pending gates should keep gate-evaluate unverified");
|
|
1614
1614
|
});
|
|
1615
1615
|
|
|
1616
|
+
test("verifyExpectedArtifact fails closed for gate-evaluate when the DB is unavailable", () => {
|
|
1617
|
+
const base = makeTmpProject();
|
|
1618
|
+
closeDatabase();
|
|
1619
|
+
|
|
1620
|
+
const verified = verifyExpectedArtifact("gate-evaluate", "M001/S01/gates+Q3", base);
|
|
1621
|
+
|
|
1622
|
+
assert.equal(verified, false, "gate-evaluate must verify against the DB-backed gate rows");
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
test("verifyExpectedArtifact ignores complete-slice gates in stale gate-evaluate unit ids", () => {
|
|
1626
|
+
const base = makeTmpProject();
|
|
1627
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
|
|
1628
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q8", scope: "slice" });
|
|
1629
|
+
saveGateResult({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", verdict: "pass", rationale: "OK", findings: "" });
|
|
1630
|
+
saveGateResult({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", verdict: "pass", rationale: "OK", findings: "" });
|
|
1631
|
+
|
|
1632
|
+
const verified = verifyExpectedArtifact("gate-evaluate", "M001/S01/gates+Q3,Q4,Q8", base);
|
|
1633
|
+
|
|
1634
|
+
assert.equal(verified, true, "pending Q8 belongs to complete-slice and must not keep gate-evaluate unverified");
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1616
1637
|
// ─── #4414 regressions ────────────────────────────────────────────────────────
|
|
1617
1638
|
|
|
1618
1639
|
test("#4414: writeBlockerPlaceholder invalidates path cache so dispatch guard sees file", () => {
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
import { _setAutoActiveForTest } from "../auto.ts";
|
|
8
|
+
import { autoSession } from "../auto-runtime-state.js";
|
|
9
|
+
import { registerHooks } from "../bootstrap/register-hooks.ts";
|
|
10
|
+
|
|
11
|
+
type HookHandler = (event: any, ctx?: any) => Promise<any> | any;
|
|
12
|
+
|
|
13
|
+
function createHookHandlers(): Map<string, HookHandler[]> {
|
|
14
|
+
const handlers = new Map<string, HookHandler[]>();
|
|
15
|
+
const pi = {
|
|
16
|
+
on(event: string, handler: HookHandler) {
|
|
17
|
+
const existing = handlers.get(event) ?? [];
|
|
18
|
+
existing.push(handler);
|
|
19
|
+
handlers.set(event, existing);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
registerHooks(pi as any, []);
|
|
24
|
+
return handlers;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function requireHook(handlers: Map<string, HookHandler[]>, event: string): HookHandler {
|
|
28
|
+
const handler = handlers.get(event)?.[0];
|
|
29
|
+
assert.ok(handler, `${event} hook should be registered`);
|
|
30
|
+
return handler;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test("before_provider_request truncates tool results outside auto-mode", async (t) => {
|
|
34
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-before-provider-context-"));
|
|
35
|
+
const gsdHome = join(dir, "home");
|
|
36
|
+
const project = join(dir, "project");
|
|
37
|
+
const previousCwd = process.cwd();
|
|
38
|
+
const previousGsdHome = process.env.GSD_HOME;
|
|
39
|
+
|
|
40
|
+
mkdirSync(join(project, ".gsd"), { recursive: true });
|
|
41
|
+
mkdirSync(gsdHome, { recursive: true });
|
|
42
|
+
writeFileSync(
|
|
43
|
+
join(project, ".gsd", "PREFERENCES.md"),
|
|
44
|
+
[
|
|
45
|
+
"---",
|
|
46
|
+
"version: 1",
|
|
47
|
+
"context_management:",
|
|
48
|
+
" tool_result_max_chars: 200",
|
|
49
|
+
" observation_mask_turns: 1",
|
|
50
|
+
"---",
|
|
51
|
+
"",
|
|
52
|
+
].join("\n"),
|
|
53
|
+
"utf-8",
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
process.env.GSD_HOME = gsdHome;
|
|
57
|
+
process.chdir(project);
|
|
58
|
+
_setAutoActiveForTest(false);
|
|
59
|
+
|
|
60
|
+
t.after(() => {
|
|
61
|
+
_setAutoActiveForTest(false);
|
|
62
|
+
process.chdir(previousCwd);
|
|
63
|
+
if (previousGsdHome === undefined) delete process.env.GSD_HOME;
|
|
64
|
+
else process.env.GSD_HOME = previousGsdHome;
|
|
65
|
+
rmSync(dir, { recursive: true, force: true });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const beforeProviderRequest = requireHook(createHookHandlers(), "before_provider_request");
|
|
69
|
+
const messageText = "m".repeat(250);
|
|
70
|
+
const responsesOutput = "r".repeat(250);
|
|
71
|
+
const payload = {
|
|
72
|
+
messages: [
|
|
73
|
+
{ role: "user", content: [{ type: "text", text: "keep me" }] },
|
|
74
|
+
{
|
|
75
|
+
role: "toolResult",
|
|
76
|
+
toolCallId: "toolu_test",
|
|
77
|
+
toolName: "Read",
|
|
78
|
+
isError: false,
|
|
79
|
+
content: [{ type: "text", text: messageText }],
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
input: [
|
|
83
|
+
{ role: "user", content: [{ type: "input_text", text: "keep me" }] },
|
|
84
|
+
{ type: "function_call_output", call_id: "call_test", output: responsesOutput },
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
await beforeProviderRequest({ payload });
|
|
89
|
+
|
|
90
|
+
const truncatedMessage = (payload.messages[1]?.content as Array<{ text?: string }>)[0]?.text ?? "";
|
|
91
|
+
const truncatedResponsesOutput = String(payload.input[1]?.output ?? "");
|
|
92
|
+
|
|
93
|
+
assert.match(truncatedMessage, /\[truncated\]/);
|
|
94
|
+
assert.match(truncatedResponsesOutput, /\[truncated\]/);
|
|
95
|
+
assert.ok(truncatedMessage.length < messageText.length);
|
|
96
|
+
assert.ok(truncatedResponsesOutput.length < responsesOutput.length);
|
|
97
|
+
assert.doesNotMatch(truncatedMessage, /result masked/);
|
|
98
|
+
assert.doesNotMatch(truncatedResponsesOutput, /result masked/);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("successful shell result clears source context before provider injection", async (t) => {
|
|
102
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-before-provider-source-"));
|
|
103
|
+
const project = join(dir, "project");
|
|
104
|
+
mkdirSync(project, { recursive: true });
|
|
105
|
+
writeFileSync(join(project, "app.ts"), "export const value = 'before';\n");
|
|
106
|
+
|
|
107
|
+
autoSession.reset();
|
|
108
|
+
autoSession.active = true;
|
|
109
|
+
autoSession.basePath = project;
|
|
110
|
+
autoSession.setCurrentUnit({
|
|
111
|
+
type: "execute-task",
|
|
112
|
+
id: "M001/S01/T01",
|
|
113
|
+
startedAt: 123,
|
|
114
|
+
workspaceRoot: project,
|
|
115
|
+
});
|
|
116
|
+
autoSession.sourceObservations.observeRead({ path: "app.ts" });
|
|
117
|
+
|
|
118
|
+
t.after(() => {
|
|
119
|
+
autoSession.reset();
|
|
120
|
+
rmSync(dir, { recursive: true, force: true });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
assert.match(autoSession.sourceObservations.renderActiveBlock() ?? "", /before/);
|
|
124
|
+
|
|
125
|
+
const handlers = createHookHandlers();
|
|
126
|
+
const toolResult = requireHook(handlers, "tool_result");
|
|
127
|
+
const beforeProviderRequest = requireHook(handlers, "before_provider_request");
|
|
128
|
+
|
|
129
|
+
await toolResult({
|
|
130
|
+
toolCallId: "toolu_bash",
|
|
131
|
+
toolName: "bash",
|
|
132
|
+
input: { command: "printf after > app.ts" },
|
|
133
|
+
isError: false,
|
|
134
|
+
result: "ok",
|
|
135
|
+
}, { cwd: project });
|
|
136
|
+
|
|
137
|
+
const payload = {
|
|
138
|
+
messages: [{ role: "user", content: [{ type: "text", text: "continue" }] }],
|
|
139
|
+
};
|
|
140
|
+
await beforeProviderRequest({ payload });
|
|
141
|
+
|
|
142
|
+
assert.equal(autoSession.sourceObservations.renderActiveBlock(), null);
|
|
143
|
+
assert.equal(payload.messages.length, 1);
|
|
144
|
+
assert.doesNotMatch(payload.messages[0].content[0].text, /Source Context Block/);
|
|
145
|
+
});
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
buildCloseoutMenuActions,
|
|
9
9
|
buildIdleMenuSummary,
|
|
10
10
|
getPrimaryCloseoutRecommendation,
|
|
11
|
+
showMilestoneMergeCloseout,
|
|
11
12
|
} from "../closeout-wizard.ts";
|
|
12
13
|
import { buildGsdHomeModel } from "../gsd-command-home.ts";
|
|
13
14
|
import type { GSDState } from "../types.ts";
|
|
@@ -119,3 +120,46 @@ test("/gsd home recommends merge milestone when closeout is pending", () => {
|
|
|
119
120
|
assert.equal(merge?.recommended, true);
|
|
120
121
|
assert.equal(merge?.enabled, true);
|
|
121
122
|
});
|
|
123
|
+
|
|
124
|
+
test("milestone merge closeout clears stale timer controls and installs the closeout outcome", () => {
|
|
125
|
+
const statuses: Array<[string, string | undefined]> = [];
|
|
126
|
+
const widgets: Array<[string, unknown]> = [];
|
|
127
|
+
|
|
128
|
+
showMilestoneMergeCloseout({
|
|
129
|
+
hasUI: true,
|
|
130
|
+
ui: {
|
|
131
|
+
setStatus: (key: string, value: string | undefined) => {
|
|
132
|
+
statuses.push([key, value]);
|
|
133
|
+
},
|
|
134
|
+
setWidget: (key: string, value: unknown) => {
|
|
135
|
+
widgets.push([key, value]);
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
} as any, {
|
|
139
|
+
milestoneId: "M004",
|
|
140
|
+
branch: "milestone/M004",
|
|
141
|
+
integrationBranch: "main",
|
|
142
|
+
files: ["src/app.ts"],
|
|
143
|
+
dirtyOverlap: [],
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
assert.deepEqual(statuses, [
|
|
147
|
+
["gsd-auto", undefined],
|
|
148
|
+
["gsd-step", undefined],
|
|
149
|
+
]);
|
|
150
|
+
assert.ok(
|
|
151
|
+
widgets.some(([key, value]) => key === "gsd-progress" && value === undefined),
|
|
152
|
+
"stale progress/timer widget should be cleared",
|
|
153
|
+
);
|
|
154
|
+
const outcome = widgets.find(([key]) => key === "gsd-outcome")?.[1];
|
|
155
|
+
assert.equal(typeof outcome, "function");
|
|
156
|
+
|
|
157
|
+
const component = (outcome as any)(
|
|
158
|
+
{ requestRender() {} },
|
|
159
|
+
{ fg: (_color: string, text: string) => text, bold: (text: string) => text },
|
|
160
|
+
);
|
|
161
|
+
const rendered = component.render(100).join("\n");
|
|
162
|
+
assert.match(rendered, /Milestone M004 merged/);
|
|
163
|
+
assert.match(rendered, /Review the closeout/);
|
|
164
|
+
assert.doesNotMatch(rendered, /\/gsd auto/);
|
|
165
|
+
});
|
|
@@ -40,6 +40,7 @@ function makeMockCtx(base: string): {
|
|
|
40
40
|
return {
|
|
41
41
|
ctx: {
|
|
42
42
|
cwd: base,
|
|
43
|
+
hasUI: true,
|
|
43
44
|
ui: {
|
|
44
45
|
notify: (message: string, kind: string) => {
|
|
45
46
|
calls.push({ message, kind });
|
|
@@ -217,7 +218,7 @@ test("dispatcher recovers a completed unmerged milestone through complete-milest
|
|
|
217
218
|
const base = makeTempRepo("gsd-dispatch-unmerged-");
|
|
218
219
|
try {
|
|
219
220
|
seedRegisteredCompletedWorktree(base);
|
|
220
|
-
const { ctx, calls } = makeMockCtx(base);
|
|
221
|
+
const { ctx, calls, widgets, statuses } = makeMockCtx(base);
|
|
221
222
|
const { pi, messages } = makeMockPi();
|
|
222
223
|
|
|
223
224
|
await handleGSDCommand("dispatch complete-milestone M008", ctx, pi);
|
|
@@ -231,6 +232,30 @@ test("dispatcher recovers a completed unmerged milestone through complete-milest
|
|
|
231
232
|
calls.some((call) => call.message.includes("Milestone M008 merged to main")),
|
|
232
233
|
"user sees merge recovery success",
|
|
233
234
|
);
|
|
235
|
+
assert.ok(
|
|
236
|
+
calls.some((call) => call.message.includes("Closeout is complete")),
|
|
237
|
+
"merge recovery ends at a closeout-complete message",
|
|
238
|
+
);
|
|
239
|
+
assert.ok(
|
|
240
|
+
calls.every((call) => !call.message.includes("Run /gsd again when ready")),
|
|
241
|
+
"merge recovery must not send the user past the closeout endpoint",
|
|
242
|
+
);
|
|
243
|
+
assert.ok(
|
|
244
|
+
statuses.some(([key, value]) => key === "gsd-auto" && value === undefined),
|
|
245
|
+
"merge recovery clears stale auto status so the run timer stops",
|
|
246
|
+
);
|
|
247
|
+
assert.ok(
|
|
248
|
+
statuses.some(([key, value]) => key === "gsd-step" && value === undefined),
|
|
249
|
+
"merge recovery clears stale step status",
|
|
250
|
+
);
|
|
251
|
+
assert.ok(
|
|
252
|
+
widgets.some(([key, value]) => key === "gsd-progress" && value === undefined),
|
|
253
|
+
"merge recovery clears stale progress/timer controls",
|
|
254
|
+
);
|
|
255
|
+
assert.ok(
|
|
256
|
+
widgets.some(([key, value]) => key === "gsd-outcome" && typeof value === "function"),
|
|
257
|
+
"merge recovery installs a durable closeout outcome",
|
|
258
|
+
);
|
|
234
259
|
assert.equal(readFileSync(join(base, "index.html"), "utf-8"), "<h1>M008</h1>\n");
|
|
235
260
|
assert.equal(git(base, "branch", "--list", "milestone/M008"), "");
|
|
236
261
|
} finally {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
import { validateContent } from "../safety/content-validator.ts";
|
|
8
|
+
|
|
9
|
+
function makeTempFile(content: string): { base: string; path: string } {
|
|
10
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-content-validator-"));
|
|
11
|
+
mkdirSync(base, { recursive: true });
|
|
12
|
+
const path = join(base, "artifact.md");
|
|
13
|
+
writeFileSync(path, content, "utf-8");
|
|
14
|
+
return { base, path };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test("validateContent marks empty milestone roadmaps as blocking", () => {
|
|
18
|
+
const { base, path } = makeTempFile([
|
|
19
|
+
"# M004: Empty roadmap",
|
|
20
|
+
"",
|
|
21
|
+
"## Slices",
|
|
22
|
+
"",
|
|
23
|
+
"_TBD_",
|
|
24
|
+
"",
|
|
25
|
+
].join("\n"));
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const violations = validateContent("plan-milestone", path);
|
|
29
|
+
assert.deepEqual(violations, [{
|
|
30
|
+
severity: "error",
|
|
31
|
+
reason: "Milestone roadmap has 0 slice(s) — expected at least 1",
|
|
32
|
+
}]);
|
|
33
|
+
} finally {
|
|
34
|
+
rmSync(base, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("validateContent accepts checkbox milestone roadmap slices", () => {
|
|
39
|
+
const { base, path } = makeTempFile([
|
|
40
|
+
"# M004: Roadmap",
|
|
41
|
+
"",
|
|
42
|
+
"## Slices",
|
|
43
|
+
"",
|
|
44
|
+
"- [ ] **S01: Browser due dates** `risk:low` `depends:[]`",
|
|
45
|
+
" > After this: due dates are visible.",
|
|
46
|
+
"",
|
|
47
|
+
].join("\n"));
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const violations = validateContent("plan-milestone", path);
|
|
51
|
+
assert.deepEqual(violations, []);
|
|
52
|
+
} finally {
|
|
53
|
+
rmSync(base, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("validateContent marks empty slice plans as blocking", () => {
|
|
58
|
+
const { base, path } = makeTempFile([
|
|
59
|
+
"# S01: Empty slice",
|
|
60
|
+
"",
|
|
61
|
+
"## Tasks",
|
|
62
|
+
"",
|
|
63
|
+
"_TBD_",
|
|
64
|
+
"",
|
|
65
|
+
].join("\n"));
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const violations = validateContent("plan-slice", path);
|
|
69
|
+
assert.equal(violations[0]?.severity, "error");
|
|
70
|
+
assert.equal(violations[0]?.reason, "Slice plan has 0 task(s) — expected at least 1");
|
|
71
|
+
} finally {
|
|
72
|
+
rmSync(base, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { describe, it, afterEach } from "node:test";
|
|
10
10
|
import assert from "node:assert/strict";
|
|
11
|
-
import { mkdtempSync, rmSync,
|
|
11
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { tmpdir } from "node:os";
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ import type { LoopDeps } from "../auto/loop-deps.js";
|
|
|
18
18
|
import { WorktreeStateProjection } from "../worktree-state-projection.js";
|
|
19
19
|
import type { SessionLockStatus } from "../session-lock.js";
|
|
20
20
|
import { writeGraph, readGraph, type WorkflowGraph, type GraphStep } from "../graph.ts";
|
|
21
|
-
import {
|
|
21
|
+
import { SourceObservationStore } from "../source-observations.js";
|
|
22
22
|
import { stringify } from "yaml";
|
|
23
23
|
|
|
24
24
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
@@ -115,6 +115,7 @@ function makeLoopSession(overrides?: Record<string, unknown>) {
|
|
|
115
115
|
currentMilestoneId: null,
|
|
116
116
|
currentUnit: null,
|
|
117
117
|
currentUnitRouting: null,
|
|
118
|
+
sourceObservations: new SourceObservationStore(),
|
|
118
119
|
completedUnits: [],
|
|
119
120
|
resourceVersionOnStart: null,
|
|
120
121
|
lastPromptCharCount: undefined,
|
|
@@ -139,6 +140,19 @@ function makeLoopSession(overrides?: Record<string, unknown>) {
|
|
|
139
140
|
newSession: () => Promise.resolve({ cancelled: false }),
|
|
140
141
|
getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
|
|
141
142
|
},
|
|
143
|
+
setCurrentUnit(this: any, unit: any) {
|
|
144
|
+
this.currentUnit = unit;
|
|
145
|
+
this.sourceObservations.beginUnit({
|
|
146
|
+
unitType: unit.type,
|
|
147
|
+
unitId: unit.id,
|
|
148
|
+
startedAt: unit.startedAt,
|
|
149
|
+
basePath: unit.workspaceRoot ?? this.basePath,
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
clearCurrentUnit(this: any) {
|
|
153
|
+
this.currentUnit = null;
|
|
154
|
+
this.sourceObservations.clear();
|
|
155
|
+
},
|
|
142
156
|
clearTimers: () => {},
|
|
143
157
|
lockBasePath: "/tmp/project",
|
|
144
158
|
...overrides,
|
|
@@ -109,22 +109,12 @@ test("checkEngineHealth treats explicit reopen as authoritative when dispatch ti
|
|
|
109
109
|
worker_id, host, pid, started_at, version, last_heartbeat_at, status, project_root_realpath
|
|
110
110
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
111
111
|
).run("worker-1", "localhost", 1, "2026-01-01T00:00:00.000Z", "test", "2026-01-01T00:00:00.000Z", "stopped", base);
|
|
112
|
-
db.exec("PRAGMA writable_schema = ON");
|
|
113
112
|
db.prepare(
|
|
114
|
-
`UPDATE sqlite_schema
|
|
115
|
-
SET sql = replace(sql, 'started_at TEXT NOT NULL', 'started_at TEXT')
|
|
116
|
-
WHERE type = 'table' AND name = 'unit_dispatches'`,
|
|
117
|
-
).run();
|
|
118
|
-
db.exec("PRAGMA writable_schema = OFF");
|
|
119
|
-
closeDatabase();
|
|
120
|
-
openDatabase(join(gsdDir, "gsd.db"));
|
|
121
|
-
const reopenedDb = _getAdapter()!;
|
|
122
|
-
reopenedDb.prepare(
|
|
123
113
|
`INSERT INTO unit_dispatches (
|
|
124
114
|
trace_id, worker_id, milestone_lease_token, milestone_id,
|
|
125
115
|
unit_type, unit_id, status, attempt_n, started_at, ended_at
|
|
126
116
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
127
|
-
).run("trace-1", "worker-1", 1, "M001", "complete-milestone", "M001", "completed", 1,
|
|
117
|
+
).run("trace-1", "worker-1", 1, "M001", "complete-milestone", "M001", "completed", 1, "", "");
|
|
128
118
|
appendEvent(base, {
|
|
129
119
|
cmd: "reopen-milestone",
|
|
130
120
|
params: { milestoneId: "M001" },
|
|
@@ -19,10 +19,12 @@ import {
|
|
|
19
19
|
saveGateResult,
|
|
20
20
|
markAllGatesOmitted,
|
|
21
21
|
getPendingSliceGateCount,
|
|
22
|
+
getGateResults,
|
|
22
23
|
} from "../gsd-db.ts";
|
|
23
24
|
import { deriveState, invalidateStateCache } from "../state.ts";
|
|
24
25
|
import { renderPlanFromDb } from "../markdown-renderer.ts";
|
|
25
26
|
import { invalidateAllCaches } from "../cache.ts";
|
|
27
|
+
import { DISPATCH_RULES } from "../auto-dispatch.ts";
|
|
26
28
|
|
|
27
29
|
function setupTestProject(): { tmpDir: string; dbPath: string } {
|
|
28
30
|
const tmpDir = mkdtempSync(join(tmpdir(), "gate-dispatch-"));
|
|
@@ -213,4 +215,66 @@ describe("evaluating-gates phase", () => {
|
|
|
213
215
|
`pending Q8 must not stall evaluating-gates — got phase=${state.phase}`,
|
|
214
216
|
);
|
|
215
217
|
});
|
|
218
|
+
|
|
219
|
+
test("gate-evaluate dispatch id includes only gate-evaluate-owned gates", async () => {
|
|
220
|
+
planSlice(tmpDir);
|
|
221
|
+
await renderPlanFromDb(tmpDir, "M001", "S01");
|
|
222
|
+
|
|
223
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
|
|
224
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
|
|
225
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q8", scope: "slice" });
|
|
226
|
+
|
|
227
|
+
invalidateStateCache();
|
|
228
|
+
const state = await deriveState(tmpDir);
|
|
229
|
+
assert.equal(state.phase, "evaluating-gates");
|
|
230
|
+
|
|
231
|
+
const rule = DISPATCH_RULES.find((candidate) => candidate.name === "evaluating-gates → gate-evaluate");
|
|
232
|
+
assert.ok(rule, "gate-evaluate dispatch rule must exist");
|
|
233
|
+
|
|
234
|
+
const result = await rule.match({
|
|
235
|
+
basePath: tmpDir,
|
|
236
|
+
mid: "M001",
|
|
237
|
+
midTitle: "Test Milestone",
|
|
238
|
+
state,
|
|
239
|
+
prefs: { gate_evaluation: { enabled: true } },
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
assert.ok(result);
|
|
243
|
+
assert.equal(result.action, "dispatch");
|
|
244
|
+
if (result.action !== "dispatch") throw new Error("expected gate-evaluate dispatch");
|
|
245
|
+
assert.equal(result.unitId, "M001/S01/gates+Q3,Q4");
|
|
246
|
+
assert.doesNotMatch(result.prompt, /\*\*Q8\*\*/);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("disabled gate evaluation only omits gate-evaluate-owned gates", async () => {
|
|
250
|
+
planSlice(tmpDir);
|
|
251
|
+
await renderPlanFromDb(tmpDir, "M001", "S01");
|
|
252
|
+
|
|
253
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
|
|
254
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
|
|
255
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q8", scope: "slice" });
|
|
256
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
|
|
257
|
+
|
|
258
|
+
invalidateStateCache();
|
|
259
|
+
const state = await deriveState(tmpDir);
|
|
260
|
+
assert.equal(state.phase, "evaluating-gates");
|
|
261
|
+
|
|
262
|
+
const rule = DISPATCH_RULES.find((candidate) => candidate.name === "evaluating-gates → gate-evaluate");
|
|
263
|
+
assert.ok(rule, "gate-evaluate dispatch rule must exist");
|
|
264
|
+
|
|
265
|
+
const result = await rule.match({
|
|
266
|
+
basePath: tmpDir,
|
|
267
|
+
mid: "M001",
|
|
268
|
+
midTitle: "Test Milestone",
|
|
269
|
+
state,
|
|
270
|
+
prefs: { gate_evaluation: { enabled: false } },
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
assert.equal(result?.action, "skip");
|
|
274
|
+
const byId = new Map(getGateResults("M001", "S01").map((gate) => [gate.gate_id, gate]));
|
|
275
|
+
assert.equal(byId.get("Q3")?.verdict, "omitted");
|
|
276
|
+
assert.equal(byId.get("Q4")?.verdict, "omitted");
|
|
277
|
+
assert.equal(byId.get("Q8")?.status, "pending");
|
|
278
|
+
assert.equal(byId.get("Q5")?.status, "pending");
|
|
279
|
+
});
|
|
216
280
|
});
|
|
@@ -83,6 +83,21 @@ describe("quality_gates CRUD", () => {
|
|
|
83
83
|
assert.ok(results[0].evaluated_at);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
+
test("saveGateResult throws when the gate row does not exist", () => {
|
|
87
|
+
assert.throws(
|
|
88
|
+
() => saveGateResult({
|
|
89
|
+
milestoneId: "M001",
|
|
90
|
+
sliceId: "S01",
|
|
91
|
+
gateId: "Q3",
|
|
92
|
+
verdict: "pass",
|
|
93
|
+
rationale: "No row exists.",
|
|
94
|
+
findings: "",
|
|
95
|
+
}),
|
|
96
|
+
/quality gate row not found/,
|
|
97
|
+
);
|
|
98
|
+
assert.equal(getGateResults("M001", "S01").length, 0);
|
|
99
|
+
});
|
|
100
|
+
|
|
86
101
|
test("getPendingGates filters by scope", () => {
|
|
87
102
|
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
|
|
88
103
|
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
|