gsd-pi 2.17.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.
- package/README.md +39 -0
- package/dist/onboarding.js +2 -2
- package/dist/remote-questions-config.d.ts +10 -0
- package/dist/remote-questions-config.js +36 -0
- package/dist/resources/extensions/gsd/activity-log.ts +37 -7
- package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +65 -16
- package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/dist/resources/extensions/gsd/auto.ts +399 -29
- package/dist/resources/extensions/gsd/captures.ts +384 -0
- package/dist/resources/extensions/gsd/commands.ts +382 -23
- package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +201 -2
- package/dist/resources/extensions/gsd/files.ts +123 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +237 -4
- package/dist/resources/extensions/gsd/index.ts +47 -3
- package/dist/resources/extensions/gsd/metrics.ts +48 -0
- package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/dist/resources/extensions/gsd/model-router.ts +256 -0
- package/dist/resources/extensions/gsd/paths.ts +9 -0
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/dist/resources/extensions/gsd/preferences.ts +132 -1
- package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -0
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/dist/resources/extensions/gsd/queue-order.ts +231 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/dist/resources/extensions/gsd/state.ts +15 -3
- package/dist/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +14 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/dist/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/dist/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/dist/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/dist/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +227 -1
- package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +154 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +193 -0
- package/dist/resources/extensions/gsd/visualizer-views.ts +293 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/dist/resources/extensions/gsd/worktree.ts +22 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +33 -0
- package/dist/resources/extensions/remote-questions/format.ts +12 -6
- package/dist/resources/extensions/remote-questions/manager.ts +8 -0
- package/dist/resources/extensions/shared/next-action-ui.ts +16 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/cli/args.d.ts +5 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +21 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts +14 -3
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.js +52 -17
- package/packages/pi-coding-agent/dist/cli/list-models.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts +27 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js +79 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +140 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js +162 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +100 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +113 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +26 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +98 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts +62 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js +145 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js +118 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +5 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +4 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +17 -2
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +25 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +121 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
- 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 +32 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +21 -0
- package/packages/pi-coding-agent/src/cli/list-models.ts +70 -17
- package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +170 -0
- package/packages/pi-coding-agent/src/core/discovery-cache.ts +97 -0
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +125 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +231 -0
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +135 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +107 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.test.ts +145 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.ts +188 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +21 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/index.ts +5 -0
- package/packages/pi-coding-agent/src/main.ts +19 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +163 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +37 -0
- package/src/resources/extensions/gsd/activity-log.ts +37 -7
- package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +65 -16
- package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/src/resources/extensions/gsd/auto.ts +399 -29
- package/src/resources/extensions/gsd/captures.ts +384 -0
- package/src/resources/extensions/gsd/commands.ts +382 -23
- package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/src/resources/extensions/gsd/docs/preferences-reference.md +201 -2
- package/src/resources/extensions/gsd/files.ts +123 -1
- package/src/resources/extensions/gsd/guided-flow.ts +237 -4
- package/src/resources/extensions/gsd/index.ts +47 -3
- package/src/resources/extensions/gsd/metrics.ts +48 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/src/resources/extensions/gsd/model-router.ts +256 -0
- package/src/resources/extensions/gsd/paths.ts +9 -0
- package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/src/resources/extensions/gsd/preferences.ts +132 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -0
- package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/src/resources/extensions/gsd/queue-order.ts +231 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/src/resources/extensions/gsd/state.ts +15 -3
- package/src/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/src/resources/extensions/gsd/templates/preferences.md +14 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +227 -1
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/src/resources/extensions/gsd/triage-ui.ts +175 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +154 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +193 -0
- package/src/resources/extensions/gsd/visualizer-views.ts +293 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/src/resources/extensions/gsd/worktree.ts +22 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +33 -0
- package/src/resources/extensions/remote-questions/format.ts +12 -6
- package/src/resources/extensions/remote-questions/manager.ts +8 -0
- package/src/resources/extensions/shared/next-action-ui.ts +16 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Tests for GSD visualizer view renderers.
|
|
2
|
+
// Tests the pure view functions with mock data — no file I/O.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
renderProgressView,
|
|
6
|
+
renderDepsView,
|
|
7
|
+
renderMetricsView,
|
|
8
|
+
renderTimelineView,
|
|
9
|
+
} from "../visualizer-views.js";
|
|
10
|
+
import type { VisualizerData } from "../visualizer-data.js";
|
|
11
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
12
|
+
|
|
13
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
14
|
+
|
|
15
|
+
// ─── Mock theme ─────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const mockTheme = {
|
|
18
|
+
fg: (_color: string, text: string) => text,
|
|
19
|
+
bold: (text: string) => text,
|
|
20
|
+
} as any;
|
|
21
|
+
|
|
22
|
+
// ─── Test data factories ────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
function makeVisualizerData(overrides: Partial<VisualizerData> = {}): VisualizerData {
|
|
25
|
+
return {
|
|
26
|
+
milestones: [],
|
|
27
|
+
phase: "executing",
|
|
28
|
+
totals: null,
|
|
29
|
+
byPhase: [],
|
|
30
|
+
bySlice: [],
|
|
31
|
+
byModel: [],
|
|
32
|
+
units: [],
|
|
33
|
+
...overrides,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── renderProgressView ─────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
console.log("\n=== renderProgressView ===");
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
const data = makeVisualizerData({
|
|
43
|
+
milestones: [
|
|
44
|
+
{
|
|
45
|
+
id: "M001",
|
|
46
|
+
title: "First Milestone",
|
|
47
|
+
status: "active",
|
|
48
|
+
dependsOn: [],
|
|
49
|
+
slices: [
|
|
50
|
+
{
|
|
51
|
+
id: "S01",
|
|
52
|
+
title: "Core Types",
|
|
53
|
+
done: true,
|
|
54
|
+
active: false,
|
|
55
|
+
risk: "low",
|
|
56
|
+
depends: [],
|
|
57
|
+
tasks: [],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "S02",
|
|
61
|
+
title: "State Engine",
|
|
62
|
+
done: false,
|
|
63
|
+
active: true,
|
|
64
|
+
risk: "high",
|
|
65
|
+
depends: ["S01"],
|
|
66
|
+
tasks: [
|
|
67
|
+
{ id: "T01", title: "Dispatch Loop", done: false, active: true },
|
|
68
|
+
{ id: "T02", title: "Session Mgmt", done: true, active: false },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "S03",
|
|
73
|
+
title: "Dashboard",
|
|
74
|
+
done: false,
|
|
75
|
+
active: false,
|
|
76
|
+
risk: "medium",
|
|
77
|
+
depends: ["S02"],
|
|
78
|
+
tasks: [],
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "M002",
|
|
84
|
+
title: "Plugin Arch",
|
|
85
|
+
status: "pending",
|
|
86
|
+
dependsOn: ["M001"],
|
|
87
|
+
slices: [],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const lines = renderProgressView(data, mockTheme, 80);
|
|
93
|
+
assertTrue(lines.length > 0, "progress view produces output");
|
|
94
|
+
assertTrue(lines.some(l => l.includes("M001")), "shows milestone M001");
|
|
95
|
+
assertTrue(lines.some(l => l.includes("S01")), "shows slice S01");
|
|
96
|
+
assertTrue(lines.some(l => l.includes("T01")), "shows task T01 for active slice");
|
|
97
|
+
assertTrue(lines.some(l => l.includes("M002")), "shows milestone M002");
|
|
98
|
+
assertTrue(lines.some(l => l.includes("depends on M001")), "shows dependency note");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
const data = makeVisualizerData({ milestones: [] });
|
|
103
|
+
const lines = renderProgressView(data, mockTheme, 80);
|
|
104
|
+
assertEq(lines.length, 0, "empty milestones produce no lines");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ─── renderDepsView ─────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
console.log("\n=== renderDepsView ===");
|
|
110
|
+
|
|
111
|
+
{
|
|
112
|
+
const data = makeVisualizerData({
|
|
113
|
+
milestones: [
|
|
114
|
+
{
|
|
115
|
+
id: "M001",
|
|
116
|
+
title: "First",
|
|
117
|
+
status: "active",
|
|
118
|
+
dependsOn: [],
|
|
119
|
+
slices: [
|
|
120
|
+
{ id: "S01", title: "A", done: false, active: true, risk: "low", depends: [], tasks: [] },
|
|
121
|
+
{ id: "S02", title: "B", done: false, active: false, risk: "low", depends: ["S01"], tasks: [] },
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "M002",
|
|
126
|
+
title: "Second",
|
|
127
|
+
status: "pending",
|
|
128
|
+
dependsOn: ["M001"],
|
|
129
|
+
slices: [],
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const lines = renderDepsView(data, mockTheme, 80);
|
|
135
|
+
assertTrue(lines.length > 0, "deps view produces output");
|
|
136
|
+
assertTrue(lines.some(l => l.includes("M001") && l.includes("M002")), "shows milestone dep edge");
|
|
137
|
+
assertTrue(lines.some(l => l.includes("S01") && l.includes("S02")), "shows slice dep edge");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
{
|
|
141
|
+
const data = makeVisualizerData({
|
|
142
|
+
milestones: [
|
|
143
|
+
{ id: "M001", title: "Only", status: "active", dependsOn: [], slices: [] },
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const lines = renderDepsView(data, mockTheme, 80);
|
|
148
|
+
assertTrue(lines.some(l => l.includes("No milestone dependencies")), "shows no-deps message");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── renderMetricsView ──────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
console.log("\n=== renderMetricsView ===");
|
|
154
|
+
|
|
155
|
+
{
|
|
156
|
+
const data = makeVisualizerData({
|
|
157
|
+
totals: {
|
|
158
|
+
units: 5,
|
|
159
|
+
tokens: { input: 1000, output: 500, cacheRead: 200, cacheWrite: 100, total: 1800 },
|
|
160
|
+
cost: 2.50,
|
|
161
|
+
duration: 60000,
|
|
162
|
+
toolCalls: 15,
|
|
163
|
+
assistantMessages: 10,
|
|
164
|
+
userMessages: 5,
|
|
165
|
+
},
|
|
166
|
+
byPhase: [
|
|
167
|
+
{
|
|
168
|
+
phase: "execution",
|
|
169
|
+
units: 3,
|
|
170
|
+
tokens: { input: 600, output: 300, cacheRead: 100, cacheWrite: 50, total: 1050 },
|
|
171
|
+
cost: 1.50,
|
|
172
|
+
duration: 40000,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
phase: "planning",
|
|
176
|
+
units: 2,
|
|
177
|
+
tokens: { input: 400, output: 200, cacheRead: 100, cacheWrite: 50, total: 750 },
|
|
178
|
+
cost: 1.00,
|
|
179
|
+
duration: 20000,
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
byModel: [
|
|
183
|
+
{
|
|
184
|
+
model: "claude-opus-4-6",
|
|
185
|
+
units: 5,
|
|
186
|
+
tokens: { input: 1000, output: 500, cacheRead: 200, cacheWrite: 100, total: 1800 },
|
|
187
|
+
cost: 2.50,
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const lines = renderMetricsView(data, mockTheme, 80);
|
|
193
|
+
assertTrue(lines.length > 0, "metrics view produces output");
|
|
194
|
+
assertTrue(lines.some(l => l.includes("$2.50")), "shows total cost");
|
|
195
|
+
assertTrue(lines.some(l => l.includes("execution")), "shows phase name");
|
|
196
|
+
assertTrue(lines.some(l => l.includes("claude-opus-4-6")), "shows model name");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
{
|
|
200
|
+
const data = makeVisualizerData({ totals: null });
|
|
201
|
+
const lines = renderMetricsView(data, mockTheme, 80);
|
|
202
|
+
assertTrue(lines.some(l => l.includes("No metrics data")), "shows no-data message");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ─── renderTimelineView ─────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
console.log("\n=== renderTimelineView ===");
|
|
208
|
+
|
|
209
|
+
{
|
|
210
|
+
const now = Date.now();
|
|
211
|
+
const data = makeVisualizerData({
|
|
212
|
+
units: [
|
|
213
|
+
{
|
|
214
|
+
type: "execute-task",
|
|
215
|
+
id: "M001/S01/T01",
|
|
216
|
+
model: "claude-opus-4-6",
|
|
217
|
+
startedAt: now - 120000,
|
|
218
|
+
finishedAt: now - 60000,
|
|
219
|
+
tokens: { input: 500, output: 200, cacheRead: 100, cacheWrite: 50, total: 850 },
|
|
220
|
+
cost: 0.42,
|
|
221
|
+
toolCalls: 5,
|
|
222
|
+
assistantMessages: 3,
|
|
223
|
+
userMessages: 1,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
type: "plan-slice",
|
|
227
|
+
id: "M001/S02",
|
|
228
|
+
model: "claude-opus-4-6",
|
|
229
|
+
startedAt: now - 60000,
|
|
230
|
+
finishedAt: now - 30000,
|
|
231
|
+
tokens: { input: 300, output: 150, cacheRead: 50, cacheWrite: 25, total: 525 },
|
|
232
|
+
cost: 0.18,
|
|
233
|
+
toolCalls: 2,
|
|
234
|
+
assistantMessages: 2,
|
|
235
|
+
userMessages: 1,
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const lines = renderTimelineView(data, mockTheme, 80);
|
|
241
|
+
assertTrue(lines.length >= 2, "timeline view produces lines for each unit");
|
|
242
|
+
assertTrue(lines.some(l => l.includes("execute-task")), "shows unit type");
|
|
243
|
+
assertTrue(lines.some(l => l.includes("M001/S01/T01")), "shows unit id");
|
|
244
|
+
assertTrue(lines.some(l => l.includes("$0.42")), "shows unit cost");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
{
|
|
248
|
+
const data = makeVisualizerData({ units: [] });
|
|
249
|
+
const lines = renderTimelineView(data, mockTheme, 80);
|
|
250
|
+
assertTrue(lines.some(l => l.includes("No execution history")), "shows empty message");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ─── Report ─────────────────────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
report();
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Triage Resolution — Execute triage classifications
|
|
3
|
+
*
|
|
4
|
+
* Provides resolution executors for each capture classification type:
|
|
5
|
+
*
|
|
6
|
+
* - inject: appends a new task to the current slice plan
|
|
7
|
+
* - replan: writes REPLAN-TRIGGER.md so next dispatchNextUnit enters replanning-slice
|
|
8
|
+
* - defer/note: query helpers for loading deferred/replan captures
|
|
9
|
+
*
|
|
10
|
+
* Also provides detectFileOverlap() for surfacing downstream impact on quick tasks.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import type { Classification, CaptureEntry } from "./captures.js";
|
|
16
|
+
import {
|
|
17
|
+
loadPendingCaptures,
|
|
18
|
+
loadAllCaptures,
|
|
19
|
+
markCaptureResolved,
|
|
20
|
+
} from "./captures.js";
|
|
21
|
+
|
|
22
|
+
// ─── Resolution Executors ─────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Inject a new task into the current slice plan.
|
|
26
|
+
* Reads the plan, finds the highest task ID, appends a new task entry.
|
|
27
|
+
* Returns the new task ID, or null if injection failed.
|
|
28
|
+
*/
|
|
29
|
+
export function executeInject(
|
|
30
|
+
basePath: string,
|
|
31
|
+
mid: string,
|
|
32
|
+
sid: string,
|
|
33
|
+
capture: CaptureEntry,
|
|
34
|
+
): string | null {
|
|
35
|
+
try {
|
|
36
|
+
// Resolve the plan file path
|
|
37
|
+
const planPath = join(basePath, ".gsd", "milestones", mid, "slices", sid, `${sid}-PLAN.md`);
|
|
38
|
+
if (!existsSync(planPath)) return null;
|
|
39
|
+
|
|
40
|
+
const content = readFileSync(planPath, "utf-8");
|
|
41
|
+
|
|
42
|
+
// Find the highest existing task ID
|
|
43
|
+
const taskMatches = [...content.matchAll(/- \[[ x]\] \*\*T(\d+):/g)];
|
|
44
|
+
if (taskMatches.length === 0) return null;
|
|
45
|
+
|
|
46
|
+
const maxId = Math.max(...taskMatches.map(m => parseInt(m[1], 10)));
|
|
47
|
+
const newId = `T${String(maxId + 1).padStart(2, "0")}`;
|
|
48
|
+
|
|
49
|
+
// Build the new task entry
|
|
50
|
+
const newTask = [
|
|
51
|
+
`- [ ] **${newId}: ${capture.text}** \`est:30m\``,
|
|
52
|
+
` - Why: Injected from capture ${capture.id} during triage`,
|
|
53
|
+
` - Do: ${capture.text}`,
|
|
54
|
+
` - Done when: Capture intent fulfilled`,
|
|
55
|
+
].join("\n");
|
|
56
|
+
|
|
57
|
+
// Find the last task entry and append after it
|
|
58
|
+
// Look for the "## Files Likely Touched" section as the boundary
|
|
59
|
+
const filesSection = content.indexOf("## Files Likely Touched");
|
|
60
|
+
if (filesSection !== -1) {
|
|
61
|
+
const updated = content.slice(0, filesSection) + newTask + "\n\n" + content.slice(filesSection);
|
|
62
|
+
writeFileSync(planPath, updated, "utf-8");
|
|
63
|
+
} else {
|
|
64
|
+
// No Files section — append at end
|
|
65
|
+
writeFileSync(planPath, content.trimEnd() + "\n\n" + newTask + "\n", "utf-8");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return newId;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Trigger replanning by writing a REPLAN-TRIGGER.md marker file.
|
|
76
|
+
* The existing state.ts derivation detects this and sets phase to "replanning-slice".
|
|
77
|
+
* Returns true if the trigger was written successfully.
|
|
78
|
+
*/
|
|
79
|
+
export function executeReplan(
|
|
80
|
+
basePath: string,
|
|
81
|
+
mid: string,
|
|
82
|
+
sid: string,
|
|
83
|
+
capture: CaptureEntry,
|
|
84
|
+
): boolean {
|
|
85
|
+
try {
|
|
86
|
+
const triggerPath = join(
|
|
87
|
+
basePath, ".gsd", "milestones", mid, "slices", sid, `${sid}-REPLAN-TRIGGER.md`,
|
|
88
|
+
);
|
|
89
|
+
const content = [
|
|
90
|
+
`# Replan Trigger`,
|
|
91
|
+
``,
|
|
92
|
+
`**Source:** Capture ${capture.id}`,
|
|
93
|
+
`**Capture:** ${capture.text}`,
|
|
94
|
+
`**Rationale:** ${capture.rationale ?? "User-initiated replan via capture triage"}`,
|
|
95
|
+
`**Triggered:** ${new Date().toISOString()}`,
|
|
96
|
+
``,
|
|
97
|
+
`This file was created by the triage pipeline. The next dispatch cycle`,
|
|
98
|
+
`will detect it and enter the replanning-slice phase.`,
|
|
99
|
+
].join("\n");
|
|
100
|
+
|
|
101
|
+
writeFileSync(triggerPath, content, "utf-8");
|
|
102
|
+
return true;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── File Overlap Detection ───────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Detect file overlap between a capture's affected files and planned tasks.
|
|
112
|
+
*
|
|
113
|
+
* Parses the slice plan for task file references and returns task IDs
|
|
114
|
+
* whose files overlap with the capture's affected files.
|
|
115
|
+
*
|
|
116
|
+
* @param affectedFiles - Files the capture would touch
|
|
117
|
+
* @param planContent - Content of the slice plan.md
|
|
118
|
+
* @returns Array of task IDs (e.g., ["T03", "T04"]) whose files overlap
|
|
119
|
+
*/
|
|
120
|
+
export function detectFileOverlap(
|
|
121
|
+
affectedFiles: string[],
|
|
122
|
+
planContent: string,
|
|
123
|
+
): string[] {
|
|
124
|
+
if (!affectedFiles || affectedFiles.length === 0) return [];
|
|
125
|
+
|
|
126
|
+
const overlappingTasks: string[] = [];
|
|
127
|
+
|
|
128
|
+
// Normalize affected files for comparison
|
|
129
|
+
const normalizedAffected = new Set(
|
|
130
|
+
affectedFiles.map(f => f.replace(/^\.\//, "").toLowerCase()),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Parse plan for incomplete tasks and their file references
|
|
134
|
+
const taskPattern = /- \[ \] \*\*(T\d+):[^*]*\*\*/g;
|
|
135
|
+
const tasks = [...planContent.matchAll(taskPattern)];
|
|
136
|
+
|
|
137
|
+
for (const taskMatch of tasks) {
|
|
138
|
+
const taskId = taskMatch[1];
|
|
139
|
+
const taskStart = taskMatch.index!;
|
|
140
|
+
|
|
141
|
+
// Find the end of this task (next task or end of section)
|
|
142
|
+
const nextTask = planContent.indexOf("- [", taskStart + 1);
|
|
143
|
+
const sectionEnd = planContent.indexOf("##", taskStart + 1);
|
|
144
|
+
const taskEnd = Math.min(
|
|
145
|
+
nextTask === -1 ? planContent.length : nextTask,
|
|
146
|
+
sectionEnd === -1 ? planContent.length : sectionEnd,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const taskContent = planContent.slice(taskStart, taskEnd);
|
|
150
|
+
|
|
151
|
+
// Extract file references — look for backtick-quoted paths
|
|
152
|
+
const fileRefs = [...taskContent.matchAll(/`([^`]+\.[a-z]+)`/g)]
|
|
153
|
+
.map(m => m[1].replace(/^\.\//, "").toLowerCase());
|
|
154
|
+
|
|
155
|
+
// Check for overlap
|
|
156
|
+
const hasOverlap = fileRefs.some(f => normalizedAffected.has(f));
|
|
157
|
+
if (hasOverlap) {
|
|
158
|
+
overlappingTasks.push(taskId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return overlappingTasks;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Load deferred captures (classification === "defer") for injection into
|
|
167
|
+
* reassess-roadmap prompts.
|
|
168
|
+
*/
|
|
169
|
+
export function loadDeferredCaptures(basePath: string): CaptureEntry[] {
|
|
170
|
+
return loadAllCaptures(basePath).filter(c => c.classification === "defer");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Load replan-triggering captures for injection into replan-slice prompts.
|
|
175
|
+
*/
|
|
176
|
+
export function loadReplanCaptures(basePath: string): CaptureEntry[] {
|
|
177
|
+
return loadAllCaptures(basePath).filter(c => c.classification === "replan");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Build a quick-task execution prompt from a capture.
|
|
182
|
+
*/
|
|
183
|
+
export function buildQuickTaskPrompt(capture: CaptureEntry): string {
|
|
184
|
+
return [
|
|
185
|
+
`You are executing a quick one-off task captured during a GSD auto-mode session.`,
|
|
186
|
+
``,
|
|
187
|
+
`## Quick Task`,
|
|
188
|
+
``,
|
|
189
|
+
`**Capture ID:** ${capture.id}`,
|
|
190
|
+
`**Task:** ${capture.text}`,
|
|
191
|
+
``,
|
|
192
|
+
`## Instructions`,
|
|
193
|
+
``,
|
|
194
|
+
`1. Execute this task as a small, self-contained change.`,
|
|
195
|
+
`2. Do NOT modify any \`.gsd/\` plan files — this is a one-off, not a planned task.`,
|
|
196
|
+
`3. Commit your changes with a descriptive message.`,
|
|
197
|
+
`4. Keep changes minimal and focused on the capture text.`,
|
|
198
|
+
`5. When done, say: "Quick task complete."`,
|
|
199
|
+
].join("\n");
|
|
200
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Triage UI — Confirmation flow for programmatic triage results
|
|
3
|
+
*
|
|
4
|
+
* Used by auto-mode dispatch (S02) when triage fires between tasks.
|
|
5
|
+
* For manual `/gsd triage`, the LLM session handles confirmation directly.
|
|
6
|
+
*
|
|
7
|
+
* This module provides `showTriageConfirmation` which presents each
|
|
8
|
+
* triage result to the user via `showNextAction` and returns the
|
|
9
|
+
* confirmed classifications.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
13
|
+
import { showNextAction } from "../shared/next-action-ui.js";
|
|
14
|
+
import type { CaptureEntry, Classification, TriageResult } from "./captures.js";
|
|
15
|
+
import { markCaptureResolved } from "./captures.js";
|
|
16
|
+
|
|
17
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
export interface ConfirmedTriage {
|
|
20
|
+
captureId: string;
|
|
21
|
+
classification: Classification;
|
|
22
|
+
rationale: string;
|
|
23
|
+
affectedFiles?: string[];
|
|
24
|
+
targetSlice?: string;
|
|
25
|
+
userOverride: boolean; // true if user changed the proposed classification
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── Classification Labels ────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const CLASSIFICATION_LABELS: Record<Classification, { label: string; description: string }> = {
|
|
31
|
+
"quick-task": {
|
|
32
|
+
label: "Quick task",
|
|
33
|
+
description: "Execute as a one-off at the next seam — no plan modification.",
|
|
34
|
+
},
|
|
35
|
+
"inject": {
|
|
36
|
+
label: "Inject into plan",
|
|
37
|
+
description: "Add a new task to the current slice plan.",
|
|
38
|
+
},
|
|
39
|
+
"defer": {
|
|
40
|
+
label: "Defer",
|
|
41
|
+
description: "Move to a future slice or milestone — not urgent now.",
|
|
42
|
+
},
|
|
43
|
+
"replan": {
|
|
44
|
+
label: "Replan slice",
|
|
45
|
+
description: "Remaining tasks need rewriting — triggers slice replan.",
|
|
46
|
+
},
|
|
47
|
+
"note": {
|
|
48
|
+
label: "Note",
|
|
49
|
+
description: "Informational only — no action needed.",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const ALL_CLASSIFICATIONS: Classification[] = [
|
|
54
|
+
"quick-task", "inject", "defer", "replan", "note",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Present triage results to the user for confirmation.
|
|
61
|
+
*
|
|
62
|
+
* For each capture:
|
|
63
|
+
* - note/defer: auto-confirm (no user interaction needed)
|
|
64
|
+
* - quick-task/inject/replan: show confirmation UI with proposed + alternatives
|
|
65
|
+
*
|
|
66
|
+
* Returns confirmed results with final classifications.
|
|
67
|
+
* Updates CAPTURES.md with resolved status.
|
|
68
|
+
*
|
|
69
|
+
* @param fileOverlaps - Map of captureId → list of planned task IDs whose files overlap
|
|
70
|
+
*/
|
|
71
|
+
export async function showTriageConfirmation(
|
|
72
|
+
ctx: ExtensionCommandContext,
|
|
73
|
+
triageResults: TriageResult[],
|
|
74
|
+
captures: CaptureEntry[],
|
|
75
|
+
basePath: string,
|
|
76
|
+
fileOverlaps?: Map<string, string[]>,
|
|
77
|
+
): Promise<ConfirmedTriage[]> {
|
|
78
|
+
const confirmed: ConfirmedTriage[] = [];
|
|
79
|
+
const captureMap = new Map(captures.map(c => [c.id, c]));
|
|
80
|
+
|
|
81
|
+
for (const result of triageResults) {
|
|
82
|
+
const capture = captureMap.get(result.captureId);
|
|
83
|
+
if (!capture) continue;
|
|
84
|
+
|
|
85
|
+
// Auto-confirm note and defer — low-impact, no plan modification
|
|
86
|
+
if (result.classification === "note" || result.classification === "defer") {
|
|
87
|
+
const resolution = result.classification === "note"
|
|
88
|
+
? "acknowledged as note"
|
|
89
|
+
: `deferred${result.targetSlice ? ` to ${result.targetSlice}` : ""}`;
|
|
90
|
+
|
|
91
|
+
markCaptureResolved(
|
|
92
|
+
basePath,
|
|
93
|
+
result.captureId,
|
|
94
|
+
result.classification,
|
|
95
|
+
resolution,
|
|
96
|
+
result.rationale,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
confirmed.push({
|
|
100
|
+
captureId: result.captureId,
|
|
101
|
+
classification: result.classification,
|
|
102
|
+
rationale: result.rationale,
|
|
103
|
+
affectedFiles: result.affectedFiles,
|
|
104
|
+
targetSlice: result.targetSlice,
|
|
105
|
+
userOverride: false,
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Build summary lines for the confirmation UI
|
|
111
|
+
const summary: string[] = [
|
|
112
|
+
`"${capture.text}"`,
|
|
113
|
+
"",
|
|
114
|
+
`Proposed: **${CLASSIFICATION_LABELS[result.classification].label}** — ${result.rationale}`,
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
// Add file overlap warning if present
|
|
118
|
+
const overlaps = fileOverlaps?.get(result.captureId);
|
|
119
|
+
if (overlaps && overlaps.length > 0) {
|
|
120
|
+
summary.push("");
|
|
121
|
+
summary.push(`⚠ Touches files planned for ${overlaps.join(", ")} — consider inject or defer`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (result.affectedFiles && result.affectedFiles.length > 0) {
|
|
125
|
+
summary.push("");
|
|
126
|
+
summary.push(`Files: ${result.affectedFiles.join(", ")}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Build action options — proposed first (recommended), then alternatives
|
|
130
|
+
const proposed = result.classification;
|
|
131
|
+
const actions = ALL_CLASSIFICATIONS.map(cls => ({
|
|
132
|
+
id: cls,
|
|
133
|
+
label: CLASSIFICATION_LABELS[cls].label,
|
|
134
|
+
description: CLASSIFICATION_LABELS[cls].description,
|
|
135
|
+
recommended: cls === proposed,
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
const choice = await showNextAction(ctx as any, {
|
|
139
|
+
title: `Triage: ${result.captureId}`,
|
|
140
|
+
summary,
|
|
141
|
+
actions,
|
|
142
|
+
notYetMessage: "Capture will remain pending for later triage.",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (choice === "not_yet") {
|
|
146
|
+
// User skipped — leave capture pending
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const finalClassification = choice as Classification;
|
|
151
|
+
const userOverride = finalClassification !== proposed;
|
|
152
|
+
const resolution = userOverride
|
|
153
|
+
? `user chose ${finalClassification} (was ${proposed})`
|
|
154
|
+
: `confirmed as ${finalClassification}`;
|
|
155
|
+
|
|
156
|
+
markCaptureResolved(
|
|
157
|
+
basePath,
|
|
158
|
+
result.captureId,
|
|
159
|
+
finalClassification,
|
|
160
|
+
resolution,
|
|
161
|
+
userOverride ? `User override: ${result.rationale}` : result.rationale,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
confirmed.push({
|
|
165
|
+
captureId: result.captureId,
|
|
166
|
+
classification: finalClassification,
|
|
167
|
+
rationale: result.rationale,
|
|
168
|
+
affectedFiles: result.affectedFiles,
|
|
169
|
+
targetSlice: result.targetSlice,
|
|
170
|
+
userOverride,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return confirmed;
|
|
175
|
+
}
|