gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.bbb5216

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/bundled-resource-path.d.ts +8 -0
  2. package/dist/bundled-resource-path.js +14 -0
  3. package/dist/headless-query.js +6 -6
  4. package/dist/resources/extensions/gsd/auto/session.js +27 -32
  5. package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
  8. package/dist/resources/extensions/gsd/auto-loop.js +956 -0
  9. package/dist/resources/extensions/gsd/auto-observability.js +4 -2
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
  11. package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
  12. package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
  13. package/dist/resources/extensions/gsd/auto-start.js +330 -309
  14. package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
  15. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
  16. package/dist/resources/extensions/gsd/auto-timers.js +3 -4
  17. package/dist/resources/extensions/gsd/auto-verification.js +35 -73
  18. package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
  19. package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
  20. package/dist/resources/extensions/gsd/auto.js +283 -1013
  21. package/dist/resources/extensions/gsd/captures.js +10 -4
  22. package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  24. package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
  25. package/dist/resources/extensions/gsd/git-service.js +1 -1
  26. package/dist/resources/extensions/gsd/gsd-db.js +296 -151
  27. package/dist/resources/extensions/gsd/index.js +92 -228
  28. package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
  29. package/dist/resources/extensions/gsd/progress-score.js +61 -156
  30. package/dist/resources/extensions/gsd/quick.js +98 -122
  31. package/dist/resources/extensions/gsd/session-lock.js +13 -0
  32. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  33. package/dist/resources/extensions/gsd/undo.js +43 -48
  34. package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
  35. package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
  36. package/dist/resources/extensions/gsd/verification-gate.js +6 -35
  37. package/dist/resources/extensions/gsd/worktree-command.js +30 -24
  38. package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
  39. package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
  40. package/dist/resources/extensions/gsd/worktree.js +7 -44
  41. package/dist/tool-bootstrap.js +59 -11
  42. package/dist/worktree-cli.js +7 -7
  43. package/package.json +1 -1
  44. package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
  45. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  46. package/packages/pi-ai/dist/models.generated.js +735 -2588
  47. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  48. package/packages/pi-ai/src/models.generated.ts +1039 -2892
  49. package/packages/pi-coding-agent/package.json +1 -1
  50. package/pkg/package.json +1 -1
  51. package/src/resources/extensions/gsd/auto/session.ts +47 -30
  52. package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
  53. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
  54. package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
  55. package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
  56. package/src/resources/extensions/gsd/auto-observability.ts +4 -2
  57. package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
  58. package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
  59. package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
  60. package/src/resources/extensions/gsd/auto-start.ts +440 -354
  61. package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
  62. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
  63. package/src/resources/extensions/gsd/auto-timers.ts +3 -4
  64. package/src/resources/extensions/gsd/auto-verification.ts +76 -90
  65. package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
  66. package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
  67. package/src/resources/extensions/gsd/auto.ts +515 -1199
  68. package/src/resources/extensions/gsd/captures.ts +10 -4
  69. package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
  70. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  71. package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
  72. package/src/resources/extensions/gsd/git-service.ts +8 -1
  73. package/src/resources/extensions/gsd/gitignore.ts +4 -2
  74. package/src/resources/extensions/gsd/gsd-db.ts +375 -180
  75. package/src/resources/extensions/gsd/index.ts +104 -263
  76. package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
  77. package/src/resources/extensions/gsd/progress-score.ts +65 -200
  78. package/src/resources/extensions/gsd/quick.ts +121 -125
  79. package/src/resources/extensions/gsd/session-lock.ts +11 -0
  80. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  81. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
  82. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
  83. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  84. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
  85. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
  86. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
  87. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
  88. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
  89. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
  90. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  91. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
  92. package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
  93. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
  94. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
  95. package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
  96. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
  97. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
  98. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
  99. package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
  100. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
  101. package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
  102. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
  103. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
  104. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  105. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  106. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
  107. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
  108. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
  109. package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
  110. package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
  111. package/src/resources/extensions/gsd/types.ts +90 -81
  112. package/src/resources/extensions/gsd/undo.ts +42 -46
  113. package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
  114. package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
  115. package/src/resources/extensions/gsd/verification-gate.ts +6 -39
  116. package/src/resources/extensions/gsd/worktree-command.ts +36 -24
  117. package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
  118. package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
  119. package/src/resources/extensions/gsd/worktree.ts +7 -44
  120. package/dist/resources/extensions/gsd/auto-constants.js +0 -5
  121. package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
  122. package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
  123. package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
  124. package/src/resources/extensions/gsd/auto-constants.ts +0 -6
  125. package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
  126. package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
  127. package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
  128. package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
  129. package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
  130. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
  131. package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
  132. package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
  133. package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
  134. package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
  135. package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
@@ -1,356 +0,0 @@
1
- /**
2
- * Mechanical Completion — unit tests (ADR-003).
3
- *
4
- * Tests deterministic slice/milestone completion using fixture data.
5
- * Uses node:test + node:assert for consistency with token-profile.test.ts.
6
- */
7
-
8
- import test from "node:test";
9
- import assert from "node:assert/strict";
10
- import { mkdirSync, writeFileSync, readFileSync, rmSync, existsSync } from "node:fs";
11
- import { join } from "node:path";
12
- import { tmpdir } from "node:os";
13
- import { randomBytes } from "node:crypto";
14
-
15
- // ─── Fixture Helpers ──────────────────────────────────────────────────────────
16
-
17
- function createTmpBase(): string {
18
- const base = join(tmpdir(), `gsd-mech-test-${randomBytes(4).toString("hex")}`);
19
- mkdirSync(base, { recursive: true });
20
- return base;
21
- }
22
-
23
- function scaffold(base: string, mid: string, sid: string, taskSummaries: Array<{ tid: string; content: string }>) {
24
- const gsdRoot = join(base, ".gsd");
25
- const mDir = join(gsdRoot, "milestones", mid);
26
- const sDir = join(mDir, "slices", sid);
27
- const tDir = join(sDir, "tasks");
28
- mkdirSync(tDir, { recursive: true });
29
-
30
- for (const { tid, content } of taskSummaries) {
31
- writeFileSync(join(tDir, `${tid}-SUMMARY.md`), content, "utf-8");
32
- }
33
-
34
- return { gsdRoot, mDir, sDir, tDir };
35
- }
36
-
37
- function makeTaskSummary(tid: string, opts: {
38
- oneLiner?: string;
39
- provides?: string[];
40
- key_files?: string[];
41
- key_decisions?: string[];
42
- verification_result?: string;
43
- }): string {
44
- const lines: string[] = [
45
- "---",
46
- `id: ${tid}`,
47
- `parent: S01`,
48
- `milestone: M001`,
49
- ];
50
- if (opts.provides?.length) lines.push(`provides:\n${opts.provides.map(p => ` - ${p}`).join("\n")}`);
51
- if (opts.key_files?.length) lines.push(`key_files:\n${opts.key_files.map(f => ` - ${f}`).join("\n")}`);
52
- if (opts.key_decisions?.length) lines.push(`key_decisions:\n${opts.key_decisions.map(d => ` - ${d}`).join("\n")}`);
53
- lines.push(`verification_result: ${opts.verification_result ?? "passed"}`);
54
- lines.push("---");
55
- lines.push("");
56
- lines.push(`# ${tid}: Test Task`);
57
- lines.push("");
58
- if (opts.oneLiner) lines.push(`**${opts.oneLiner}**`);
59
- lines.push("");
60
- lines.push("## What Happened");
61
- lines.push("");
62
- lines.push(`Implemented the feature described in ${tid}. This was a significant change that modified multiple files across the codebase to support the new functionality.`);
63
- lines.push("");
64
- return lines.join("\n");
65
- }
66
-
67
- // ─── Source-level structural tests ────────────────────────────────────────────
68
-
69
- const mechanicalSrc = readFileSync(
70
- join(import.meta.dirname!, "..", "mechanical-completion.ts"),
71
- "utf-8",
72
- );
73
-
74
- test("mechanical-completion: exports mechanicalSliceCompletion", () => {
75
- assert.ok(
76
- mechanicalSrc.includes("export async function mechanicalSliceCompletion"),
77
- "should export mechanicalSliceCompletion",
78
- );
79
- });
80
-
81
- test("mechanical-completion: exports aggregateMilestoneVerification", () => {
82
- assert.ok(
83
- mechanicalSrc.includes("export async function aggregateMilestoneVerification"),
84
- "should export aggregateMilestoneVerification",
85
- );
86
- });
87
-
88
- test("mechanical-completion: exports generateMilestoneSummary", () => {
89
- assert.ok(
90
- mechanicalSrc.includes("export async function generateMilestoneSummary"),
91
- "should export generateMilestoneSummary",
92
- );
93
- });
94
-
95
- test("mechanical-completion: exports appendNewDecisions", () => {
96
- assert.ok(
97
- mechanicalSrc.includes("export async function appendNewDecisions"),
98
- "should export appendNewDecisions",
99
- );
100
- });
101
-
102
- test("mechanical-completion: uses atomicWriteSync for file writes", () => {
103
- assert.ok(
104
- mechanicalSrc.includes("atomicWriteSync"),
105
- "should use atomicWriteSync for safe file writes",
106
- );
107
- });
108
-
109
- test("mechanical-completion: quality gate checks summary length for multi-task slices", () => {
110
- assert.ok(
111
- mechanicalSrc.includes("totalContent.length < 200"),
112
- "should have quality gate for summary content length",
113
- );
114
- });
115
-
116
- test("mechanical-completion: marks slice [x] in roadmap", () => {
117
- assert.ok(
118
- mechanicalSrc.includes("markSliceInRoadmap"),
119
- "should mark slice done in roadmap",
120
- );
121
- });
122
-
123
- test("mechanical-completion: aggregates VERIFY.json files for milestone validation", () => {
124
- assert.ok(
125
- mechanicalSrc.includes("resolveTaskJsonFiles") && mechanicalSrc.includes("VERIFY"),
126
- "should read VERIFY.json files for milestone validation",
127
- );
128
- });
129
-
130
- test("mechanical-completion: deduplicates decisions against existing DECISIONS.md", () => {
131
- assert.ok(
132
- mechanicalSrc.includes("existing.includes(d.trim())"),
133
- "should deduplicate decisions against existing content",
134
- );
135
- });
136
-
137
- test("mechanical-completion: produces VALIDATION.md with verdict frontmatter", () => {
138
- assert.ok(
139
- mechanicalSrc.includes("verdict:") && mechanicalSrc.includes("remediation_round: 0"),
140
- "VALIDATION.md should have verdict and remediation_round frontmatter",
141
- );
142
- });
143
-
144
- // ─── Integration tests with fixture data ──────────────────────────────────────
145
-
146
- test("mechanical: slice completion with 2 task summaries produces SUMMARY.md", async () => {
147
- const base = createTmpBase();
148
- try {
149
- const mid = "M001";
150
- const sid = "S01";
151
-
152
- // Scaffold task summaries
153
- scaffold(base, mid, sid, [
154
- {
155
- tid: "T01",
156
- content: makeTaskSummary("T01", {
157
- oneLiner: "Set up project structure",
158
- provides: ["project-scaffold"],
159
- key_files: ["src/index.ts", "package.json"],
160
- verification_result: "passed",
161
- }),
162
- },
163
- {
164
- tid: "T02",
165
- content: makeTaskSummary("T02", {
166
- oneLiner: "Add core API endpoints",
167
- provides: ["api-endpoints"],
168
- key_files: ["src/api.ts"],
169
- key_decisions: ["Used Express over Fastify"],
170
- verification_result: "passed",
171
- }),
172
- },
173
- ]);
174
-
175
- // Write a roadmap with the slice unchecked
176
- const roadmapPath = join(base, ".gsd", "milestones", mid, `${mid}-ROADMAP.md`);
177
- writeFileSync(roadmapPath, `# Roadmap\n\n- [ ] **${sid}: First Slice**\n`, "utf-8");
178
-
179
- // Write a slice plan with Verification section
180
- const planPath = join(base, ".gsd", "milestones", mid, "slices", sid, `${sid}-PLAN.md`);
181
- writeFileSync(planPath, `# Plan\n\n## Verification\n\n- Run \`npm test\`\n- Check output\n`, "utf-8");
182
-
183
- // Dynamic import to get the actual module
184
- const { mechanicalSliceCompletion } = await import("../mechanical-completion.js");
185
- const ok = await mechanicalSliceCompletion(base, mid, sid);
186
-
187
- assert.ok(ok, "should return true for valid slice completion");
188
-
189
- // Check SUMMARY.md was written
190
- const summaryPath = join(base, ".gsd", "milestones", mid, "slices", sid, `${sid}-SUMMARY.md`);
191
- assert.ok(existsSync(summaryPath), "SUMMARY.md should exist");
192
-
193
- const summaryContent = readFileSync(summaryPath, "utf-8");
194
- assert.ok(summaryContent.includes("T01"), "summary should reference T01");
195
- assert.ok(summaryContent.includes("T02"), "summary should reference T02");
196
- assert.ok(summaryContent.includes("verification_result: passed"), "should have passed verification");
197
-
198
- // Check roadmap was updated
199
- const updatedRoadmap = readFileSync(roadmapPath, "utf-8");
200
- assert.ok(updatedRoadmap.includes("[x]"), "roadmap should have [x] checkbox");
201
-
202
- // Check UAT was written
203
- const uatPath = join(base, ".gsd", "milestones", mid, "slices", sid, `${sid}-UAT.md`);
204
- assert.ok(existsSync(uatPath), "UAT.md should exist");
205
- const uatContent = readFileSync(uatPath, "utf-8");
206
- assert.ok(uatContent.includes("npm test"), "UAT should contain verification content");
207
- } finally {
208
- rmSync(base, { recursive: true, force: true });
209
- }
210
- });
211
-
212
- test("mechanical: returns false for empty task summaries", async () => {
213
- const base = createTmpBase();
214
- try {
215
- const mid = "M001";
216
- const sid = "S01";
217
- scaffold(base, mid, sid, []);
218
-
219
- const { mechanicalSliceCompletion } = await import("../mechanical-completion.js");
220
- const ok = await mechanicalSliceCompletion(base, mid, sid);
221
- assert.ok(!ok, "should return false when no summaries exist");
222
- } finally {
223
- rmSync(base, { recursive: true, force: true });
224
- }
225
- });
226
-
227
- test("mechanical: returns false for insufficient summary content in multi-task slice", async () => {
228
- const base = createTmpBase();
229
- try {
230
- const mid = "M001";
231
- const sid = "S01";
232
-
233
- // Two tasks but with very short content (under 200 chars)
234
- scaffold(base, mid, sid, [
235
- { tid: "T01", content: "---\nid: T01\nparent: S01\nmilestone: M001\n---\n\n# T01: A\n\n**Short**\n" },
236
- { tid: "T02", content: "---\nid: T02\nparent: S01\nmilestone: M001\n---\n\n# T02: B\n\n**Brief**\n" },
237
- ]);
238
-
239
- const { mechanicalSliceCompletion } = await import("../mechanical-completion.js");
240
- const ok = await mechanicalSliceCompletion(base, mid, sid);
241
- assert.ok(!ok, "should return false when summaries are too short");
242
- } finally {
243
- rmSync(base, { recursive: true, force: true });
244
- }
245
- });
246
-
247
- test("mechanical: milestone verification aggregates VERIFY.json files", async () => {
248
- const base = createTmpBase();
249
- try {
250
- const mid = "M001";
251
- const sid = "S01";
252
- const { tDir } = scaffold(base, mid, sid, []);
253
-
254
- // Write VERIFY.json files
255
- const evidence = {
256
- schemaVersion: 1,
257
- taskId: "T01",
258
- unitId: "M001/S01/T01",
259
- timestamp: Date.now(),
260
- passed: true,
261
- discoverySource: "plan",
262
- checks: [
263
- { command: "npm test", exitCode: 0, durationMs: 1500, verdict: "pass", blocking: true },
264
- ],
265
- };
266
- writeFileSync(join(tDir, "T01-VERIFY.json"), JSON.stringify(evidence), "utf-8");
267
-
268
- const evidence2 = { ...evidence, taskId: "T02", passed: false, checks: [
269
- { command: "npm test", exitCode: 1, durationMs: 500, verdict: "fail", blocking: true },
270
- ]};
271
- writeFileSync(join(tDir, "T02-VERIFY.json"), JSON.stringify(evidence2), "utf-8");
272
-
273
- const { aggregateMilestoneVerification } = await import("../mechanical-completion.js");
274
- const result = await aggregateMilestoneVerification(base, mid);
275
-
276
- assert.equal(result.verdict, "mixed", "should be mixed when some pass and some fail");
277
- assert.equal(result.checks.length, 2, "should have 2 checks");
278
-
279
- // Check VALIDATION.md was written
280
- const validationPath = join(base, ".gsd", "milestones", mid, `${mid}-VALIDATION.md`);
281
- assert.ok(existsSync(validationPath), "VALIDATION.md should exist");
282
- const validationContent = readFileSync(validationPath, "utf-8");
283
- assert.ok(validationContent.includes("verdict: mixed"), "should have mixed verdict in frontmatter");
284
- } finally {
285
- rmSync(base, { recursive: true, force: true });
286
- }
287
- });
288
-
289
- test("mechanical: milestone summary aggregates slice summaries", async () => {
290
- const base = createTmpBase();
291
- try {
292
- const mid = "M001";
293
-
294
- // Create two slices with summaries
295
- for (const sid of ["S01", "S02"]) {
296
- const sDir = join(base, ".gsd", "milestones", mid, "slices", sid);
297
- mkdirSync(sDir, { recursive: true });
298
- writeFileSync(
299
- join(sDir, `${sid}-SUMMARY.md`),
300
- `---\nid: ${sid}\nprovides:\n - feature-${sid.toLowerCase()}\nkey_files:\n - src/${sid.toLowerCase()}.ts\n---\n\n# ${sid}: Slice\n\n**${sid} implemented**\n`,
301
- "utf-8",
302
- );
303
- }
304
-
305
- const { generateMilestoneSummary } = await import("../mechanical-completion.js");
306
- const content = await generateMilestoneSummary(base, mid);
307
-
308
- assert.ok(content.includes("S01"), "should reference S01");
309
- assert.ok(content.includes("S02"), "should reference S02");
310
- assert.ok(content.includes("feature-s01"), "should aggregate provides");
311
- assert.ok(content.includes("feature-s02"), "should aggregate provides");
312
-
313
- const summaryPath = join(base, ".gsd", "milestones", mid, `${mid}-SUMMARY.md`);
314
- assert.ok(existsSync(summaryPath), "M##-SUMMARY.md should exist");
315
- } finally {
316
- rmSync(base, { recursive: true, force: true });
317
- }
318
- });
319
-
320
- test("mechanical: decision deduplication skips existing decisions", async () => {
321
- const base = createTmpBase();
322
- try {
323
- const gsdRoot = join(base, ".gsd");
324
- mkdirSync(gsdRoot, { recursive: true });
325
-
326
- // Write existing decisions
327
- const decisionsPath = join(gsdRoot, "DECISIONS.md");
328
- writeFileSync(decisionsPath, "# Decisions\n\n- Used TypeScript for type safety\n", "utf-8");
329
-
330
- const { appendNewDecisions } = await import("../mechanical-completion.js");
331
-
332
- // Call with one existing and one new decision
333
- const mockSummaries = [
334
- {
335
- frontmatter: {
336
- id: "T01", parent: "S01", milestone: "M001",
337
- provides: [], requires: [], affects: [],
338
- key_files: [], key_decisions: ["Used TypeScript for type safety", "Chose Express over Koa"],
339
- patterns_established: [], drill_down_paths: [], observability_surfaces: [],
340
- duration: "", verification_result: "passed", completed_at: "", blocker_discovered: false,
341
- },
342
- title: "T01", oneLiner: "", whatHappened: "", deviations: "", filesModified: [],
343
- },
344
- ];
345
-
346
- await appendNewDecisions(base, mockSummaries as any);
347
-
348
- const updated = readFileSync(decisionsPath, "utf-8");
349
- assert.ok(updated.includes("Chose Express over Koa"), "should append new decision");
350
- // The existing decision should not be duplicated
351
- const matches = updated.match(/Used TypeScript for type safety/g);
352
- assert.equal(matches?.length, 1, "should not duplicate existing decision");
353
- } finally {
354
- rmSync(base, { recursive: true, force: true });
355
- }
356
- });
@@ -1,206 +0,0 @@
1
- /**
2
- * progress-score.test.ts — Tests for progress score / traffic light (#1221).
3
- *
4
- * Tests:
5
- * - Score computation from health signals
6
- * - Signal evaluation (trend, error streak, recent errors)
7
- * - Context-aware scoring (retry counts, unit progress)
8
- * - Formatting (single-line, detailed report)
9
- */
10
-
11
- import {
12
- recordHealthSnapshot,
13
- resetProactiveHealing,
14
- } from "../doctor-proactive.ts";
15
-
16
- import {
17
- computeProgressScore,
18
- computeProgressScoreWithContext,
19
- formatProgressLine,
20
- formatProgressReport,
21
- } from "../progress-score.ts";
22
-
23
- import { createTestContext } from "./test-helpers.ts";
24
-
25
- const { assertEq, assertTrue, assertMatch, report } = createTestContext();
26
-
27
- async function main(): Promise<void> {
28
- try {
29
- // ── Base Score: No Data ─────────────────────────────────────────────
30
- console.log("\n=== progress: green with no data ===");
31
- {
32
- resetProactiveHealing();
33
- const score = computeProgressScore();
34
- assertEq(score.level, "green", "green when no data available");
35
- assertTrue(score.summary.includes("Progressing well"), "summary says progressing");
36
- assertTrue(score.signals.length > 0, "has signals");
37
- }
38
-
39
- // ── Green: Clean Health Data ────────────────────────────────────────
40
- console.log("\n=== progress: green with clean health ===");
41
- {
42
- resetProactiveHealing();
43
- for (let i = 0; i < 5; i++) {
44
- recordHealthSnapshot(0, 0, 0);
45
- }
46
- const score = computeProgressScore();
47
- assertEq(score.level, "green", "green with all clean snapshots");
48
- }
49
-
50
- // ── Yellow: Some Warnings ──────────────────────────────────────────
51
- console.log("\n=== progress: yellow with error streak ===");
52
- {
53
- resetProactiveHealing();
54
- recordHealthSnapshot(1, 2, 0);
55
- recordHealthSnapshot(1, 1, 0);
56
- const score = computeProgressScore();
57
- assertEq(score.level, "yellow", "yellow with consecutive errors");
58
- assertTrue(score.summary.includes("Struggling"), "summary says struggling");
59
- }
60
-
61
- // ── Red: Degrading Health ──────────────────────────────────────────
62
- console.log("\n=== progress: red with degrading trend ===");
63
- {
64
- resetProactiveHealing();
65
- // 5 older clean snapshots
66
- for (let i = 0; i < 5; i++) {
67
- recordHealthSnapshot(0, 0, 0);
68
- }
69
- // 5 recent error snapshots — triggers degrading trend
70
- for (let i = 0; i < 5; i++) {
71
- recordHealthSnapshot(3, 5, 0);
72
- }
73
- const score = computeProgressScore();
74
- assertEq(score.level, "red", "red with degrading trend and persistent errors");
75
- assertTrue(score.summary.includes("Stuck"), "summary says stuck");
76
- }
77
-
78
- // ── Red: High Error Streak ─────────────────────────────────────────
79
- console.log("\n=== progress: red with high error streak ===");
80
- {
81
- resetProactiveHealing();
82
- for (let i = 0; i < 4; i++) {
83
- recordHealthSnapshot(2, 0, 0);
84
- }
85
- const score = computeProgressScore();
86
- assertEq(score.level, "red", "red with 4 consecutive error units");
87
- }
88
-
89
- // ── Context-Aware Scoring ──────────────────────────────────────────
90
- console.log("\n=== progress: context with retries ===");
91
- {
92
- resetProactiveHealing();
93
- for (let i = 0; i < 3; i++) {
94
- recordHealthSnapshot(0, 0, 0);
95
- }
96
- const score = computeProgressScoreWithContext({
97
- currentUnitId: "M001/S01/T03",
98
- completedUnits: 2,
99
- totalUnits: 5,
100
- retryCount: 0,
101
- maxRetries: 5,
102
- });
103
- assertEq(score.level, "green", "green with no retries");
104
- assertTrue(score.summary.includes("M001/S01/T03"), "summary includes unit ID");
105
- assertTrue(score.summary.includes("2 of 5"), "summary includes progress");
106
- }
107
-
108
- console.log("\n=== progress: context with high retry count ===");
109
- {
110
- resetProactiveHealing();
111
- for (let i = 0; i < 3; i++) {
112
- recordHealthSnapshot(0, 0, 0);
113
- }
114
- const score = computeProgressScoreWithContext({
115
- currentUnitId: "M001/S01/T03",
116
- retryCount: 4,
117
- maxRetries: 5,
118
- });
119
- assertEq(score.level, "red", "red with high retry count");
120
- assertTrue(score.summary.includes("looping"), "summary mentions looping");
121
- }
122
-
123
- console.log("\n=== progress: context with moderate retries ===");
124
- {
125
- resetProactiveHealing();
126
- for (let i = 0; i < 3; i++) {
127
- recordHealthSnapshot(0, 0, 0);
128
- }
129
- const score = computeProgressScoreWithContext({
130
- currentUnitId: "M001/S01/T03",
131
- retryCount: 1,
132
- maxRetries: 5,
133
- });
134
- assertEq(score.level, "yellow", "yellow with 1 retry");
135
- }
136
-
137
- // ── Formatting ─────────────────────────────────────────────────────
138
- console.log("\n=== progress: formatProgressLine ===");
139
- {
140
- resetProactiveHealing();
141
- const score = computeProgressScore();
142
- const line = formatProgressLine(score);
143
- assertTrue(line.includes("Progressing well"), "line includes summary");
144
- // Should start with green circle emoji
145
- assertTrue(line.startsWith("\uD83D\uDFE2"), "starts with green circle");
146
- }
147
-
148
- console.log("\n=== progress: formatProgressLine yellow ===");
149
- {
150
- resetProactiveHealing();
151
- recordHealthSnapshot(1, 0, 0);
152
- recordHealthSnapshot(1, 0, 0);
153
- const score = computeProgressScore();
154
- const line = formatProgressLine(score);
155
- assertTrue(line.startsWith("\uD83D\uDFE1"), "starts with yellow circle");
156
- }
157
-
158
- console.log("\n=== progress: formatProgressReport ===");
159
- {
160
- resetProactiveHealing();
161
- recordHealthSnapshot(0, 1, 0);
162
- const score = computeProgressScore();
163
- const detailed = formatProgressReport(score);
164
- assertTrue(detailed.includes("Signals:"), "report has signals section");
165
- assertTrue(detailed.includes("health_trend"), "report includes trend signal");
166
- assertTrue(detailed.includes("error_streak"), "report includes streak signal");
167
- }
168
-
169
- // ── Signal Details ─────────────────────────────────────────────────
170
- console.log("\n=== progress: signal names are consistent ===");
171
- {
172
- resetProactiveHealing();
173
- recordHealthSnapshot(0, 0, 0);
174
- const score = computeProgressScore();
175
- const names = score.signals.map(s => s.name);
176
- assertTrue(names.includes("health_trend"), "has health_trend signal");
177
- assertTrue(names.includes("error_streak"), "has error_streak signal");
178
- assertTrue(names.includes("recent_errors"), "has recent_errors signal");
179
- assertTrue(names.includes("artifact_production"), "has artifact_production signal");
180
- assertTrue(names.includes("dispatch_velocity"), "has dispatch_velocity signal");
181
- }
182
-
183
- console.log("\n=== progress: all signals have valid levels ===");
184
- {
185
- resetProactiveHealing();
186
- for (let i = 0; i < 5; i++) {
187
- recordHealthSnapshot(1, 1, 1);
188
- }
189
- const score = computeProgressScore();
190
- for (const signal of score.signals) {
191
- assertTrue(
192
- signal.level === "green" || signal.level === "yellow" || signal.level === "red",
193
- `signal ${signal.name} has valid level: ${signal.level}`,
194
- );
195
- assertTrue(signal.detail.length > 0, `signal ${signal.name} has non-empty detail`);
196
- }
197
- }
198
-
199
- } finally {
200
- resetProactiveHealing();
201
- }
202
-
203
- report();
204
- }
205
-
206
- main();