gsd-pi 2.44.0-dev.73f2fd5 → 2.44.0-dev.8894d5b
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/resources/extensions/gsd/auto/infra-errors.js +0 -3
- package/dist/resources/extensions/gsd/auto/phases.js +36 -36
- package/dist/resources/extensions/gsd/auto-prompts.js +1 -24
- package/dist/resources/extensions/gsd/auto-timers.js +3 -57
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +0 -4
- package/dist/resources/extensions/gsd/auto-worktree.js +6 -9
- package/dist/resources/extensions/gsd/auto.js +3 -30
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +0 -136
- package/dist/resources/extensions/gsd/commands/catalog.js +1 -6
- package/dist/resources/extensions/gsd/commands/handlers/core.js +0 -1
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +0 -5
- package/dist/resources/extensions/gsd/db-writer.js +16 -34
- package/dist/resources/extensions/gsd/doctor.js +0 -8
- package/dist/resources/extensions/gsd/git-service.js +3 -8
- package/dist/resources/extensions/gsd/gsd-db.js +1 -12
- package/dist/resources/extensions/gsd/markdown-renderer.js +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +4 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +14 -3
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +37 -7
- package/dist/resources/extensions/gsd/provider-error-pause.js +0 -7
- package/dist/resources/extensions/gsd/tools/plan-slice.js +0 -1
- package/dist/resources/extensions/gsd/tools/plan-task.js +0 -1
- package/dist/resources/extensions/gsd/tools/replan-slice.js +0 -2
- package/dist/resources/extensions/gsd/worktree-resolver.js +0 -6
- package/dist/resources/extensions/mcp-client/index.js +0 -14
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
- 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 +2 -2
- 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 +20 -20
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +1 -3
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +1 -15
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +0 -11
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +1 -20
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +0 -3
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +0 -6
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +0 -17
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +1 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +1 -8
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +0 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +0 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +1 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +2 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +2 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +8 -17
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -7
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.ts +1 -15
- package/packages/pi-coding-agent/src/core/model-registry.ts +1 -21
- package/packages/pi-coding-agent/src/core/settings-manager.ts +0 -9
- package/packages/pi-coding-agent/src/main.ts +0 -19
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +0 -10
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +0 -15
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +3 -18
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -16
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -8
- package/src/resources/extensions/gsd/auto/infra-errors.ts +0 -3
- package/src/resources/extensions/gsd/auto/phases.ts +48 -45
- package/src/resources/extensions/gsd/auto-prompts.ts +1 -24
- package/src/resources/extensions/gsd/auto-timers.ts +3 -64
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +0 -5
- package/src/resources/extensions/gsd/auto-worktree.ts +6 -9
- package/src/resources/extensions/gsd/auto.ts +3 -37
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +0 -129
- package/src/resources/extensions/gsd/commands/catalog.ts +1 -6
- package/src/resources/extensions/gsd/commands/handlers/core.ts +0 -1
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +0 -5
- package/src/resources/extensions/gsd/db-writer.ts +17 -39
- package/src/resources/extensions/gsd/doctor.ts +1 -7
- package/src/resources/extensions/gsd/git-service.ts +2 -6
- package/src/resources/extensions/gsd/gsd-db.ts +1 -16
- package/src/resources/extensions/gsd/markdown-renderer.ts +1 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +4 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
- package/src/resources/extensions/gsd/prompts/replan-slice.md +14 -3
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +37 -7
- package/src/resources/extensions/gsd/provider-error-pause.ts +0 -9
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +0 -79
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +2 -20
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +7 -11
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +1 -2
- package/src/resources/extensions/gsd/tools/plan-slice.ts +0 -2
- package/src/resources/extensions/gsd/tools/plan-task.ts +0 -2
- package/src/resources/extensions/gsd/tools/replan-slice.ts +0 -3
- package/src/resources/extensions/gsd/worktree-resolver.ts +0 -7
- package/src/resources/extensions/mcp-client/index.ts +0 -20
- package/dist/resources/extensions/gsd/commands-mcp-status.js +0 -187
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +0 -88
- package/packages/pi-coding-agent/dist/core/local-model-check.d.ts +0 -15
- package/packages/pi-coding-agent/dist/core/local-model-check.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/core/local-model-check.js +0 -41
- package/packages/pi-coding-agent/dist/core/local-model-check.js.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts +0 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js +0 -32
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts +0 -15
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js +0 -40
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js.map +0 -1
- package/packages/pi-coding-agent/src/core/local-model-check.ts +0 -45
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts +0 -38
- package/packages/pi-coding-agent/src/modes/interactive/components/timestamp.ts +0 -48
- package/src/resources/extensions/gsd/commands-mcp-status.ts +0 -247
- package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +0 -88
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +0 -114
- package/src/resources/extensions/gsd/tests/est-annotation-timeout.test.ts +0 -120
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +0 -103
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +0 -66
- package/src/resources/extensions/gsd/tests/stop-auto-merge-back.test.ts +0 -67
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +0 -49
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +0 -127
- /package/dist/web/standalone/.next/static/{kxxAA66bah_yhPYqLBHE2 → oZMtyM-zfu6Inx-S59cOl}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{kxxAA66bah_yhPYqLBHE2 → oZMtyM-zfu6Inx-S59cOl}/_ssgManifest.js +0 -0
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* completed-units-metrics-sync.test.ts — Regression tests for #2313.
|
|
3
|
-
*
|
|
4
|
-
* 1. completed-units.json should be archived (not wiped) on milestone transition
|
|
5
|
-
* 2. metrics.json should be in the worktree → project root sync file list
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import test from "node:test";
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
|
-
import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, existsSync, cpSync } from "node:fs";
|
|
11
|
-
import { join } from "node:path";
|
|
12
|
-
import { tmpdir } from "node:os";
|
|
13
|
-
|
|
14
|
-
// ─── Bug 1: completed-units.json should be archived, not wiped ─────────────
|
|
15
|
-
|
|
16
|
-
const phasesSrcPath = join(import.meta.dirname, "..", "auto", "phases.ts");
|
|
17
|
-
const phasesSrc = readFileSync(phasesSrcPath, "utf-8");
|
|
18
|
-
|
|
19
|
-
test("#2313: completed-units.json should not be blindly wiped to [] on milestone transition", () => {
|
|
20
|
-
// The milestone transition block should NOT write an empty array to completed-units.json
|
|
21
|
-
// without first archiving the existing data. Look for the archive/rename pattern.
|
|
22
|
-
const transitionIdx = phasesSrc.indexOf("Milestone transition");
|
|
23
|
-
assert.ok(transitionIdx !== -1, "Milestone transition section exists");
|
|
24
|
-
|
|
25
|
-
// Find the completed-units handling block
|
|
26
|
-
const completedUnitsIdx = phasesSrc.indexOf("completed-units", transitionIdx);
|
|
27
|
-
assert.ok(completedUnitsIdx !== -1, "completed-units handling exists in transition");
|
|
28
|
-
|
|
29
|
-
// Get a window around the completed-units handling (1200 chars to
|
|
30
|
-
// accommodate CRLF line endings on Windows which inflate byte offsets).
|
|
31
|
-
const windowStart = Math.max(0, completedUnitsIdx - 300);
|
|
32
|
-
const windowEnd = Math.min(phasesSrc.length, completedUnitsIdx + 900);
|
|
33
|
-
const window = phasesSrc.slice(windowStart, windowEnd).toLowerCase();
|
|
34
|
-
|
|
35
|
-
// Should archive/rename the old file before resetting
|
|
36
|
-
const hasArchive = window.includes("archive") ||
|
|
37
|
-
window.includes("rename") ||
|
|
38
|
-
window.includes("cpsync") ||
|
|
39
|
-
window.includes("safecopy") ||
|
|
40
|
-
window.includes("completed-units-");
|
|
41
|
-
|
|
42
|
-
assert.ok(
|
|
43
|
-
hasArchive,
|
|
44
|
-
"completed-units.json should be archived before reset during milestone transition",
|
|
45
|
-
);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// ─── Bug 2: metrics.json should be in the sync file lists ──────────────────
|
|
49
|
-
|
|
50
|
-
test("#2313: syncStateToProjectRoot should sync metrics.json", () => {
|
|
51
|
-
const syncSrcPath = join(import.meta.dirname, "..", "auto-worktree-sync.ts");
|
|
52
|
-
const syncSrc = readFileSync(syncSrcPath, "utf-8");
|
|
53
|
-
|
|
54
|
-
// syncStateToProjectRoot should copy metrics.json from worktree to project root
|
|
55
|
-
assert.ok(
|
|
56
|
-
syncSrc.includes("metrics.json"),
|
|
57
|
-
"auto-worktree-sync.ts should reference metrics.json for sync",
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("#2313: syncWorktreeStateBack should include metrics.json in root files list", () => {
|
|
62
|
-
const autoWorktreeSrcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
|
|
63
|
-
const autoWorktreeSrc = readFileSync(autoWorktreeSrcPath, "utf-8");
|
|
64
|
-
|
|
65
|
-
// Find the rootFiles array in syncWorktreeStateBack
|
|
66
|
-
const syncBackIdx = autoWorktreeSrc.indexOf("syncWorktreeStateBack");
|
|
67
|
-
assert.ok(syncBackIdx !== -1, "syncWorktreeStateBack exists");
|
|
68
|
-
|
|
69
|
-
const rootFilesIdx = autoWorktreeSrc.indexOf("rootFiles", syncBackIdx);
|
|
70
|
-
assert.ok(rootFilesIdx !== -1, "rootFiles list exists in syncWorktreeStateBack");
|
|
71
|
-
|
|
72
|
-
// Get the rootFiles array content
|
|
73
|
-
const arrayStart = autoWorktreeSrc.indexOf("[", rootFilesIdx);
|
|
74
|
-
const arrayEnd = autoWorktreeSrc.indexOf("]", arrayStart);
|
|
75
|
-
const rootFilesBlock = autoWorktreeSrc.slice(arrayStart, arrayEnd);
|
|
76
|
-
|
|
77
|
-
assert.ok(
|
|
78
|
-
rootFilesBlock.includes("metrics.json"),
|
|
79
|
-
"metrics.json should be in syncWorktreeStateBack rootFiles list",
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// ─── Functional test: completed-units archive ────────────────────────────────
|
|
84
|
-
|
|
85
|
-
test("#2313: functional — completed-units archive creates milestone-specific file", () => {
|
|
86
|
-
const tmpBase = mkdtempSync(join(tmpdir(), "gsd-completed-units-"));
|
|
87
|
-
const gsdDir = join(tmpBase, ".gsd");
|
|
88
|
-
mkdirSync(gsdDir, { recursive: true });
|
|
89
|
-
|
|
90
|
-
// Simulate existing completed-units.json with data
|
|
91
|
-
const existing = [
|
|
92
|
-
{ type: "task", id: "T01" },
|
|
93
|
-
{ type: "slice", id: "S01" },
|
|
94
|
-
];
|
|
95
|
-
const completedKeysPath = join(gsdDir, "completed-units.json");
|
|
96
|
-
writeFileSync(completedKeysPath, JSON.stringify(existing, null, 2));
|
|
97
|
-
|
|
98
|
-
// Simulate the archive behavior: copy to milestone-specific file
|
|
99
|
-
const milestoneId = "M001";
|
|
100
|
-
const archivePath = join(gsdDir, `completed-units-${milestoneId}.json`);
|
|
101
|
-
cpSync(completedKeysPath, archivePath);
|
|
102
|
-
|
|
103
|
-
// Reset the main file
|
|
104
|
-
writeFileSync(completedKeysPath, JSON.stringify([], null, 2));
|
|
105
|
-
|
|
106
|
-
// Verify archive exists with original data
|
|
107
|
-
assert.ok(existsSync(archivePath), "archive file should exist");
|
|
108
|
-
const archived = JSON.parse(readFileSync(archivePath, "utf-8"));
|
|
109
|
-
assert.deepEqual(archived, existing, "archived data should match original");
|
|
110
|
-
|
|
111
|
-
// Verify main file is reset
|
|
112
|
-
const current = JSON.parse(readFileSync(completedKeysPath, "utf-8"));
|
|
113
|
-
assert.deepEqual(current, [], "current completed-units should be empty after transition");
|
|
114
|
-
});
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* est-annotation-timeout.test.ts — Regression tests for #2243.
|
|
3
|
-
*
|
|
4
|
-
* Tasks with `est: 30m` or `est: 2h` annotations should get extended
|
|
5
|
-
* supervision timeouts. The parseEstimateMinutes helper should parse
|
|
6
|
-
* estimate strings, and startUnitSupervision should use them.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import test from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import { readFileSync } from "node:fs";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
|
|
14
|
-
const timersSrcPath = join(import.meta.dirname, "..", "auto-timers.ts");
|
|
15
|
-
const timersSrc = readFileSync(timersSrcPath, "utf-8");
|
|
16
|
-
|
|
17
|
-
// ─── Source analysis: parseEstimateMinutes exists and is exported ────────────
|
|
18
|
-
|
|
19
|
-
test("#2243: auto-timers.ts should export parseEstimateMinutes", () => {
|
|
20
|
-
assert.ok(
|
|
21
|
-
timersSrc.includes("export function parseEstimateMinutes"),
|
|
22
|
-
"parseEstimateMinutes should be exported from auto-timers.ts",
|
|
23
|
-
);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// ─── Inline unit test of parseEstimateMinutes logic ─────────────────────────
|
|
27
|
-
// Since importing the module pulls in heavy deps, test the parsing logic inline.
|
|
28
|
-
|
|
29
|
-
function parseEstimateMinutes(estimate: string): number | null {
|
|
30
|
-
if (!estimate || typeof estimate !== "string") return null;
|
|
31
|
-
const trimmed = estimate.trim();
|
|
32
|
-
if (!trimmed) return null;
|
|
33
|
-
|
|
34
|
-
let totalMinutes = 0;
|
|
35
|
-
let matched = false;
|
|
36
|
-
|
|
37
|
-
const hoursMatch = trimmed.match(/(\d+)\s*h/i);
|
|
38
|
-
if (hoursMatch) {
|
|
39
|
-
totalMinutes += Number(hoursMatch[1]) * 60;
|
|
40
|
-
matched = true;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const minutesMatch = trimmed.match(/(\d+)\s*m/i);
|
|
44
|
-
if (minutesMatch) {
|
|
45
|
-
totalMinutes += Number(minutesMatch[1]);
|
|
46
|
-
matched = true;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return matched ? totalMinutes : null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
test("#2243: parseEstimateMinutes parses '30m' correctly", () => {
|
|
53
|
-
assert.equal(parseEstimateMinutes("30m"), 30);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("#2243: parseEstimateMinutes parses '2h' correctly", () => {
|
|
57
|
-
assert.equal(parseEstimateMinutes("2h"), 120);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("#2243: parseEstimateMinutes parses '1h30m' correctly", () => {
|
|
61
|
-
assert.equal(parseEstimateMinutes("1h30m"), 90);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("#2243: parseEstimateMinutes parses '15m' correctly", () => {
|
|
65
|
-
assert.equal(parseEstimateMinutes("15m"), 15);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("#2243: parseEstimateMinutes returns null for empty string", () => {
|
|
69
|
-
assert.equal(parseEstimateMinutes(""), null);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("#2243: parseEstimateMinutes returns null for invalid string", () => {
|
|
73
|
-
assert.equal(parseEstimateMinutes("not a time"), null);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// ─── Source analysis: startUnitSupervision uses task estimates ───────────────
|
|
77
|
-
|
|
78
|
-
test("#2243: startUnitSupervision should reference task estimates for timeout scaling", () => {
|
|
79
|
-
const usesEstimate =
|
|
80
|
-
timersSrc.includes("parseEstimateMinutes") &&
|
|
81
|
-
timersSrc.includes("estimateMinutes") &&
|
|
82
|
-
timersSrc.includes("taskEstimate");
|
|
83
|
-
|
|
84
|
-
assert.ok(
|
|
85
|
-
usesEstimate,
|
|
86
|
-
"startUnitSupervision should use task estimate annotations for timeout scaling",
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("#2243: SupervisionContext should accept an optional taskEstimate field", () => {
|
|
91
|
-
const ctxIdx = timersSrc.indexOf("SupervisionContext");
|
|
92
|
-
assert.ok(ctxIdx !== -1, "SupervisionContext interface exists");
|
|
93
|
-
|
|
94
|
-
const ctxEnd = timersSrc.indexOf("}", ctxIdx);
|
|
95
|
-
const ctxBlock = timersSrc.slice(ctxIdx, ctxEnd);
|
|
96
|
-
|
|
97
|
-
assert.ok(
|
|
98
|
-
ctxBlock.includes("taskEstimate"),
|
|
99
|
-
"SupervisionContext should include a taskEstimate field",
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("#2243: timeouts should be scaled by estimate (timeoutScale in source)", () => {
|
|
104
|
-
assert.ok(
|
|
105
|
-
timersSrc.includes("timeoutScale"),
|
|
106
|
-
"auto-timers.ts should use a timeoutScale factor derived from est: annotations",
|
|
107
|
-
);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("#2243: idle timeout should NOT be scaled (idle is idle regardless of estimate)", () => {
|
|
111
|
-
// Find the idleTimeoutMs line
|
|
112
|
-
const idleIdx = timersSrc.indexOf("const idleTimeoutMs");
|
|
113
|
-
assert.ok(idleIdx !== -1, "idleTimeoutMs variable exists");
|
|
114
|
-
|
|
115
|
-
const idleLine = timersSrc.slice(idleIdx, timersSrc.indexOf("\n", idleIdx));
|
|
116
|
-
assert.ok(
|
|
117
|
-
!idleLine.includes("timeoutScale"),
|
|
118
|
-
"idleTimeoutMs should NOT be scaled — idle is idle",
|
|
119
|
-
);
|
|
120
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import test, { describe } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
formatMcpStatusReport,
|
|
6
|
-
formatMcpServerDetail,
|
|
7
|
-
type McpServerStatus,
|
|
8
|
-
} from "../commands-mcp-status.ts";
|
|
9
|
-
|
|
10
|
-
// ─── formatMcpStatusReport ──────────────────────────────────────────────────
|
|
11
|
-
|
|
12
|
-
describe("formatMcpStatusReport", () => {
|
|
13
|
-
test("returns no-servers message when list is empty", () => {
|
|
14
|
-
const result = formatMcpStatusReport([]);
|
|
15
|
-
assert.match(result, /no mcp servers configured/i);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("lists all servers with connection status", () => {
|
|
19
|
-
const servers: McpServerStatus[] = [
|
|
20
|
-
{ name: "railway", transport: "stdio", connected: true, toolCount: 5, error: undefined },
|
|
21
|
-
{ name: "linear", transport: "http", connected: false, toolCount: 0, error: undefined },
|
|
22
|
-
];
|
|
23
|
-
const result = formatMcpStatusReport(servers);
|
|
24
|
-
assert.match(result, /railway/);
|
|
25
|
-
assert.match(result, /linear/);
|
|
26
|
-
assert.match(result, /connected/i);
|
|
27
|
-
assert.match(result, /disconnected/i);
|
|
28
|
-
assert.match(result, /5 tools/);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("shows error state for servers with errors", () => {
|
|
32
|
-
const servers: McpServerStatus[] = [
|
|
33
|
-
{ name: "broken", transport: "stdio", connected: false, toolCount: 0, error: "Connection refused" },
|
|
34
|
-
];
|
|
35
|
-
const result = formatMcpStatusReport(servers);
|
|
36
|
-
assert.match(result, /error/i);
|
|
37
|
-
assert.match(result, /Connection refused/);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("includes server count in header", () => {
|
|
41
|
-
const servers: McpServerStatus[] = [
|
|
42
|
-
{ name: "a", transport: "stdio", connected: true, toolCount: 3, error: undefined },
|
|
43
|
-
{ name: "b", transport: "http", connected: true, toolCount: 2, error: undefined },
|
|
44
|
-
];
|
|
45
|
-
const result = formatMcpStatusReport(servers);
|
|
46
|
-
assert.match(result, /2/);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// ─── formatMcpServerDetail ──────────────────────────────────────────────────
|
|
51
|
-
|
|
52
|
-
describe("formatMcpServerDetail", () => {
|
|
53
|
-
test("shows server name and transport", () => {
|
|
54
|
-
const result = formatMcpServerDetail({
|
|
55
|
-
name: "railway",
|
|
56
|
-
transport: "stdio",
|
|
57
|
-
connected: true,
|
|
58
|
-
toolCount: 3,
|
|
59
|
-
tools: ["railway_list_projects", "railway_deploy", "railway_logs"],
|
|
60
|
-
error: undefined,
|
|
61
|
-
});
|
|
62
|
-
assert.match(result, /railway/);
|
|
63
|
-
assert.match(result, /stdio/);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("lists individual tools when available", () => {
|
|
67
|
-
const result = formatMcpServerDetail({
|
|
68
|
-
name: "railway",
|
|
69
|
-
transport: "stdio",
|
|
70
|
-
connected: true,
|
|
71
|
-
toolCount: 2,
|
|
72
|
-
tools: ["railway_list_projects", "railway_deploy"],
|
|
73
|
-
error: undefined,
|
|
74
|
-
});
|
|
75
|
-
assert.match(result, /railway_list_projects/);
|
|
76
|
-
assert.match(result, /railway_deploy/);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("shows error message for failed servers", () => {
|
|
80
|
-
const result = formatMcpServerDetail({
|
|
81
|
-
name: "broken",
|
|
82
|
-
transport: "stdio",
|
|
83
|
-
connected: false,
|
|
84
|
-
toolCount: 0,
|
|
85
|
-
tools: [],
|
|
86
|
-
error: "spawn ENOENT",
|
|
87
|
-
});
|
|
88
|
-
assert.match(result, /error/i);
|
|
89
|
-
assert.match(result, /spawn ENOENT/);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("shows disconnected status with no tools", () => {
|
|
93
|
-
const result = formatMcpServerDetail({
|
|
94
|
-
name: "offline",
|
|
95
|
-
transport: "http",
|
|
96
|
-
connected: false,
|
|
97
|
-
toolCount: 0,
|
|
98
|
-
tools: [],
|
|
99
|
-
error: undefined,
|
|
100
|
-
});
|
|
101
|
-
assert.match(result, /disconnected/i);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* merge-conflict-stops-loop.test.ts — #2330
|
|
3
|
-
*
|
|
4
|
-
* When a squash merge has real code conflicts (not just .gsd/ files),
|
|
5
|
-
* the merge retries forever because MergeConflictError is caught
|
|
6
|
-
* silently in mergeAndExit. This test verifies that:
|
|
7
|
-
* 1. worktree-resolver re-throws MergeConflictError for code conflicts
|
|
8
|
-
* 2. auto/phases.ts wraps mergeAndExit calls to stop the loop on conflict
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { readFileSync } from "node:fs";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
import { createTestContext } from "./test-helpers.ts";
|
|
14
|
-
|
|
15
|
-
const { assertTrue, report } = createTestContext();
|
|
16
|
-
|
|
17
|
-
const resolverPath = join(import.meta.dirname, "..", "worktree-resolver.ts");
|
|
18
|
-
const resolverSrc = readFileSync(resolverPath, "utf-8");
|
|
19
|
-
|
|
20
|
-
const phasesPath = join(import.meta.dirname, "..", "auto", "phases.ts");
|
|
21
|
-
const phasesSrc = readFileSync(phasesPath, "utf-8");
|
|
22
|
-
|
|
23
|
-
console.log("\n=== #2330: Merge conflict stops auto loop ===");
|
|
24
|
-
|
|
25
|
-
// ── Test 1: worktree-resolver re-throws MergeConflictError ──────────────
|
|
26
|
-
|
|
27
|
-
const methodStart = resolverSrc.indexOf("Worktree-mode merge:");
|
|
28
|
-
assertTrue(methodStart > 0, "worktree-resolver has _mergeWorktreeMode method");
|
|
29
|
-
|
|
30
|
-
const methodBody = resolverSrc.slice(methodStart, methodStart + 5000);
|
|
31
|
-
const rethrowsConflict =
|
|
32
|
-
methodBody.includes("MergeConflictError") &&
|
|
33
|
-
methodBody.includes("throw err");
|
|
34
|
-
|
|
35
|
-
assertTrue(
|
|
36
|
-
rethrowsConflict,
|
|
37
|
-
"worktree-resolver._mergeWorktreeMode re-throws MergeConflictError (#2330)",
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
// ── Test 2: auto/phases.ts imports and uses MergeConflictError ──────────
|
|
41
|
-
|
|
42
|
-
assertTrue(
|
|
43
|
-
phasesSrc.includes("MergeConflictError") && phasesSrc.includes("mergeAndExit"),
|
|
44
|
-
"auto/phases.ts handles MergeConflictError from mergeAndExit (#2330)",
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
// ── Test 3: The handler stops the loop (doesn't just warn) ──────────────
|
|
48
|
-
|
|
49
|
-
// Find the instanceof MergeConflictError check (not the import line)
|
|
50
|
-
const instanceofIdx = phasesSrc.indexOf("instanceof MergeConflictError");
|
|
51
|
-
assertTrue(instanceofIdx > 0, "auto/phases.ts has instanceof MergeConflictError check");
|
|
52
|
-
|
|
53
|
-
if (instanceofIdx > 0) {
|
|
54
|
-
const afterHandler = phasesSrc.slice(instanceofIdx, instanceofIdx + 500);
|
|
55
|
-
const stopsLoop =
|
|
56
|
-
afterHandler.includes("stopAuto") ||
|
|
57
|
-
afterHandler.includes('action: "break"') ||
|
|
58
|
-
afterHandler.includes("reason: \"merge-conflict\"");
|
|
59
|
-
|
|
60
|
-
assertTrue(
|
|
61
|
-
stopsLoop,
|
|
62
|
-
"auto/phases.ts stops the loop when merge conflict is detected (#2330)",
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
report();
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* stop-auto-merge-back.test.ts — Regression test for #2317.
|
|
3
|
-
*
|
|
4
|
-
* When auto-mode stops after a milestone is complete, stopAuto should trigger
|
|
5
|
-
* merge-back (mergeAndExit) instead of just exiting the worktree with
|
|
6
|
-
* preserveBranch: true. Otherwise milestone code stays stranded on the
|
|
7
|
-
* worktree branch and never reaches main.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import test from "node:test";
|
|
11
|
-
import assert from "node:assert/strict";
|
|
12
|
-
import { readFileSync } from "node:fs";
|
|
13
|
-
import { join } from "node:path";
|
|
14
|
-
|
|
15
|
-
// ─── Source analysis: stopAuto calls mergeAndExit for complete milestones ────
|
|
16
|
-
|
|
17
|
-
const autoSrcPath = join(import.meta.dirname, "..", "auto.ts");
|
|
18
|
-
const autoSrc = readFileSync(autoSrcPath, "utf-8");
|
|
19
|
-
|
|
20
|
-
test("#2317: stopAuto should check milestone completion status before choosing exit strategy", () => {
|
|
21
|
-
// stopAuto Step 4 should NOT unconditionally call exitMilestone(preserveBranch: true).
|
|
22
|
-
// It should check if the milestone is complete and call mergeAndExit instead.
|
|
23
|
-
|
|
24
|
-
// Find the Step 4 section
|
|
25
|
-
const step4Idx = autoSrc.indexOf("Step 4: Auto-worktree exit");
|
|
26
|
-
assert.ok(step4Idx !== -1, "Step 4 comment exists in stopAuto");
|
|
27
|
-
|
|
28
|
-
// Extract a reasonable window around Step 4 (up to Step 5)
|
|
29
|
-
const step5Idx = autoSrc.indexOf("Step 5:", step4Idx);
|
|
30
|
-
const step4Block = autoSrc.slice(step4Idx, step5Idx);
|
|
31
|
-
|
|
32
|
-
// The fix: Step 4 should call mergeAndExit when milestone is complete
|
|
33
|
-
assert.ok(
|
|
34
|
-
step4Block.includes("mergeAndExit"),
|
|
35
|
-
"Step 4 should call mergeAndExit for completed milestones",
|
|
36
|
-
);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("#2317: stopAuto should detect milestone completion via SUMMARY file or DB", () => {
|
|
40
|
-
const step4Idx = autoSrc.indexOf("Step 4: Auto-worktree exit");
|
|
41
|
-
const step5Idx = autoSrc.indexOf("Step 5:", step4Idx);
|
|
42
|
-
const step4Block = autoSrc.slice(step4Idx, step5Idx);
|
|
43
|
-
|
|
44
|
-
// Should check completion status — either via SUMMARY file, DB getMilestone, or phase
|
|
45
|
-
const checksCompletion =
|
|
46
|
-
step4Block.includes("SUMMARY") ||
|
|
47
|
-
step4Block.includes("getMilestone") ||
|
|
48
|
-
step4Block.includes("complete") ||
|
|
49
|
-
step4Block.includes("isMilestoneComplete");
|
|
50
|
-
|
|
51
|
-
assert.ok(
|
|
52
|
-
checksCompletion,
|
|
53
|
-
"Step 4 should check if milestone is complete before deciding exit strategy",
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("#2317: stopAuto still preserves branch for incomplete milestones", () => {
|
|
58
|
-
const step4Idx = autoSrc.indexOf("Step 4: Auto-worktree exit");
|
|
59
|
-
const step5Idx = autoSrc.indexOf("Step 5:", step4Idx);
|
|
60
|
-
const step4Block = autoSrc.slice(step4Idx, step5Idx);
|
|
61
|
-
|
|
62
|
-
// preserveBranch should still be used as fallback for non-complete milestones
|
|
63
|
-
assert.ok(
|
|
64
|
-
step4Block.includes("preserveBranch"),
|
|
65
|
-
"Step 4 should still preserve branch for incomplete milestones (fallback path)",
|
|
66
|
-
);
|
|
67
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* terminated-transient.test.ts — Regression test for #2309.
|
|
3
|
-
*
|
|
4
|
-
* classifyProviderError should treat 'terminated' errors (process killed,
|
|
5
|
-
* connection reset) as transient with auto-resume, not permanent.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import test from "node:test";
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
|
-
import { classifyProviderError } from "../provider-error-pause.ts";
|
|
11
|
-
|
|
12
|
-
test("#2309: 'terminated' errors should be classified as transient", () => {
|
|
13
|
-
const result = classifyProviderError("terminated");
|
|
14
|
-
assert.equal(result.isTransient, true, "'terminated' should be transient");
|
|
15
|
-
assert.equal(result.isRateLimit, false, "'terminated' is not a rate limit");
|
|
16
|
-
assert.ok(result.suggestedDelayMs > 0, "'terminated' should have a retry delay");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test("#2309: 'connection reset' errors should be classified as transient", () => {
|
|
20
|
-
const result = classifyProviderError("connection reset by peer");
|
|
21
|
-
assert.equal(result.isTransient, true, "'connection reset' should be transient");
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("#2309: 'other side closed' errors should be classified as transient", () => {
|
|
25
|
-
const result = classifyProviderError("other side closed the connection");
|
|
26
|
-
assert.equal(result.isTransient, true, "'other side closed' should be transient");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("#2309: 'fetch failed' errors should be classified as transient", () => {
|
|
30
|
-
const result = classifyProviderError("fetch failed: network error");
|
|
31
|
-
assert.equal(result.isTransient, true, "'fetch failed' should be transient");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("#2309: 'connection refused' errors should be classified as transient", () => {
|
|
35
|
-
const result = classifyProviderError("ECONNREFUSED: connection refused");
|
|
36
|
-
assert.equal(result.isTransient, true, "'connection refused' should be transient");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("#2309: permanent errors are still permanent", () => {
|
|
40
|
-
const authResult = classifyProviderError("unauthorized: invalid API key");
|
|
41
|
-
assert.equal(authResult.isTransient, false, "auth errors should stay permanent");
|
|
42
|
-
assert.equal(authResult.suggestedDelayMs, 0, "permanent errors have no delay");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("#2309: rate limits are still transient", () => {
|
|
46
|
-
const rlResult = classifyProviderError("rate limit exceeded (429)");
|
|
47
|
-
assert.equal(rlResult.isTransient, true, "rate limits are still transient");
|
|
48
|
-
assert.equal(rlResult.isRateLimit, true, "rate limits are flagged as rate limits");
|
|
49
|
-
});
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* validate-milestone handler — the core operation behind gsd_validate_milestone.
|
|
3
|
-
*
|
|
4
|
-
* Persists milestone validation results to the assessments table,
|
|
5
|
-
* renders VALIDATION.md to disk, and invalidates caches.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
transaction,
|
|
12
|
-
_getAdapter,
|
|
13
|
-
} from "../gsd-db.js";
|
|
14
|
-
import { resolveMilestonePath, clearPathCache } from "../paths.js";
|
|
15
|
-
import { saveFile, clearParseCache } from "../files.js";
|
|
16
|
-
import { invalidateStateCache } from "../state.js";
|
|
17
|
-
|
|
18
|
-
export interface ValidateMilestoneParams {
|
|
19
|
-
milestoneId: string;
|
|
20
|
-
verdict: "pass" | "needs-attention" | "needs-remediation";
|
|
21
|
-
remediationRound: number;
|
|
22
|
-
successCriteriaChecklist: string;
|
|
23
|
-
sliceDeliveryAudit: string;
|
|
24
|
-
crossSliceIntegration: string;
|
|
25
|
-
requirementCoverage: string;
|
|
26
|
-
verdictRationale: string;
|
|
27
|
-
remediationPlan?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ValidateMilestoneResult {
|
|
31
|
-
milestoneId: string;
|
|
32
|
-
verdict: string;
|
|
33
|
-
validationPath: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function renderValidationMarkdown(params: ValidateMilestoneParams): string {
|
|
37
|
-
let md = `---
|
|
38
|
-
verdict: ${params.verdict}
|
|
39
|
-
remediation_round: ${params.remediationRound}
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
# Milestone Validation: ${params.milestoneId}
|
|
43
|
-
|
|
44
|
-
## Success Criteria Checklist
|
|
45
|
-
${params.successCriteriaChecklist}
|
|
46
|
-
|
|
47
|
-
## Slice Delivery Audit
|
|
48
|
-
${params.sliceDeliveryAudit}
|
|
49
|
-
|
|
50
|
-
## Cross-Slice Integration
|
|
51
|
-
${params.crossSliceIntegration}
|
|
52
|
-
|
|
53
|
-
## Requirement Coverage
|
|
54
|
-
${params.requirementCoverage}
|
|
55
|
-
|
|
56
|
-
## Verdict Rationale
|
|
57
|
-
${params.verdictRationale}
|
|
58
|
-
`;
|
|
59
|
-
|
|
60
|
-
if (params.verdict === "needs-remediation" && params.remediationPlan) {
|
|
61
|
-
md += `\n## Remediation Plan\n${params.remediationPlan}\n`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return md;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function handleValidateMilestone(
|
|
68
|
-
params: ValidateMilestoneParams,
|
|
69
|
-
basePath: string,
|
|
70
|
-
): Promise<ValidateMilestoneResult | { error: string }> {
|
|
71
|
-
if (!params.milestoneId || typeof params.milestoneId !== "string" || params.milestoneId.trim() === "") {
|
|
72
|
-
return { error: "milestoneId is required and must be a non-empty string" };
|
|
73
|
-
}
|
|
74
|
-
const validVerdicts = ["pass", "needs-attention", "needs-remediation"];
|
|
75
|
-
if (!validVerdicts.includes(params.verdict)) {
|
|
76
|
-
return { error: `verdict must be one of: ${validVerdicts.join(", ")}` };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ── Filesystem render ──────────────────────────────────────────────────
|
|
80
|
-
const validationMd = renderValidationMarkdown(params);
|
|
81
|
-
|
|
82
|
-
let validationPath: string;
|
|
83
|
-
const milestoneDir = resolveMilestonePath(basePath, params.milestoneId);
|
|
84
|
-
if (milestoneDir) {
|
|
85
|
-
validationPath = join(milestoneDir, `${params.milestoneId}-VALIDATION.md`);
|
|
86
|
-
} else {
|
|
87
|
-
const gsdDir = join(basePath, ".gsd");
|
|
88
|
-
const manualDir = join(gsdDir, "milestones", params.milestoneId);
|
|
89
|
-
validationPath = join(manualDir, `${params.milestoneId}-VALIDATION.md`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
await saveFile(validationPath, validationMd);
|
|
94
|
-
} catch (renderErr) {
|
|
95
|
-
process.stderr.write(
|
|
96
|
-
`gsd-db: validate_milestone — disk render failed: ${(renderErr as Error).message}\n`,
|
|
97
|
-
);
|
|
98
|
-
return { error: `disk render failed: ${(renderErr as Error).message}` };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ── DB write — store in assessments table ──────────────────────────────
|
|
102
|
-
const validatedAt = new Date().toISOString();
|
|
103
|
-
|
|
104
|
-
transaction(() => {
|
|
105
|
-
const adapter = _getAdapter()!;
|
|
106
|
-
adapter.prepare(
|
|
107
|
-
`INSERT OR REPLACE INTO assessments (path, milestone_id, slice_id, task_id, status, scope, full_content, created_at)
|
|
108
|
-
VALUES (:path, :mid, NULL, NULL, :verdict, 'milestone-validation', :content, :created_at)`,
|
|
109
|
-
).run({
|
|
110
|
-
":path": validationPath,
|
|
111
|
-
":mid": params.milestoneId,
|
|
112
|
-
":verdict": params.verdict,
|
|
113
|
-
":content": validationMd,
|
|
114
|
-
":created_at": validatedAt,
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
invalidateStateCache();
|
|
119
|
-
clearPathCache();
|
|
120
|
-
clearParseCache();
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
milestoneId: params.milestoneId,
|
|
124
|
-
verdict: params.verdict,
|
|
125
|
-
validationPath,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
File without changes
|
|
File without changes
|