gsd-pi 2.18.0 → 2.19.0

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 (73) hide show
  1. package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
  2. package/dist/resources/extensions/gsd/auto-prompts.ts +45 -15
  3. package/dist/resources/extensions/gsd/auto.ts +276 -19
  4. package/dist/resources/extensions/gsd/captures.ts +384 -0
  5. package/dist/resources/extensions/gsd/commands.ts +139 -3
  6. package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
  7. package/dist/resources/extensions/gsd/dashboard-overlay.ts +10 -0
  8. package/dist/resources/extensions/gsd/metrics.ts +48 -0
  9. package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
  10. package/dist/resources/extensions/gsd/model-router.ts +256 -0
  11. package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  12. package/dist/resources/extensions/gsd/preferences.ts +73 -0
  13. package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
  14. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  15. package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  16. package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  17. package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
  18. package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  19. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  20. package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  21. package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  22. package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  23. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +227 -1
  24. package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  25. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  26. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  27. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
  28. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
  29. package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
  30. package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
  31. package/dist/resources/extensions/gsd/visualizer-data.ts +154 -0
  32. package/dist/resources/extensions/gsd/visualizer-overlay.ts +193 -0
  33. package/dist/resources/extensions/gsd/visualizer-views.ts +293 -0
  34. package/dist/resources/extensions/remote-questions/discord-adapter.ts +33 -0
  35. package/dist/resources/extensions/remote-questions/format.ts +12 -6
  36. package/dist/resources/extensions/remote-questions/manager.ts +8 -0
  37. package/package.json +1 -1
  38. package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
  39. package/src/resources/extensions/gsd/auto-prompts.ts +45 -15
  40. package/src/resources/extensions/gsd/auto.ts +276 -19
  41. package/src/resources/extensions/gsd/captures.ts +384 -0
  42. package/src/resources/extensions/gsd/commands.ts +139 -3
  43. package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
  44. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
  45. package/src/resources/extensions/gsd/metrics.ts +48 -0
  46. package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
  47. package/src/resources/extensions/gsd/model-router.ts +256 -0
  48. package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  49. package/src/resources/extensions/gsd/preferences.ts +73 -0
  50. package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
  51. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  52. package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  53. package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  54. package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
  55. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  56. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  57. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  58. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  59. package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  60. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +227 -1
  61. package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  62. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  63. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  64. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
  65. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
  66. package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
  67. package/src/resources/extensions/gsd/triage-ui.ts +175 -0
  68. package/src/resources/extensions/gsd/visualizer-data.ts +154 -0
  69. package/src/resources/extensions/gsd/visualizer-overlay.ts +193 -0
  70. package/src/resources/extensions/gsd/visualizer-views.ts +293 -0
  71. package/src/resources/extensions/remote-questions/discord-adapter.ts +33 -0
  72. package/src/resources/extensions/remote-questions/format.ts +12 -6
  73. package/src/resources/extensions/remote-questions/manager.ts +8 -0
@@ -1,87 +1,240 @@
1
- /**
2
- * Routing History — structural tests for adaptive learning module.
3
- *
4
- * Verifies routing-history.ts exports and structure from #579.
5
- * Uses source-level checks to avoid @gsd/pi-coding-agent import chain.
6
- */
7
-
8
1
  import test from "node:test";
9
2
  import assert from "node:assert/strict";
10
- import { readFileSync } from "node:fs";
11
- import { join, dirname } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
- const historySrc = readFileSync(join(__dirname, "..", "routing-history.ts"), "utf-8");
16
-
17
- // ═══════════════════════════════════════════════════════════════════════════
18
- // Module Exports
19
- // ═══════════════════════════════════════════════════════════════════════════
20
-
21
- test("routing-history: exports initRoutingHistory", () => {
22
- assert.ok(historySrc.includes("export function initRoutingHistory"), "should export initRoutingHistory");
3
+ import { mkdirSync, rmSync, writeFileSync, readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import {
8
+ initRoutingHistory,
9
+ resetRoutingHistory,
10
+ recordOutcome,
11
+ recordFeedback,
12
+ getAdaptiveTierAdjustment,
13
+ clearRoutingHistory,
14
+ getRoutingHistory,
15
+ } from "../routing-history.js";
16
+
17
+ // ─── Test Setup ──────────────────────────────────────────────────────────────
18
+
19
+ function makeTmpDir(): string {
20
+ const dir = join(tmpdir(), `gsd-routing-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
21
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
22
+ return dir;
23
+ }
24
+
25
+ function cleanup(dir: string): void {
26
+ try { rmSync(dir, { recursive: true, force: true }); } catch {}
27
+ resetRoutingHistory();
28
+ }
29
+
30
+ // ─── recordOutcome ───────────────────────────────────────────────────────────
31
+
32
+ test("recordOutcome tracks success and failure counts", () => {
33
+ const dir = makeTmpDir();
34
+ try {
35
+ initRoutingHistory(dir);
36
+ recordOutcome("execute-task", "standard", true);
37
+ recordOutcome("execute-task", "standard", true);
38
+ recordOutcome("execute-task", "standard", false);
39
+
40
+ const history = getRoutingHistory();
41
+ assert.ok(history);
42
+ const pattern = history.patterns["execute-task"];
43
+ assert.ok(pattern);
44
+ assert.equal(pattern.standard.success, 2);
45
+ assert.equal(pattern.standard.fail, 1);
46
+ } finally {
47
+ cleanup(dir);
48
+ }
23
49
  });
24
50
 
25
- test("routing-history: exports recordOutcome", () => {
26
- assert.ok(historySrc.includes("export function recordOutcome"), "should export recordOutcome");
51
+ test("recordOutcome tracks tag-specific patterns", () => {
52
+ const dir = makeTmpDir();
53
+ try {
54
+ initRoutingHistory(dir);
55
+ recordOutcome("execute-task", "light", true, ["docs"]);
56
+
57
+ const history = getRoutingHistory();
58
+ assert.ok(history);
59
+ assert.ok(history.patterns["execute-task:docs"]);
60
+ assert.equal(history.patterns["execute-task:docs"].light.success, 1);
61
+ } finally {
62
+ cleanup(dir);
63
+ }
27
64
  });
28
65
 
29
- test("routing-history: exports recordFeedback", () => {
30
- assert.ok(historySrc.includes("export function recordFeedback"), "should export recordFeedback");
66
+ test("recordOutcome applies rolling window", () => {
67
+ const dir = makeTmpDir();
68
+ try {
69
+ initRoutingHistory(dir);
70
+ // Record 60 successes — should be capped to 50
71
+ for (let i = 0; i < 60; i++) {
72
+ recordOutcome("execute-task", "standard", true);
73
+ }
74
+
75
+ const history = getRoutingHistory();
76
+ assert.ok(history);
77
+ const total = history.patterns["execute-task"].standard.success +
78
+ history.patterns["execute-task"].standard.fail;
79
+ assert.ok(total <= 50, `total ${total} should be <= 50`);
80
+ } finally {
81
+ cleanup(dir);
82
+ }
31
83
  });
32
84
 
33
- test("routing-history: exports getAdaptiveTierAdjustment", () => {
34
- assert.ok(historySrc.includes("export function getAdaptiveTierAdjustment"), "should export getAdaptiveTierAdjustment");
85
+ // ─── getAdaptiveTierAdjustment ───────────────────────────────────────────────
86
+
87
+ test("no adjustment when insufficient data", () => {
88
+ const dir = makeTmpDir();
89
+ try {
90
+ initRoutingHistory(dir);
91
+ recordOutcome("execute-task", "light", false);
92
+ // Only 1 data point — not enough
93
+ const adj = getAdaptiveTierAdjustment("execute-task", "light");
94
+ assert.equal(adj, null);
95
+ } finally {
96
+ cleanup(dir);
97
+ }
35
98
  });
36
99
 
37
- test("routing-history: exports resetRoutingHistory", () => {
38
- assert.ok(historySrc.includes("export function resetRoutingHistory"), "should export resetRoutingHistory");
100
+ test("bumps tier when failure rate exceeds threshold", () => {
101
+ const dir = makeTmpDir();
102
+ try {
103
+ initRoutingHistory(dir);
104
+ // Record high failure rate at light tier
105
+ recordOutcome("execute-task", "light", false);
106
+ recordOutcome("execute-task", "light", false);
107
+ recordOutcome("execute-task", "light", true);
108
+ // 2/3 = 66% failure rate > 20% threshold
109
+
110
+ const adj = getAdaptiveTierAdjustment("execute-task", "light");
111
+ assert.equal(adj, "standard");
112
+ } finally {
113
+ cleanup(dir);
114
+ }
39
115
  });
40
116
 
41
- // ═══════════════════════════════════════════════════════════════════════════
42
- // Design Constants
43
- // ═══════════════════════════════════════════════════════════════════════════
44
-
45
- test("routing-history: uses rolling window of 50 entries", () => {
46
- assert.ok(historySrc.includes("ROLLING_WINDOW = 50"), "should use 50-entry rolling window");
117
+ test("no adjustment when success rate is high", () => {
118
+ const dir = makeTmpDir();
119
+ try {
120
+ initRoutingHistory(dir);
121
+ for (let i = 0; i < 10; i++) {
122
+ recordOutcome("execute-task", "light", true);
123
+ }
124
+ const adj = getAdaptiveTierAdjustment("execute-task", "light");
125
+ assert.equal(adj, null);
126
+ } finally {
127
+ cleanup(dir);
128
+ }
47
129
  });
48
130
 
49
- test("routing-history: failure threshold is 20%", () => {
50
- assert.ok(historySrc.includes("FAILURE_THRESHOLD = 0.20"), "should use 20% failure threshold");
131
+ test("tag-specific patterns take precedence", () => {
132
+ const dir = makeTmpDir();
133
+ try {
134
+ initRoutingHistory(dir);
135
+ // Base pattern has high success rate (tagged calls also count toward base)
136
+ for (let i = 0; i < 15; i++) {
137
+ recordOutcome("execute-task", "light", true);
138
+ }
139
+ // But docs-tagged tasks fail at light
140
+ recordOutcome("execute-task", "light", false, ["docs"]);
141
+ recordOutcome("execute-task", "light", false, ["docs"]);
142
+ recordOutcome("execute-task", "light", true, ["docs"]);
143
+
144
+ // With tags, should bump (docs pattern: 1/3 success = 66% failure)
145
+ const adj = getAdaptiveTierAdjustment("execute-task", "light", ["docs"]);
146
+ assert.equal(adj, "standard");
147
+
148
+ // Without tags, should not bump (base: 16/18 success = 11% failure)
149
+ const adjBase = getAdaptiveTierAdjustment("execute-task", "light");
150
+ assert.equal(adjBase, null);
151
+ } finally {
152
+ cleanup(dir);
153
+ }
51
154
  });
52
155
 
53
- test("routing-history: feedback weight is 2x", () => {
54
- assert.ok(historySrc.includes("FEEDBACK_WEIGHT = 2"), "feedback should count 2x");
156
+ // ─── recordFeedback ──────────────────────────────────────────────────────────
157
+
158
+ test("recordFeedback stores feedback entries", () => {
159
+ const dir = makeTmpDir();
160
+ try {
161
+ initRoutingHistory(dir);
162
+ recordFeedback("execute-task", "M001/S01/T01", "standard", "over");
163
+
164
+ const history = getRoutingHistory();
165
+ assert.ok(history);
166
+ assert.equal(history.feedback.length, 1);
167
+ assert.equal(history.feedback[0].rating, "over");
168
+ assert.equal(history.feedback[0].tier, "standard");
169
+ } finally {
170
+ cleanup(dir);
171
+ }
55
172
  });
56
173
 
57
- // ═══════════════════════════════════════════════════════════════════════════
58
- // Type Structure
59
- // ═══════════════════════════════════════════════════════════════════════════
60
-
61
- test("routing-history: imports ComplexityTier from types.ts", () => {
62
- assert.ok(
63
- historySrc.includes('from "./types.js"') && historySrc.includes("ComplexityTier"),
64
- "should import ComplexityTier from types.ts",
65
- );
66
- });
67
-
68
- test("routing-history: defines RoutingHistoryData interface", () => {
69
- assert.ok(historySrc.includes("interface RoutingHistoryData"), "should define RoutingHistoryData");
174
+ test("recordFeedback 'under' increases failure count at tier", () => {
175
+ const dir = makeTmpDir();
176
+ try {
177
+ initRoutingHistory(dir);
178
+ recordFeedback("execute-task", "M001/S01/T01", "light", "under");
179
+
180
+ const history = getRoutingHistory();
181
+ assert.ok(history);
182
+ // "under" adds 2 (FEEDBACK_WEIGHT) failures
183
+ assert.equal(history.patterns["execute-task"].light.fail, 2);
184
+ } finally {
185
+ cleanup(dir);
186
+ }
70
187
  });
71
188
 
72
- test("routing-history: defines FeedbackEntry interface", () => {
73
- assert.ok(historySrc.includes("interface FeedbackEntry"), "should define FeedbackEntry");
189
+ test("recordFeedback 'over' increases success count at lower tier", () => {
190
+ const dir = makeTmpDir();
191
+ try {
192
+ initRoutingHistory(dir);
193
+ recordFeedback("execute-task", "M001/S01/T01", "standard", "over");
194
+
195
+ const history = getRoutingHistory();
196
+ assert.ok(history);
197
+ // "over" at standard → adds 2 successes at light
198
+ assert.equal(history.patterns["execute-task"].light.success, 2);
199
+ } finally {
200
+ cleanup(dir);
201
+ }
74
202
  });
75
203
 
76
- // ═══════════════════════════════════════════════════════════════════════════
77
- // Persistence
78
- // ═══════════════════════════════════════════════════════════════════════════
79
-
80
- test("routing-history: persists to routing-history.json", () => {
81
- assert.ok(historySrc.includes("routing-history.json"), "should persist to routing-history.json");
204
+ // ─── clearRoutingHistory ─────────────────────────────────────────────────────
205
+
206
+ test("clearRoutingHistory resets all data", () => {
207
+ const dir = makeTmpDir();
208
+ try {
209
+ initRoutingHistory(dir);
210
+ recordOutcome("execute-task", "light", true);
211
+ clearRoutingHistory(dir);
212
+
213
+ const history = getRoutingHistory();
214
+ assert.ok(history);
215
+ assert.deepEqual(history.patterns, {});
216
+ assert.deepEqual(history.feedback, []);
217
+ } finally {
218
+ cleanup(dir);
219
+ }
82
220
  });
83
221
 
84
- test("routing-history: has save and load functions", () => {
85
- assert.ok(historySrc.includes("saveHistory") || historySrc.includes("function save"), "should have save");
86
- assert.ok(historySrc.includes("loadHistory") || historySrc.includes("function load"), "should have load");
222
+ // ─── Persistence ─────────────────────────────────────────────────────────────
223
+
224
+ test("routing history persists to disk and reloads", () => {
225
+ const dir = makeTmpDir();
226
+ try {
227
+ initRoutingHistory(dir);
228
+ recordOutcome("execute-task", "standard", true);
229
+ recordOutcome("execute-task", "standard", true);
230
+ resetRoutingHistory();
231
+
232
+ // Reload from disk
233
+ initRoutingHistory(dir);
234
+ const history = getRoutingHistory();
235
+ assert.ok(history);
236
+ assert.equal(history.patterns["execute-task"].standard.success, 2);
237
+ } finally {
238
+ cleanup(dir);
239
+ }
87
240
  });
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Triage dispatch ordering contract tests.
3
+ *
4
+ * These tests verify structural invariants of the triage integration
5
+ * by inspecting the actual source code of auto.ts and post-unit-hooks.ts.
6
+ * Full behavioral testing requires the @gsd/pi-coding-agent runtime.
7
+ */
8
+
9
+ import test from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import { readFileSync } from "node:fs";
12
+ import { join, dirname } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ const autoPath = join(__dirname, "..", "auto.ts");
17
+ const hooksPath = join(__dirname, "..", "post-unit-hooks.ts");
18
+ const autoPromptsPath = join(__dirname, "..", "auto-prompts.ts");
19
+
20
+ const autoSrc = readFileSync(autoPath, "utf-8");
21
+ const hooksSrc = readFileSync(hooksPath, "utf-8");
22
+ const autoPromptsSrc = (() => { try { return readFileSync(autoPromptsPath, "utf-8"); } catch { return autoSrc; } })();
23
+
24
+ // ─── Hook exclusion ──────────────────────────────────────────────────────────
25
+
26
+ test("dispatch: triage-captures excluded from post-unit hook triggering", () => {
27
+ // post-unit-hooks.ts must return null for triage-captures unit type
28
+ assert.ok(
29
+ hooksSrc.includes('"triage-captures"'),
30
+ "post-unit-hooks.ts should reference triage-captures",
31
+ );
32
+ assert.ok(
33
+ hooksSrc.includes('completedUnitType === "triage-captures"'),
34
+ "should check for triage-captures in the hook exclusion guard",
35
+ );
36
+ });
37
+
38
+ // ─── Triage check placement ──────────────────────────────────────────────────
39
+
40
+ test("dispatch: triage check appears after hook section and before stepMode check", () => {
41
+ const hookRetryIndex = autoSrc.indexOf("isRetryPending()");
42
+ // Find the triage check in handleAgentEnd (not in getAutoDashboardData)
43
+ const triageCheckIndex = autoSrc.indexOf("Triage check: dispatch triage unit");
44
+ const stepModeIndex = autoSrc.indexOf("In step mode, pause and show a wizard");
45
+
46
+ assert.ok(hookRetryIndex > 0, "hook retry check should exist");
47
+ assert.ok(triageCheckIndex > 0, "triage check block should exist");
48
+ assert.ok(stepModeIndex > 0, "step mode check should exist");
49
+
50
+ assert.ok(
51
+ triageCheckIndex > hookRetryIndex,
52
+ "triage check should come after hook retry check",
53
+ );
54
+ assert.ok(
55
+ triageCheckIndex < stepModeIndex,
56
+ "triage check should come before stepMode check",
57
+ );
58
+ });
59
+
60
+ // ─── Guard conditions ────────────────────────────────────────────────────────
61
+
62
+ test("dispatch: triage check guards against step mode", () => {
63
+ // The triage block should check !stepMode
64
+ const triageBlock = autoSrc.slice(
65
+ autoSrc.indexOf("Triage check: dispatch triage unit"),
66
+ autoSrc.indexOf("In step mode, pause and show a wizard"),
67
+ );
68
+ assert.ok(
69
+ triageBlock.includes("!stepMode"),
70
+ "triage block should guard against step mode",
71
+ );
72
+ });
73
+
74
+ test("dispatch: triage check guards against hook unit types", () => {
75
+ const triageBlock = autoSrc.slice(
76
+ autoSrc.indexOf("Triage check: dispatch triage unit"),
77
+ autoSrc.indexOf("In step mode, pause and show a wizard"),
78
+ );
79
+ assert.ok(
80
+ triageBlock.includes('!currentUnit.type.startsWith("hook/")'),
81
+ "triage block should not fire for hook units",
82
+ );
83
+ });
84
+
85
+ test("dispatch: triage check guards against triage-on-triage", () => {
86
+ const triageBlock = autoSrc.slice(
87
+ autoSrc.indexOf("Triage check: dispatch triage unit"),
88
+ autoSrc.indexOf("In step mode, pause and show a wizard"),
89
+ );
90
+ assert.ok(
91
+ triageBlock.includes('currentUnit.type !== "triage-captures"'),
92
+ "triage block should not fire for triage units",
93
+ );
94
+ });
95
+
96
+ test("dispatch: triage check guards against quick-task triggering triage", () => {
97
+ const triageBlock = autoSrc.slice(
98
+ autoSrc.indexOf("Triage check: dispatch triage unit"),
99
+ autoSrc.indexOf("In step mode, pause and show a wizard"),
100
+ );
101
+ assert.ok(
102
+ triageBlock.includes('currentUnit.type !== "quick-task"'),
103
+ "triage block should not fire for quick-task units",
104
+ );
105
+ });
106
+
107
+ test("dispatch: triage dispatch uses early-return pattern", () => {
108
+ const triageBlock = autoSrc.slice(
109
+ autoSrc.indexOf("Triage check: dispatch triage unit"),
110
+ autoSrc.indexOf("In step mode, pause and show a wizard"),
111
+ );
112
+ assert.ok(
113
+ triageBlock.includes("return; // handleAgentEnd will fire again"),
114
+ "triage dispatch should return after sending message",
115
+ );
116
+ });
117
+
118
+ test("dispatch: triage imports hasPendingCaptures and loadPendingCaptures", () => {
119
+ assert.ok(
120
+ autoSrc.includes('hasPendingCaptures, loadPendingCaptures, countPendingCaptures') &&
121
+ autoSrc.includes('from "./captures.js"'),
122
+ "auto.ts should import capture functions including countPendingCaptures",
123
+ );
124
+ });
125
+
126
+ // ─── Prompt integration ──────────────────────────────────────────────────────
127
+
128
+ test("dispatch: replan prompt builder loads capture context", () => {
129
+ const src = autoPromptsSrc;
130
+ assert.ok(
131
+ src.includes("loadReplanCaptures"),
132
+ "buildReplanSlicePrompt should load replan captures",
133
+ );
134
+ assert.ok(
135
+ src.includes("captureContext"),
136
+ "buildReplanSlicePrompt should pass captureContext to template",
137
+ );
138
+ });
139
+
140
+ test("dispatch: reassess prompt builder loads deferred captures", () => {
141
+ const src = autoPromptsSrc;
142
+ assert.ok(
143
+ src.includes("loadDeferredCaptures"),
144
+ "buildReassessRoadmapPrompt should load deferred captures",
145
+ );
146
+ assert.ok(
147
+ src.includes("deferredCaptures"),
148
+ "buildReassessRoadmapPrompt should pass deferredCaptures to template",
149
+ );
150
+ });
151
+
152
+ // ─── Prompt templates ────────────────────────────────────────────────────────
153
+
154
+ test("dispatch: replan prompt template includes captureContext variable", () => {
155
+ const promptPath = join(__dirname, "..", "prompts", "replan-slice.md");
156
+ const prompt = readFileSync(promptPath, "utf-8");
157
+ assert.ok(
158
+ prompt.includes("{{captureContext}}"),
159
+ "replan-slice.md should include {{captureContext}}",
160
+ );
161
+ });
162
+
163
+ test("dispatch: reassess prompt template includes deferredCaptures variable", () => {
164
+ const promptPath = join(__dirname, "..", "prompts", "reassess-roadmap.md");
165
+ const prompt = readFileSync(promptPath, "utf-8");
166
+ assert.ok(
167
+ prompt.includes("{{deferredCaptures}}"),
168
+ "reassess-roadmap.md should include {{deferredCaptures}}",
169
+ );
170
+ });
171
+
172
+ test("dispatch: triage prompt template exists and has classification criteria", () => {
173
+ const promptPath = join(__dirname, "..", "prompts", "triage-captures.md");
174
+ const prompt = readFileSync(promptPath, "utf-8");
175
+ assert.ok(prompt.includes("quick-task"), "should have quick-task classification");
176
+ assert.ok(prompt.includes("inject"), "should have inject classification");
177
+ assert.ok(prompt.includes("defer"), "should have defer classification");
178
+ assert.ok(prompt.includes("replan"), "should have replan classification");
179
+ assert.ok(prompt.includes("note"), "should have note classification");
180
+ assert.ok(prompt.includes("{{pendingCaptures}}"), "should have pending captures variable");
181
+ });
182
+
183
+ // ─── Dashboard integration ───────────────────────────────────────────────────
184
+
185
+ test("dashboard: AutoDashboardData includes pendingCaptureCount field", () => {
186
+ assert.ok(
187
+ autoSrc.includes("pendingCaptureCount"),
188
+ "auto.ts should have pendingCaptureCount in AutoDashboardData",
189
+ );
190
+ });
191
+
192
+ test("dashboard: getAutoDashboardData computes pendingCaptureCount", () => {
193
+ assert.ok(
194
+ autoSrc.includes("pendingCaptureCount = countPendingCaptures") ||
195
+ autoSrc.includes("pendingCaptureCount = countPendingCaptures(basePath)"),
196
+ "getAutoDashboardData should compute pendingCaptureCount from countPendingCaptures (single-read)",
197
+ );
198
+ });
199
+
200
+ test("dashboard: overlay renders pending captures badge", () => {
201
+ const overlayPath = join(__dirname, "..", "dashboard-overlay.ts");
202
+ const overlaySrc = readFileSync(overlayPath, "utf-8");
203
+ assert.ok(
204
+ overlaySrc.includes("pendingCaptureCount"),
205
+ "dashboard-overlay.ts should reference pendingCaptureCount",
206
+ );
207
+ assert.ok(
208
+ overlaySrc.includes("pending capture"),
209
+ "dashboard-overlay.ts should show pending captures text",
210
+ );
211
+ });
212
+
213
+ test("dashboard: overlay labels triage-captures and quick-task unit types", () => {
214
+ const overlayPath = join(__dirname, "..", "dashboard-overlay.ts");
215
+ const overlaySrc = readFileSync(overlayPath, "utf-8");
216
+ assert.ok(
217
+ overlaySrc.includes('"triage-captures"'),
218
+ "unitLabel should handle triage-captures",
219
+ );
220
+ assert.ok(
221
+ overlaySrc.includes('"quick-task"'),
222
+ "unitLabel should handle quick-task",
223
+ );
224
+ });