@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { test } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
import * as fs from "node:fs";
|
|
4
5
|
import * as path from "node:path";
|
|
5
6
|
import * as os from "node:os";
|
|
@@ -11,9 +12,23 @@ import {
|
|
|
11
12
|
insertRequirement,
|
|
12
13
|
insertArtifact,
|
|
13
14
|
insertMilestone,
|
|
15
|
+
insertSlice,
|
|
16
|
+
insertTask,
|
|
17
|
+
insertMemoryRow,
|
|
18
|
+
insertVerificationEvidence,
|
|
19
|
+
insertAssessment,
|
|
20
|
+
insertGateRow,
|
|
21
|
+
insertReplanHistory,
|
|
22
|
+
saveGateResult,
|
|
23
|
+
recordMilestoneCommitAttribution,
|
|
14
24
|
getMilestone,
|
|
25
|
+
getSlice,
|
|
26
|
+
getTask,
|
|
15
27
|
getDecisionById,
|
|
16
28
|
getRequirementById,
|
|
29
|
+
getVerificationEvidence,
|
|
30
|
+
updateSliceStatus,
|
|
31
|
+
updateTaskStatus,
|
|
17
32
|
_getAdapter,
|
|
18
33
|
copyWorktreeDb,
|
|
19
34
|
reconcileWorktreeDb,
|
|
@@ -62,7 +77,34 @@ function seedMainDb(dbPath: string): void {
|
|
|
62
77
|
});
|
|
63
78
|
}
|
|
64
79
|
|
|
65
|
-
function
|
|
80
|
+
function seedTrackedTask(options: {
|
|
81
|
+
milestoneId?: string;
|
|
82
|
+
sliceId?: string;
|
|
83
|
+
taskId?: string;
|
|
84
|
+
sliceTargets?: string[];
|
|
85
|
+
taskTargets?: string[];
|
|
86
|
+
} = {}): { milestoneId: string; sliceId: string; taskId: string } {
|
|
87
|
+
const milestoneId = options.milestoneId ?? "M-TRACK";
|
|
88
|
+
const sliceId = options.sliceId ?? "S-TRACK";
|
|
89
|
+
const taskId = options.taskId ?? "T-TRACK";
|
|
90
|
+
insertMilestone({ id: milestoneId, title: "Tracked Milestone", status: "active" });
|
|
91
|
+
insertSlice({
|
|
92
|
+
id: sliceId,
|
|
93
|
+
milestoneId,
|
|
94
|
+
title: "Tracked Slice",
|
|
95
|
+
planning: { targetRepositories: options.sliceTargets ?? [] },
|
|
96
|
+
});
|
|
97
|
+
insertTask({
|
|
98
|
+
id: taskId,
|
|
99
|
+
sliceId,
|
|
100
|
+
milestoneId,
|
|
101
|
+
title: "Tracked Task",
|
|
102
|
+
planning: { targetRepositories: options.taskTargets ?? [] },
|
|
103
|
+
});
|
|
104
|
+
return { milestoneId, sliceId, taskId };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function registerCleanup(t: { after: (fn: () => void) => void }, ...dirs: string[]): void {
|
|
66
108
|
t.after(() => {
|
|
67
109
|
closeDatabase();
|
|
68
110
|
for (const dir of dirs) {
|
|
@@ -438,3 +480,471 @@ test("reconcileWorktreeDb does not downgrade milestone status complete→active
|
|
|
438
480
|
assert.ok(m !== null, "milestone M-COMP still exists after reconcile");
|
|
439
481
|
assert.equal(m!.status, "complete", "complete milestone must not be downgraded to active by stale worktree");
|
|
440
482
|
});
|
|
483
|
+
|
|
484
|
+
test("reconcileWorktreeDb does not downgrade completed slices or tasks", (t) => {
|
|
485
|
+
const mainDir = tempDir();
|
|
486
|
+
const wtDir = tempDir();
|
|
487
|
+
registerCleanup(t, mainDir, wtDir);
|
|
488
|
+
|
|
489
|
+
const mainDb = path.join(mainDir, "gsd.db");
|
|
490
|
+
const wtDb = path.join(wtDir, "gsd.db");
|
|
491
|
+
const completedAt = "2026-06-01T00:00:00.000Z";
|
|
492
|
+
|
|
493
|
+
seedMainDb(mainDb);
|
|
494
|
+
const ids = seedTrackedTask({
|
|
495
|
+
milestoneId: "M-COMPLETE-UNITS",
|
|
496
|
+
sliceId: "S-COMPLETE",
|
|
497
|
+
taskId: "T-COMPLETE",
|
|
498
|
+
});
|
|
499
|
+
closeDatabase();
|
|
500
|
+
|
|
501
|
+
copyWorktreeDb(mainDb, wtDb);
|
|
502
|
+
|
|
503
|
+
openDatabase(mainDb);
|
|
504
|
+
updateSliceStatus(ids.milestoneId, ids.sliceId, "complete", completedAt);
|
|
505
|
+
updateTaskStatus(ids.milestoneId, ids.sliceId, ids.taskId, "complete", completedAt);
|
|
506
|
+
closeDatabase();
|
|
507
|
+
|
|
508
|
+
openDatabase(wtDb);
|
|
509
|
+
const wtAdapter = _getAdapter()!;
|
|
510
|
+
wtAdapter.prepare(
|
|
511
|
+
"UPDATE slices SET status = 'active', completed_at = NULL WHERE milestone_id = :mid AND id = :sid",
|
|
512
|
+
).run({ ":mid": ids.milestoneId, ":sid": ids.sliceId });
|
|
513
|
+
wtAdapter.prepare(
|
|
514
|
+
"UPDATE tasks SET status = 'pending', completed_at = NULL WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid",
|
|
515
|
+
).run({ ":mid": ids.milestoneId, ":sid": ids.sliceId, ":tid": ids.taskId });
|
|
516
|
+
closeDatabase();
|
|
517
|
+
|
|
518
|
+
openDatabase(mainDb);
|
|
519
|
+
reconcileWorktreeDb(mainDb, wtDb);
|
|
520
|
+
|
|
521
|
+
const slice = getSlice(ids.milestoneId, ids.sliceId);
|
|
522
|
+
assert.equal(slice?.status, "complete", "complete slice must not be downgraded by stale worktree");
|
|
523
|
+
assert.equal(slice?.completed_at, completedAt, "complete slice timestamp must be preserved");
|
|
524
|
+
|
|
525
|
+
const task = getTask(ids.milestoneId, ids.sliceId, ids.taskId);
|
|
526
|
+
assert.equal(task?.status, "complete", "complete task must not be downgraded by stale worktree");
|
|
527
|
+
assert.equal(task?.completed_at, completedAt, "complete task timestamp must be preserved");
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test("reconcileWorktreeDb merges V29 target_repositories from worktree units", (t) => {
|
|
531
|
+
const mainDir = tempDir();
|
|
532
|
+
const wtDir = tempDir();
|
|
533
|
+
registerCleanup(t, mainDir, wtDir);
|
|
534
|
+
|
|
535
|
+
const mainDb = path.join(mainDir, "gsd.db");
|
|
536
|
+
const wtDb = path.join(wtDir, "gsd.db");
|
|
537
|
+
|
|
538
|
+
seedMainDb(mainDb);
|
|
539
|
+
const ids = seedTrackedTask({
|
|
540
|
+
milestoneId: "M-REPOS",
|
|
541
|
+
sliceId: "S-REPOS",
|
|
542
|
+
taskId: "T-REPOS",
|
|
543
|
+
sliceTargets: ["main-slice"],
|
|
544
|
+
taskTargets: ["main-task"],
|
|
545
|
+
});
|
|
546
|
+
closeDatabase();
|
|
547
|
+
|
|
548
|
+
copyWorktreeDb(mainDb, wtDb);
|
|
549
|
+
|
|
550
|
+
openDatabase(wtDb);
|
|
551
|
+
const wtAdapter = _getAdapter()!;
|
|
552
|
+
wtAdapter.prepare(
|
|
553
|
+
"UPDATE slices SET target_repositories = :targets WHERE milestone_id = :mid AND id = :sid",
|
|
554
|
+
).run({ ":targets": JSON.stringify(["worktree-slice"]), ":mid": ids.milestoneId, ":sid": ids.sliceId });
|
|
555
|
+
wtAdapter.prepare(
|
|
556
|
+
"UPDATE tasks SET target_repositories = :targets WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid",
|
|
557
|
+
).run({ ":targets": JSON.stringify(["worktree-task"]), ":mid": ids.milestoneId, ":sid": ids.sliceId, ":tid": ids.taskId });
|
|
558
|
+
closeDatabase();
|
|
559
|
+
|
|
560
|
+
openDatabase(mainDb);
|
|
561
|
+
reconcileWorktreeDb(mainDb, wtDb);
|
|
562
|
+
|
|
563
|
+
assert.deepEqual(getSlice(ids.milestoneId, ids.sliceId)?.target_repositories, ["worktree-slice"]);
|
|
564
|
+
assert.deepEqual(getTask(ids.milestoneId, ids.sliceId, ids.taskId)?.target_repositories, ["worktree-task"]);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test("reconcileWorktreeDb preserves target_repositories when worktree predates V29", (t) => {
|
|
568
|
+
const mainDir = tempDir();
|
|
569
|
+
const wtDir = tempDir();
|
|
570
|
+
registerCleanup(t, mainDir, wtDir);
|
|
571
|
+
|
|
572
|
+
const mainDb = path.join(mainDir, "gsd.db");
|
|
573
|
+
const wtDb = path.join(wtDir, "gsd.db");
|
|
574
|
+
|
|
575
|
+
seedMainDb(mainDb);
|
|
576
|
+
const ids = seedTrackedTask({
|
|
577
|
+
milestoneId: "M-OLD-REPOS",
|
|
578
|
+
sliceId: "S-OLD-REPOS",
|
|
579
|
+
taskId: "T-OLD-REPOS",
|
|
580
|
+
sliceTargets: ["main-slice"],
|
|
581
|
+
taskTargets: ["main-task"],
|
|
582
|
+
});
|
|
583
|
+
closeDatabase();
|
|
584
|
+
|
|
585
|
+
copyWorktreeDb(mainDb, wtDb);
|
|
586
|
+
openDatabase(wtDb);
|
|
587
|
+
const wtAdapter = _getAdapter()!;
|
|
588
|
+
wtAdapter.exec("ALTER TABLE slices DROP COLUMN target_repositories");
|
|
589
|
+
wtAdapter.exec("ALTER TABLE tasks DROP COLUMN target_repositories");
|
|
590
|
+
closeDatabase();
|
|
591
|
+
|
|
592
|
+
openDatabase(mainDb);
|
|
593
|
+
reconcileWorktreeDb(mainDb, wtDb);
|
|
594
|
+
|
|
595
|
+
assert.deepEqual(getSlice(ids.milestoneId, ids.sliceId)?.target_repositories, ["main-slice"]);
|
|
596
|
+
assert.deepEqual(getTask(ids.milestoneId, ids.sliceId, ids.taskId)?.target_repositories, ["main-task"]);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test("reconcileWorktreeDb preserves target_repositories when migrated old worktree has default empty arrays", (t) => {
|
|
600
|
+
const mainDir = tempDir();
|
|
601
|
+
const wtDir = tempDir();
|
|
602
|
+
registerCleanup(t, mainDir, wtDir);
|
|
603
|
+
|
|
604
|
+
const mainDb = path.join(mainDir, "gsd.db");
|
|
605
|
+
const wtDb = path.join(wtDir, "gsd.db");
|
|
606
|
+
|
|
607
|
+
seedMainDb(mainDb);
|
|
608
|
+
const ids = seedTrackedTask({
|
|
609
|
+
milestoneId: "M-MIGRATED-REPOS",
|
|
610
|
+
sliceId: "S-MIGRATED-REPOS",
|
|
611
|
+
taskId: "T-MIGRATED-REPOS",
|
|
612
|
+
sliceTargets: ["main-slice"],
|
|
613
|
+
taskTargets: ["main-task"],
|
|
614
|
+
});
|
|
615
|
+
closeDatabase();
|
|
616
|
+
|
|
617
|
+
copyWorktreeDb(mainDb, wtDb);
|
|
618
|
+
openDatabase(wtDb);
|
|
619
|
+
const wtAdapter = _getAdapter()!;
|
|
620
|
+
wtAdapter.prepare(
|
|
621
|
+
"UPDATE slices SET target_repositories = '[]' WHERE milestone_id = :mid AND id = :sid",
|
|
622
|
+
).run({ ":mid": ids.milestoneId, ":sid": ids.sliceId });
|
|
623
|
+
wtAdapter.prepare(
|
|
624
|
+
"UPDATE tasks SET target_repositories = '[]' WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid",
|
|
625
|
+
).run({ ":mid": ids.milestoneId, ":sid": ids.sliceId, ":tid": ids.taskId });
|
|
626
|
+
closeDatabase();
|
|
627
|
+
|
|
628
|
+
openDatabase(mainDb);
|
|
629
|
+
reconcileWorktreeDb(mainDb, wtDb);
|
|
630
|
+
|
|
631
|
+
assert.deepEqual(getSlice(ids.milestoneId, ids.sliceId)?.target_repositories, ["main-slice"]);
|
|
632
|
+
assert.deepEqual(getTask(ids.milestoneId, ids.sliceId, ids.taskId)?.target_repositories, ["main-task"]);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test("reconcileWorktreeDb preserves memory metadata when worktree predates memory columns", (t) => {
|
|
636
|
+
const mainDir = tempDir();
|
|
637
|
+
const wtDir = tempDir();
|
|
638
|
+
registerCleanup(t, mainDir, wtDir);
|
|
639
|
+
|
|
640
|
+
const mainDb = path.join(mainDir, "gsd.db");
|
|
641
|
+
const wtDb = path.join(wtDir, "gsd.db");
|
|
642
|
+
const memoryId = "mem-reconcile";
|
|
643
|
+
const createdAt = "2026-06-01T00:00:00.000Z";
|
|
644
|
+
const updatedAt = "2026-06-02T00:00:00.000Z";
|
|
645
|
+
const lastHitAt = "2026-06-03T00:00:00.000Z";
|
|
646
|
+
|
|
647
|
+
seedMainDb(mainDb);
|
|
648
|
+
insertMemoryRow({
|
|
649
|
+
id: memoryId,
|
|
650
|
+
category: "architecture",
|
|
651
|
+
content: "Main memory content",
|
|
652
|
+
confidence: 0.7,
|
|
653
|
+
sourceUnitType: "task",
|
|
654
|
+
sourceUnitId: "T001",
|
|
655
|
+
createdAt,
|
|
656
|
+
updatedAt,
|
|
657
|
+
scope: "workspace",
|
|
658
|
+
tags: ["db", "memory"],
|
|
659
|
+
structuredFields: { sourceDecisionId: "D-MEM" },
|
|
660
|
+
});
|
|
661
|
+
_getAdapter()!.prepare(
|
|
662
|
+
"UPDATE memories SET last_hit_at = :last_hit_at, hit_count = 3 WHERE id = :id",
|
|
663
|
+
).run({ ":last_hit_at": lastHitAt, ":id": memoryId });
|
|
664
|
+
closeDatabase();
|
|
665
|
+
|
|
666
|
+
copyWorktreeDb(mainDb, wtDb);
|
|
667
|
+
openDatabase(wtDb);
|
|
668
|
+
const wtAdapter = _getAdapter()!;
|
|
669
|
+
wtAdapter.exec("DROP INDEX IF EXISTS idx_memories_scope");
|
|
670
|
+
wtAdapter.exec("ALTER TABLE memories DROP COLUMN scope");
|
|
671
|
+
wtAdapter.exec("ALTER TABLE memories DROP COLUMN tags");
|
|
672
|
+
wtAdapter.exec("ALTER TABLE memories DROP COLUMN structured_fields");
|
|
673
|
+
wtAdapter.exec("ALTER TABLE memories DROP COLUMN last_hit_at");
|
|
674
|
+
wtAdapter.prepare(
|
|
675
|
+
"UPDATE memories SET content = :content, confidence = :confidence, updated_at = :updated_at WHERE id = :id",
|
|
676
|
+
).run({
|
|
677
|
+
":content": "Worktree memory content",
|
|
678
|
+
":confidence": 0.9,
|
|
679
|
+
":updated_at": "2026-06-04T00:00:00.000Z",
|
|
680
|
+
":id": memoryId,
|
|
681
|
+
});
|
|
682
|
+
closeDatabase();
|
|
683
|
+
|
|
684
|
+
openDatabase(mainDb);
|
|
685
|
+
reconcileWorktreeDb(mainDb, wtDb);
|
|
686
|
+
|
|
687
|
+
const row = _getAdapter()!.prepare("SELECT * FROM memories WHERE id = :id").get({ ":id": memoryId }) as Record<string, unknown>;
|
|
688
|
+
assert.equal(row["content"], "Worktree memory content");
|
|
689
|
+
assert.equal(row["confidence"], 0.9);
|
|
690
|
+
assert.equal(row["scope"], "workspace");
|
|
691
|
+
assert.equal(row["tags"], JSON.stringify(["db", "memory"]));
|
|
692
|
+
assert.equal(row["structured_fields"], JSON.stringify({ sourceDecisionId: "D-MEM" }));
|
|
693
|
+
assert.equal(row["last_hit_at"], lastHitAt);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
test("reconcileWorktreeDb continues when an older worktree is missing an optional table", (t) => {
|
|
697
|
+
const mainDir = tempDir();
|
|
698
|
+
const wtDir = tempDir();
|
|
699
|
+
registerCleanup(t, mainDir, wtDir);
|
|
700
|
+
|
|
701
|
+
const mainDb = path.join(mainDir, "gsd.db");
|
|
702
|
+
const wtDb = path.join(wtDir, "gsd.db");
|
|
703
|
+
|
|
704
|
+
seedMainDb(mainDb);
|
|
705
|
+
closeDatabase();
|
|
706
|
+
copyWorktreeDb(mainDb, wtDb);
|
|
707
|
+
|
|
708
|
+
openDatabase(wtDb);
|
|
709
|
+
insertDecision({
|
|
710
|
+
id: "D-MISSING-TABLE",
|
|
711
|
+
when_context: "2026-06-01",
|
|
712
|
+
scope: "M001",
|
|
713
|
+
decision: "Merge despite old optional tables",
|
|
714
|
+
choice: "guard table reads",
|
|
715
|
+
rationale: "Old worktrees may lack newer tables",
|
|
716
|
+
revisable: "yes",
|
|
717
|
+
made_by: "agent",
|
|
718
|
+
superseded_by: null,
|
|
719
|
+
});
|
|
720
|
+
_getAdapter()!.exec("DROP TABLE memories");
|
|
721
|
+
closeDatabase();
|
|
722
|
+
|
|
723
|
+
openDatabase(mainDb);
|
|
724
|
+
const result = reconcileWorktreeDb(mainDb, wtDb);
|
|
725
|
+
|
|
726
|
+
assert.equal(result.conflicts.length, 0);
|
|
727
|
+
assert.equal(getDecisionById("D-MISSING-TABLE")?.choice, "guard table reads");
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
test("reconcileWorktreeDb preserves decision seq when replaying worktree decisions", (t) => {
|
|
731
|
+
const mainDir = tempDir();
|
|
732
|
+
const wtDir = tempDir();
|
|
733
|
+
registerCleanup(t, mainDir, wtDir);
|
|
734
|
+
|
|
735
|
+
const mainDb = path.join(mainDir, "gsd.db");
|
|
736
|
+
const wtDb = path.join(wtDir, "gsd.db");
|
|
737
|
+
|
|
738
|
+
seedMainDb(mainDb);
|
|
739
|
+
insertDecision({
|
|
740
|
+
id: "D002",
|
|
741
|
+
when_context: "2026-06-02",
|
|
742
|
+
scope: "M001",
|
|
743
|
+
decision: "Second decision",
|
|
744
|
+
choice: "keep order",
|
|
745
|
+
rationale: "Reconcile must not reset seq",
|
|
746
|
+
revisable: "yes",
|
|
747
|
+
made_by: "agent",
|
|
748
|
+
superseded_by: null,
|
|
749
|
+
});
|
|
750
|
+
const before = _getAdapter()!.prepare("SELECT id, seq FROM decisions ORDER BY seq").all();
|
|
751
|
+
closeDatabase();
|
|
752
|
+
|
|
753
|
+
copyWorktreeDb(mainDb, wtDb);
|
|
754
|
+
|
|
755
|
+
openDatabase(mainDb);
|
|
756
|
+
reconcileWorktreeDb(mainDb, wtDb);
|
|
757
|
+
const after = _getAdapter()!.prepare("SELECT id, seq FROM decisions ORDER BY seq").all();
|
|
758
|
+
|
|
759
|
+
assert.deepEqual(after, before);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
test("reconcileWorktreeDb recomputes artifact hash when worktree predates content_hash", (t) => {
|
|
763
|
+
const mainDir = tempDir();
|
|
764
|
+
const wtDir = tempDir();
|
|
765
|
+
registerCleanup(t, mainDir, wtDir);
|
|
766
|
+
|
|
767
|
+
const mainDb = path.join(mainDir, "gsd.db");
|
|
768
|
+
const wtDb = path.join(wtDir, "gsd.db");
|
|
769
|
+
const artifactPath = "docs/hash.md";
|
|
770
|
+
|
|
771
|
+
seedMainDb(mainDb);
|
|
772
|
+
insertArtifact({
|
|
773
|
+
path: artifactPath,
|
|
774
|
+
artifact_type: "plan",
|
|
775
|
+
milestone_id: "M001",
|
|
776
|
+
slice_id: null,
|
|
777
|
+
task_id: null,
|
|
778
|
+
full_content: "old content",
|
|
779
|
+
});
|
|
780
|
+
closeDatabase();
|
|
781
|
+
|
|
782
|
+
copyWorktreeDb(mainDb, wtDb);
|
|
783
|
+
openDatabase(wtDb);
|
|
784
|
+
const wtAdapter = _getAdapter()!;
|
|
785
|
+
wtAdapter.exec("ALTER TABLE artifacts DROP COLUMN content_hash");
|
|
786
|
+
wtAdapter.prepare("UPDATE artifacts SET full_content = :content WHERE path = :path").run({
|
|
787
|
+
":content": "new content",
|
|
788
|
+
":path": artifactPath,
|
|
789
|
+
});
|
|
790
|
+
closeDatabase();
|
|
791
|
+
|
|
792
|
+
openDatabase(mainDb);
|
|
793
|
+
reconcileWorktreeDb(mainDb, wtDb);
|
|
794
|
+
const row = _getAdapter()!.prepare("SELECT full_content, content_hash FROM artifacts WHERE path = :path").get({
|
|
795
|
+
":path": artifactPath,
|
|
796
|
+
}) as Record<string, unknown>;
|
|
797
|
+
|
|
798
|
+
assert.equal(row["full_content"], "new content");
|
|
799
|
+
assert.equal(row["content_hash"], createHash("sha256").update("new content").digest("hex"));
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
test("reconcileWorktreeDb merges correctness rows from worktree", (t) => {
|
|
803
|
+
const mainDir = tempDir();
|
|
804
|
+
const wtDir = tempDir();
|
|
805
|
+
registerCleanup(t, mainDir, wtDir);
|
|
806
|
+
|
|
807
|
+
const mainDb = path.join(mainDir, "gsd.db");
|
|
808
|
+
const wtDb = path.join(wtDir, "gsd.db");
|
|
809
|
+
|
|
810
|
+
seedMainDb(mainDb);
|
|
811
|
+
const ids = seedTrackedTask({
|
|
812
|
+
milestoneId: "M-CORRECTNESS",
|
|
813
|
+
sliceId: "S-CORRECTNESS",
|
|
814
|
+
taskId: "T-CORRECTNESS",
|
|
815
|
+
});
|
|
816
|
+
closeDatabase();
|
|
817
|
+
|
|
818
|
+
copyWorktreeDb(mainDb, wtDb);
|
|
819
|
+
openDatabase(wtDb);
|
|
820
|
+
const wtAdapter = _getAdapter()!;
|
|
821
|
+
insertSlice({ id: "S-BASE", milestoneId: ids.milestoneId, title: "Base Slice" });
|
|
822
|
+
insertSlice({ id: "S-DEPENDENT", milestoneId: ids.milestoneId, title: "Dependent Slice", depends: ["S-BASE"] });
|
|
823
|
+
wtAdapter.prepare(
|
|
824
|
+
"INSERT INTO slice_dependencies (milestone_id, slice_id, depends_on_slice_id) VALUES (?, ?, ?)",
|
|
825
|
+
).run(ids.milestoneId, "S-DEPENDENT", "S-BASE");
|
|
826
|
+
insertAssessment({
|
|
827
|
+
path: ".gsd/milestones/M-CORRECTNESS/M-CORRECTNESS-VALIDATION.md",
|
|
828
|
+
milestoneId: ids.milestoneId,
|
|
829
|
+
status: "pass",
|
|
830
|
+
scope: "validate-milestone",
|
|
831
|
+
fullContent: "# Validation\n\nPASS",
|
|
832
|
+
});
|
|
833
|
+
insertReplanHistory({
|
|
834
|
+
milestoneId: ids.milestoneId,
|
|
835
|
+
sliceId: ids.sliceId,
|
|
836
|
+
taskId: ids.taskId,
|
|
837
|
+
summary: "Replanned in worktree",
|
|
838
|
+
previousArtifactPath: "old.md",
|
|
839
|
+
replacementArtifactPath: "new.md",
|
|
840
|
+
});
|
|
841
|
+
insertGateRow({ milestoneId: ids.milestoneId, sliceId: ids.sliceId, gateId: "Q3", scope: "slice" });
|
|
842
|
+
saveGateResult({
|
|
843
|
+
milestoneId: ids.milestoneId,
|
|
844
|
+
sliceId: ids.sliceId,
|
|
845
|
+
gateId: "Q3",
|
|
846
|
+
verdict: "pass",
|
|
847
|
+
rationale: "Worktree gate passed",
|
|
848
|
+
findings: "No findings",
|
|
849
|
+
});
|
|
850
|
+
recordMilestoneCommitAttribution({
|
|
851
|
+
commitSha: "abc123",
|
|
852
|
+
milestoneId: ids.milestoneId,
|
|
853
|
+
sliceId: ids.sliceId,
|
|
854
|
+
taskId: ids.taskId,
|
|
855
|
+
source: "recorded",
|
|
856
|
+
confidence: 0.95,
|
|
857
|
+
files: ["src/example.ts"],
|
|
858
|
+
createdAt: "2026-06-05T00:00:00.000Z",
|
|
859
|
+
});
|
|
860
|
+
closeDatabase();
|
|
861
|
+
|
|
862
|
+
openDatabase(mainDb);
|
|
863
|
+
const result = reconcileWorktreeDb(mainDb, wtDb);
|
|
864
|
+
const adapter = _getAdapter()!;
|
|
865
|
+
|
|
866
|
+
assert.ok(result.assessments > 0, "assessment rows should merge");
|
|
867
|
+
assert.ok(result.replan_history > 0, "replan history rows should merge");
|
|
868
|
+
assert.ok(result.quality_gates > 0, "quality gate rows should merge");
|
|
869
|
+
assert.ok(result.slice_dependencies > 0, "slice dependency rows should merge");
|
|
870
|
+
assert.ok(result.gate_runs > 0, "gate run rows should merge");
|
|
871
|
+
assert.ok(result.milestone_commit_attributions > 0, "commit attribution rows should merge");
|
|
872
|
+
assert.equal(
|
|
873
|
+
(adapter.prepare("SELECT count(*) AS n FROM assessments WHERE milestone_id = :mid").get({ ":mid": ids.milestoneId }) as Record<string, unknown>)["n"],
|
|
874
|
+
1,
|
|
875
|
+
);
|
|
876
|
+
assert.equal(
|
|
877
|
+
(adapter.prepare("SELECT count(*) AS n FROM replan_history WHERE summary = 'Replanned in worktree'").get() as Record<string, unknown>)["n"],
|
|
878
|
+
1,
|
|
879
|
+
);
|
|
880
|
+
assert.equal(
|
|
881
|
+
(adapter.prepare("SELECT verdict FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid AND gate_id = 'Q3'").get({
|
|
882
|
+
":mid": ids.milestoneId,
|
|
883
|
+
":sid": ids.sliceId,
|
|
884
|
+
}) as Record<string, unknown>)["verdict"],
|
|
885
|
+
"pass",
|
|
886
|
+
);
|
|
887
|
+
assert.equal(
|
|
888
|
+
(adapter.prepare("SELECT depends_on_slice_id FROM slice_dependencies WHERE milestone_id = :mid AND slice_id = 'S-DEPENDENT'").get({
|
|
889
|
+
":mid": ids.milestoneId,
|
|
890
|
+
}) as Record<string, unknown>)["depends_on_slice_id"],
|
|
891
|
+
"S-BASE",
|
|
892
|
+
);
|
|
893
|
+
assert.equal(
|
|
894
|
+
(adapter.prepare("SELECT count(*) AS n FROM gate_runs WHERE milestone_id = :mid").get({ ":mid": ids.milestoneId }) as Record<string, unknown>)["n"],
|
|
895
|
+
1,
|
|
896
|
+
);
|
|
897
|
+
assert.equal(
|
|
898
|
+
(adapter.prepare("SELECT commit_sha FROM milestone_commit_attributions WHERE milestone_id = :mid").get({
|
|
899
|
+
":mid": ids.milestoneId,
|
|
900
|
+
}) as Record<string, unknown>)["commit_sha"],
|
|
901
|
+
"abc123",
|
|
902
|
+
);
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
test("reconcileWorktreeDb appends new verification evidence without duplicating copied rows", (t) => {
|
|
906
|
+
const mainDir = tempDir();
|
|
907
|
+
const wtDir = tempDir();
|
|
908
|
+
registerCleanup(t, mainDir, wtDir);
|
|
909
|
+
|
|
910
|
+
const mainDb = path.join(mainDir, "gsd.db");
|
|
911
|
+
const wtDb = path.join(wtDir, "gsd.db");
|
|
912
|
+
|
|
913
|
+
seedMainDb(mainDb);
|
|
914
|
+
const ids = seedTrackedTask({
|
|
915
|
+
milestoneId: "M-EVIDENCE",
|
|
916
|
+
sliceId: "S-EVIDENCE",
|
|
917
|
+
taskId: "T-EVIDENCE",
|
|
918
|
+
});
|
|
919
|
+
insertVerificationEvidence({
|
|
920
|
+
taskId: ids.taskId,
|
|
921
|
+
sliceId: ids.sliceId,
|
|
922
|
+
milestoneId: ids.milestoneId,
|
|
923
|
+
command: "pnpm test",
|
|
924
|
+
exitCode: 0,
|
|
925
|
+
verdict: "pass",
|
|
926
|
+
durationMs: 100,
|
|
927
|
+
});
|
|
928
|
+
closeDatabase();
|
|
929
|
+
|
|
930
|
+
copyWorktreeDb(mainDb, wtDb);
|
|
931
|
+
openDatabase(wtDb);
|
|
932
|
+
insertVerificationEvidence({
|
|
933
|
+
taskId: ids.taskId,
|
|
934
|
+
sliceId: ids.sliceId,
|
|
935
|
+
milestoneId: ids.milestoneId,
|
|
936
|
+
command: "pnpm lint",
|
|
937
|
+
exitCode: 0,
|
|
938
|
+
verdict: "pass",
|
|
939
|
+
durationMs: 50,
|
|
940
|
+
});
|
|
941
|
+
closeDatabase();
|
|
942
|
+
|
|
943
|
+
openDatabase(mainDb);
|
|
944
|
+
const result = reconcileWorktreeDb(mainDb, wtDb);
|
|
945
|
+
const evidence = getVerificationEvidence(ids.milestoneId, ids.sliceId, ids.taskId);
|
|
946
|
+
|
|
947
|
+
assert.equal(result.verification_evidence, 1, "only the new evidence row should be appended");
|
|
948
|
+
assert.equal(evidence.length, 2, "copied evidence row should not duplicate during reconcile");
|
|
949
|
+
assert.deepEqual(evidence.map((row) => row.command).sort(), ["pnpm lint", "pnpm test"]);
|
|
950
|
+
});
|
|
@@ -97,6 +97,50 @@ test("projectRootToWorktree forwards root PROJECT.md into isolated worktrees", (
|
|
|
97
97
|
}
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
+
test("projectRootToWorktree projects prior-milestone slice/task SUMMARY.md, not just top-level files", () => {
|
|
101
|
+
// Regression: a worktree opened for the CURRENT milestone (M002) must also
|
|
102
|
+
// receive the full subtree of OTHER, already-completed milestones (M001) so
|
|
103
|
+
// their per-slice/per-task SUMMARY.md exist on the worktree filesystem.
|
|
104
|
+
// Previously only top-level *.md/*.json were projected for other milestones,
|
|
105
|
+
// leaving the stale-render detector to flag M001's summaries as "complete in
|
|
106
|
+
// DB but missing on disk".
|
|
107
|
+
const { dir, cleanup } = makeProjectRoot();
|
|
108
|
+
try {
|
|
109
|
+
const worktree = join(dir, ".gsd", "worktrees", "M002");
|
|
110
|
+
mkdirSync(join(worktree, ".gsd"), { recursive: true });
|
|
111
|
+
|
|
112
|
+
// Current milestone (M002) — what the worktree is working on.
|
|
113
|
+
mkdirSync(join(dir, ".gsd", "milestones", "M002"), { recursive: true });
|
|
114
|
+
writeFileSync(join(dir, ".gsd", "milestones", "M002", "M002-ROADMAP.md"), "# M002\n");
|
|
115
|
+
|
|
116
|
+
// Prior, completed milestone (M001) with nested slice + task summaries.
|
|
117
|
+
const m001TaskDir = join(dir, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
118
|
+
mkdirSync(m001TaskDir, { recursive: true });
|
|
119
|
+
writeFileSync(join(dir, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# M001\n");
|
|
120
|
+
writeFileSync(
|
|
121
|
+
join(dir, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md"),
|
|
122
|
+
"# S01 Summary\n",
|
|
123
|
+
);
|
|
124
|
+
writeFileSync(join(m001TaskDir, "T01-SUMMARY.md"), "# T01 Summary\n");
|
|
125
|
+
|
|
126
|
+
const workspace = createWorkspace(worktree);
|
|
127
|
+
const scope = scopeMilestone(workspace, "M002");
|
|
128
|
+
new WorktreeStateProjection().projectRootToWorktree(scope);
|
|
129
|
+
|
|
130
|
+
const wtGsd = join(worktree, ".gsd");
|
|
131
|
+
assert.ok(
|
|
132
|
+
existsSync(join(wtGsd, "milestones", "M001", "slices", "S01", "S01-SUMMARY.md")),
|
|
133
|
+
"prior-milestone slice SUMMARY.md is projected into the worktree",
|
|
134
|
+
);
|
|
135
|
+
assert.ok(
|
|
136
|
+
existsSync(join(wtGsd, "milestones", "M001", "slices", "S01", "tasks", "T01-SUMMARY.md")),
|
|
137
|
+
"prior-milestone task SUMMARY.md is projected into the worktree",
|
|
138
|
+
);
|
|
139
|
+
} finally {
|
|
140
|
+
cleanup();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
100
144
|
// ─── projectWorktreeToRoot — Module contract ────────────────────────────────
|
|
101
145
|
|
|
102
146
|
test("projectWorktreeToRoot exists and accepts a MilestoneScope", () => {
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
} from "./unit-context-manifest.js";
|
|
10
10
|
import { getRequiredWorkflowToolsForAutoUnit } from "./workflow-mcp.js";
|
|
11
11
|
import { getUnitToolSurfaceContract } from "./unit-tool-contracts.js";
|
|
12
|
+
import {
|
|
13
|
+
WHOLE_FILE_OBSERVATION_MAX_BYTES,
|
|
14
|
+
WHOLE_FILE_OBSERVATION_MAX_LINES,
|
|
15
|
+
} from "./source-observations.js";
|
|
12
16
|
|
|
13
17
|
export interface UnitToolContract {
|
|
14
18
|
unitType: string;
|
|
@@ -19,6 +23,7 @@ export interface UnitToolContract {
|
|
|
19
23
|
promptObligations: readonly string[];
|
|
20
24
|
validationRules: readonly string[];
|
|
21
25
|
closeoutTools: readonly string[];
|
|
26
|
+
sourceObservations: UnitSourceObservationContract;
|
|
22
27
|
artifacts: {
|
|
23
28
|
inline: readonly ArtifactKey[];
|
|
24
29
|
excerpt: readonly ArtifactKey[];
|
|
@@ -26,6 +31,16 @@ export interface UnitToolContract {
|
|
|
26
31
|
};
|
|
27
32
|
}
|
|
28
33
|
|
|
34
|
+
export type UnitSourceObservationContract =
|
|
35
|
+
| { mode: "none" }
|
|
36
|
+
| {
|
|
37
|
+
mode: "whole-file-active-unit";
|
|
38
|
+
seedFields: readonly ["task.files", "task.inputs"];
|
|
39
|
+
excludedFields: readonly ["expectedOutput"];
|
|
40
|
+
maxBytes: number;
|
|
41
|
+
maxLines: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
29
44
|
export type ToolContractResult =
|
|
30
45
|
| { ok: true; contract: UnitToolContract }
|
|
31
46
|
| { ok: false; reason: "unknown-unit-type" | "missing-closeout-tool"; detail: string };
|
|
@@ -72,8 +87,10 @@ export function compileUnitToolContract(unitType: string): ToolContractResult {
|
|
|
72
87
|
"unit-manifest-present",
|
|
73
88
|
"workflow-tool-surface-present",
|
|
74
89
|
...(requiresCloseoutTool(unitType) ? ["closeout-tool-present"] : []),
|
|
90
|
+
...(unitType === "execute-task" ? ["source-observation-contract-present"] : []),
|
|
75
91
|
],
|
|
76
92
|
closeoutTools,
|
|
93
|
+
sourceObservations: sourceObservationContractForUnit(unitType),
|
|
77
94
|
artifacts: {
|
|
78
95
|
inline: manifest.artifacts.inline,
|
|
79
96
|
excerpt: manifest.artifacts.excerpt,
|
|
@@ -83,6 +100,17 @@ export function compileUnitToolContract(unitType: string): ToolContractResult {
|
|
|
83
100
|
};
|
|
84
101
|
}
|
|
85
102
|
|
|
103
|
+
function sourceObservationContractForUnit(unitType: string): UnitSourceObservationContract {
|
|
104
|
+
if (unitType !== "execute-task") return { mode: "none" };
|
|
105
|
+
return {
|
|
106
|
+
mode: "whole-file-active-unit",
|
|
107
|
+
seedFields: ["task.files", "task.inputs"],
|
|
108
|
+
excludedFields: ["expectedOutput"],
|
|
109
|
+
maxBytes: WHOLE_FILE_OBSERVATION_MAX_BYTES,
|
|
110
|
+
maxLines: WHOLE_FILE_OBSERVATION_MAX_LINES,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
86
114
|
function requiresCloseoutTool(unitType: string): boolean {
|
|
87
115
|
return /^(execute-task|reactive-execute|complete-slice|validate-milestone|complete-milestone|run-uat|gate-evaluate)$/.test(unitType);
|
|
88
116
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
RUN_UAT_TOOL_PRESENTATION_PLAN_ID,
|
|
8
8
|
RUN_UAT_WORKFLOW_TOOL_NAMES,
|
|
9
9
|
} from "./unit-tool-contracts.js";
|
|
10
|
+
import { uatTypeIncludesBrowser } from "./uat-policy.js";
|
|
10
11
|
|
|
11
12
|
export {
|
|
12
13
|
RUN_UAT_BROWSER_TOOL_NAMES,
|
|
@@ -131,17 +132,6 @@ export function buildRunUatCanonicalToolNames(options: { includeBrowserTools?: r
|
|
|
131
132
|
// `human-experience` is told to capture screenshots. Without this, a webpage
|
|
132
133
|
// UAT classified as anything but `browser-executable` had no browser tools and
|
|
133
134
|
// downgraded its live checks to NEEDS-HUMAN (M001/S03 regression).
|
|
134
|
-
export const BROWSER_INCLUSIVE_UAT_TYPES: readonly string[] = [
|
|
135
|
-
"browser-executable",
|
|
136
|
-
"live-runtime",
|
|
137
|
-
"mixed",
|
|
138
|
-
"human-experience",
|
|
139
|
-
];
|
|
140
|
-
|
|
141
|
-
function uatTypeIncludesBrowser(uatType: string | undefined): boolean {
|
|
142
|
-
return uatType !== undefined && BROWSER_INCLUSIVE_UAT_TYPES.includes(uatType);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
135
|
export function runUatBrowserToolsForType(uatType: string | undefined): readonly string[] {
|
|
146
136
|
return uatTypeIncludesBrowser(uatType) ? RUN_UAT_BROWSER_TOOL_NAMES : [];
|
|
147
137
|
}
|