gsd-pi 2.24.0 → 2.26.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 +13 -3
- package/dist/headless.js +24 -4
- package/dist/models-resolver.d.ts +0 -11
- package/dist/models-resolver.js +0 -15
- package/dist/resource-loader.d.ts +0 -1
- package/dist/resource-loader.js +0 -9
- package/dist/resources/GSD-WORKFLOW.md +12 -9
- package/dist/resources/extensions/async-jobs/index.ts +9 -1
- package/dist/resources/extensions/bg-shell/index.ts +3 -2
- package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
- package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
- package/dist/resources/extensions/gsd/activity-log.ts +5 -3
- package/dist/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +7 -4
- package/dist/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/dist/resources/extensions/gsd/auto.ts +265 -48
- package/dist/resources/extensions/gsd/cache.ts +3 -1
- package/dist/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/dist/resources/extensions/gsd/doctor.ts +26 -1
- package/dist/resources/extensions/gsd/files.ts +13 -2
- package/dist/resources/extensions/gsd/git-service.ts +74 -14
- package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +54 -22
- package/dist/resources/extensions/gsd/index.ts +62 -8
- package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/dist/resources/extensions/gsd/memory-store.ts +441 -0
- package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
- package/dist/resources/extensions/gsd/migrate/writer.ts +39 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/dist/resources/extensions/gsd/preferences.ts +2 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/dist/resources/extensions/gsd/prompts/discuss.md +5 -5
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +3 -3
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/dist/resources/extensions/gsd/state.ts +17 -6
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
- package/dist/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
- package/dist/resources/extensions/gsd/types.ts +2 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/dist/resources/extensions/gsd/worktree.ts +9 -2
- package/dist/resources/extensions/search-the-web/native-search.ts +19 -5
- package/dist/resources/extensions/shared/path-display.ts +19 -0
- package/package.json +1 -6
- package/packages/pi-agent-core/dist/agent-loop.js +2 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +64 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/mistral.js +3 -0
- package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +23 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +65 -1
- package/packages/pi-ai/src/providers/mistral.ts +3 -0
- package/packages/pi-ai/src/types.ts +19 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +12 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +7 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +8 -3
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -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 +8 -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/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +2 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +5 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +41 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +301 -62
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +5 -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 +135 -30
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts +8 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js +60 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js +32 -6
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts +34 -0
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js +36 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js.map +1 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -0
- package/packages/pi-coding-agent/src/core/keybindings.ts +1 -1
- package/packages/pi-coding-agent/src/core/lsp/client.ts +11 -1
- package/packages/pi-coding-agent/src/core/lsp/index.ts +7 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +2 -1
- package/packages/pi-coding-agent/src/index.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +347 -62
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +124 -4
- package/packages/pi-coding-agent/src/tests/path-display.test.ts +85 -0
- package/packages/pi-coding-agent/src/utils/clipboard-image.ts +33 -6
- package/packages/pi-coding-agent/src/utils/path-display.ts +36 -0
- package/src/resources/GSD-WORKFLOW.md +12 -9
- package/src/resources/extensions/async-jobs/index.ts +9 -1
- package/src/resources/extensions/bg-shell/index.ts +3 -2
- package/src/resources/extensions/bg-shell/overlay.ts +18 -17
- package/src/resources/extensions/get-secrets-from-user.ts +5 -23
- package/src/resources/extensions/gsd/activity-log.ts +5 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +7 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/src/resources/extensions/gsd/auto.ts +265 -48
- package/src/resources/extensions/gsd/cache.ts +3 -1
- package/src/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/src/resources/extensions/gsd/doctor.ts +26 -1
- package/src/resources/extensions/gsd/files.ts +13 -2
- package/src/resources/extensions/gsd/git-service.ts +74 -14
- package/src/resources/extensions/gsd/gsd-db.ts +78 -1
- package/src/resources/extensions/gsd/guided-flow.ts +54 -22
- package/src/resources/extensions/gsd/index.ts +62 -8
- package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/src/resources/extensions/gsd/memory-store.ts +441 -0
- package/src/resources/extensions/gsd/migrate/command.ts +2 -2
- package/src/resources/extensions/gsd/migrate/writer.ts +39 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/src/resources/extensions/gsd/preferences.ts +2 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/src/resources/extensions/gsd/prompts/discuss.md +5 -5
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +3 -3
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/src/resources/extensions/gsd/state.ts +17 -6
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/src/resources/extensions/gsd/triage-ui.ts +1 -1
- package/src/resources/extensions/gsd/types.ts +2 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/src/resources/extensions/gsd/worktree.ts +9 -2
- package/src/resources/extensions/search-the-web/native-search.ts +19 -5
- package/src/resources/extensions/shared/path-display.ts +19 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Theme } from "@gsd/pi-coding-agent";
|
|
4
4
|
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
5
|
-
import type { VisualizerData, VisualizerMilestone } from "./visualizer-data.js";
|
|
5
|
+
import type { VisualizerData, VisualizerMilestone, SliceVerification, VisualizerSliceActivity, VisualizerStats, VisualizerSliceRef } from "./visualizer-data.js";
|
|
6
6
|
import { formatCost, formatTokenCount, classifyUnitPhase } from "./metrics.js";
|
|
7
7
|
|
|
8
8
|
// ─── Local Helpers ───────────────────────────────────────────────────────────
|
|
@@ -34,12 +34,103 @@ function joinColumns(left: string, right: string, width: number): string {
|
|
|
34
34
|
|
|
35
35
|
function sparkline(values: number[]): string {
|
|
36
36
|
if (values.length === 0) return "";
|
|
37
|
-
const chars = "
|
|
37
|
+
const chars = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
38
38
|
const max = Math.max(...values);
|
|
39
39
|
if (max === 0) return chars[0].repeat(values.length);
|
|
40
40
|
return values.map(v => chars[Math.min(7, Math.floor((v / max) * 7))]).join("");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function formatCompletionDate(input: string): string {
|
|
44
|
+
if (!input) return "unknown";
|
|
45
|
+
const parsed = new Date(input);
|
|
46
|
+
if (Number.isNaN(parsed.getTime())) return input;
|
|
47
|
+
return parsed.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sliceLabel(slice: VisualizerSliceRef): string {
|
|
51
|
+
return `${slice.milestoneId}/${slice.sliceId}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderFeatureStats(data: VisualizerData, th: Theme, width: number): string[] {
|
|
55
|
+
const stats = data.stats;
|
|
56
|
+
const lines: string[] = [];
|
|
57
|
+
lines.push(th.fg("accent", th.bold("Feature Snapshot")));
|
|
58
|
+
lines.push("");
|
|
59
|
+
|
|
60
|
+
const missingLabel = `Missing slices: ${th.fg("warning", String(stats.missingCount))}`;
|
|
61
|
+
lines.push(truncateToWidth(` ${missingLabel}`, width));
|
|
62
|
+
if (stats.missingSlices.length > 0) {
|
|
63
|
+
for (const slice of stats.missingSlices) {
|
|
64
|
+
const row = ` ${th.fg("dim", sliceLabel(slice))} ${slice.title}`;
|
|
65
|
+
lines.push(truncateToWidth(row, width));
|
|
66
|
+
}
|
|
67
|
+
const remaining = stats.missingCount - stats.missingSlices.length;
|
|
68
|
+
if (remaining > 0) {
|
|
69
|
+
lines.push(truncateToWidth(` ... and ${remaining} more`, width));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
lines.push("");
|
|
74
|
+
const updatedLabel = `Updated (last 7 days): ${th.fg("accent", String(stats.updatedCount))}`;
|
|
75
|
+
lines.push(truncateToWidth(` ${updatedLabel}`, width));
|
|
76
|
+
if (stats.updatedSlices.length > 0) {
|
|
77
|
+
for (const slice of stats.updatedSlices) {
|
|
78
|
+
const when = formatCompletionDate(slice.completedAt);
|
|
79
|
+
const row = ` ${th.fg("text", sliceLabel(slice))} ${th.fg("dim", when)} ${slice.title}`;
|
|
80
|
+
lines.push(truncateToWidth(row, width));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
lines.push("");
|
|
85
|
+
lines.push(truncateToWidth(` Recent completions: ${th.fg("success", String(stats.recentEntries.length))}`, width));
|
|
86
|
+
for (const entry of stats.recentEntries) {
|
|
87
|
+
const when = formatCompletionDate(entry.completedAt);
|
|
88
|
+
const row = ` ${th.fg("text", entry.sliceId)} — ${entry.oneLiner || entry.title} ${th.fg("dim", when)}`;
|
|
89
|
+
lines.push(truncateToWidth(row, width));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
lines.push("");
|
|
93
|
+
return lines;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function renderDiscussionStatus(data: VisualizerData, th: Theme, width: number): string[] {
|
|
97
|
+
const states = data.discussion;
|
|
98
|
+
if (states.length === 0) return [];
|
|
99
|
+
|
|
100
|
+
const counts = {
|
|
101
|
+
discussed: 0,
|
|
102
|
+
draft: 0,
|
|
103
|
+
undiscussed: 0,
|
|
104
|
+
};
|
|
105
|
+
for (const state of states) counts[state.state]++;
|
|
106
|
+
|
|
107
|
+
const lines: string[] = [];
|
|
108
|
+
lines.push(th.fg("accent", th.bold("Discussion Status")));
|
|
109
|
+
lines.push("");
|
|
110
|
+
const summary = ` Discussed: ${th.fg("success", String(counts.discussed))} Draft: ${th.fg("warning", String(counts.draft))} Pending: ${th.fg("dim", String(counts.undiscussed))}`;
|
|
111
|
+
lines.push(truncateToWidth(summary, width));
|
|
112
|
+
lines.push("");
|
|
113
|
+
|
|
114
|
+
for (const state of states) {
|
|
115
|
+
const badge =
|
|
116
|
+
state.state === "discussed"
|
|
117
|
+
? th.fg("success", "Discussed")
|
|
118
|
+
: state.state === "draft"
|
|
119
|
+
? th.fg("warning", "Draft")
|
|
120
|
+
: th.fg("dim", "Pending");
|
|
121
|
+
const when = state.lastUpdated ? ` ${th.fg("dim", formatCompletionDate(state.lastUpdated))}` : "";
|
|
122
|
+
const row = ` ${th.fg("text", state.milestoneId)} ${badge} ${state.title}${when}`;
|
|
123
|
+
lines.push(truncateToWidth(row, width));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
lines.push("");
|
|
127
|
+
return lines;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function findVerification(data: VisualizerData, milestoneId: string, sliceId: string): SliceVerification | undefined {
|
|
131
|
+
return data.sliceVerifications.find(v => v.milestoneId === milestoneId && v.sliceId === sliceId);
|
|
132
|
+
}
|
|
133
|
+
|
|
43
134
|
// ─── Progress View ───────────────────────────────────────────────────────────
|
|
44
135
|
|
|
45
136
|
export interface ProgressFilter {
|
|
@@ -52,6 +143,7 @@ export function renderProgressView(
|
|
|
52
143
|
th: Theme,
|
|
53
144
|
width: number,
|
|
54
145
|
filter?: ProgressFilter,
|
|
146
|
+
collapsed?: Set<string>,
|
|
55
147
|
): string[] {
|
|
56
148
|
const lines: string[] = [];
|
|
57
149
|
|
|
@@ -65,6 +157,9 @@ export function renderProgressView(
|
|
|
65
157
|
lines.push("");
|
|
66
158
|
}
|
|
67
159
|
|
|
160
|
+
lines.push(...renderFeatureStats(data, th, width));
|
|
161
|
+
lines.push(...renderDiscussionStatus(data, th, width));
|
|
162
|
+
|
|
68
163
|
for (const ms of data.milestones) {
|
|
69
164
|
// Apply filter to milestones
|
|
70
165
|
if (filter && filter.text) {
|
|
@@ -75,20 +170,25 @@ export function renderProgressView(
|
|
|
75
170
|
// Milestone header line
|
|
76
171
|
const statusGlyph =
|
|
77
172
|
ms.status === "complete"
|
|
78
|
-
? th.fg("success", "
|
|
173
|
+
? th.fg("success", "\u2713")
|
|
79
174
|
: ms.status === "active"
|
|
80
|
-
? th.fg("accent", "
|
|
81
|
-
: th.fg("dim", "
|
|
175
|
+
? th.fg("accent", "\u25b8")
|
|
176
|
+
: th.fg("dim", "\u25cb");
|
|
82
177
|
const statusLabel =
|
|
83
178
|
ms.status === "complete"
|
|
84
179
|
? th.fg("success", "complete")
|
|
85
180
|
: ms.status === "active"
|
|
86
181
|
? th.fg("accent", "active")
|
|
87
182
|
: th.fg("dim", "pending");
|
|
88
|
-
|
|
183
|
+
|
|
184
|
+
const collapseIndicator = collapsed?.has(ms.id) ? "[+] " : "";
|
|
185
|
+
const msLeft = `${collapseIndicator}${ms.id}: ${ms.title}`;
|
|
89
186
|
const msRight = `${statusGlyph} ${statusLabel}`;
|
|
90
187
|
lines.push(joinColumns(msLeft, msRight, width));
|
|
91
188
|
|
|
189
|
+
// If collapsed, skip rendering slices/tasks
|
|
190
|
+
if (collapsed?.has(ms.id)) continue;
|
|
191
|
+
|
|
92
192
|
if (ms.slices.length === 0 && ms.dependsOn.length > 0) {
|
|
93
193
|
lines.push(th.fg("dim", ` (depends on ${ms.dependsOn.join(", ")})`));
|
|
94
194
|
continue;
|
|
@@ -107,10 +207,10 @@ export function renderProgressView(
|
|
|
107
207
|
|
|
108
208
|
// Slice line
|
|
109
209
|
const slGlyph = sl.done
|
|
110
|
-
? th.fg("success", "
|
|
210
|
+
? th.fg("success", "\u2713")
|
|
111
211
|
: sl.active
|
|
112
|
-
? th.fg("accent", "
|
|
113
|
-
: th.fg("dim", "
|
|
212
|
+
? th.fg("accent", "\u25b8")
|
|
213
|
+
: th.fg("dim", "\u25cb");
|
|
114
214
|
const riskColor =
|
|
115
215
|
sl.risk === "high"
|
|
116
216
|
? "warning"
|
|
@@ -118,18 +218,36 @@ export function renderProgressView(
|
|
|
118
218
|
? "text"
|
|
119
219
|
: "dim";
|
|
120
220
|
const riskBadge = th.fg(riskColor, sl.risk);
|
|
121
|
-
|
|
221
|
+
|
|
222
|
+
// Verification badge
|
|
223
|
+
const ver = findVerification(data, ms.id, sl.id);
|
|
224
|
+
let verBadge = "";
|
|
225
|
+
if (ver) {
|
|
226
|
+
if (ver.verificationResult === "passed") {
|
|
227
|
+
verBadge = " " + th.fg("success", "\u2713");
|
|
228
|
+
} else if (ver.verificationResult === "failed") {
|
|
229
|
+
verBadge = " " + th.fg("error", "\u2717");
|
|
230
|
+
} else if (ver.verificationResult === "untested" || ver.verificationResult === "") {
|
|
231
|
+
verBadge = " " + th.fg("dim", "?");
|
|
232
|
+
}
|
|
233
|
+
if (ver.blockerDiscovered) {
|
|
234
|
+
verBadge += " " + th.fg("warning", "\u26a0");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const slLeft = ` ${slGlyph} ${sl.id}: ${sl.title}${verBadge}`;
|
|
122
239
|
lines.push(joinColumns(slLeft, riskBadge, width));
|
|
123
240
|
|
|
124
241
|
// Show tasks for active slice
|
|
125
242
|
if (sl.active && sl.tasks.length > 0) {
|
|
126
243
|
for (const task of sl.tasks) {
|
|
127
244
|
const tGlyph = task.done
|
|
128
|
-
? th.fg("success", "
|
|
245
|
+
? th.fg("success", "\u2713")
|
|
129
246
|
: task.active
|
|
130
|
-
? th.fg("accent", "
|
|
131
|
-
: th.fg("dim", "
|
|
132
|
-
|
|
247
|
+
? th.fg("accent", "\u25b8")
|
|
248
|
+
: th.fg("dim", "\u25cb");
|
|
249
|
+
const estimateStr = task.estimate ? th.fg("dim", ` (${task.estimate})`) : "";
|
|
250
|
+
lines.push(` ${tGlyph} ${task.id}: ${task.title}${estimateStr}`);
|
|
133
251
|
}
|
|
134
252
|
}
|
|
135
253
|
}
|
|
@@ -176,7 +294,7 @@ function renderRiskHeatmap(data: VisualizerData, th: Theme, width: number): stri
|
|
|
176
294
|
if (ms.slices.length === 0) continue;
|
|
177
295
|
const blocks = ms.slices.map(s => {
|
|
178
296
|
const color = s.risk === "high" ? "error" : s.risk === "medium" ? "warning" : "success";
|
|
179
|
-
return th.fg(color, "
|
|
297
|
+
return th.fg(color, "\u2588\u2588");
|
|
180
298
|
});
|
|
181
299
|
const row = ` ${padRight(ms.id, 6)} ${blocks.join(" ")}`;
|
|
182
300
|
lines.push(truncateToWidth(row, width));
|
|
@@ -184,7 +302,7 @@ function renderRiskHeatmap(data: VisualizerData, th: Theme, width: number): stri
|
|
|
184
302
|
|
|
185
303
|
lines.push("");
|
|
186
304
|
lines.push(
|
|
187
|
-
` ${th.fg("success", "
|
|
305
|
+
` ${th.fg("success", "\u2588\u2588")} low ${th.fg("warning", "\u2588\u2588")} med ${th.fg("error", "\u2588\u2588")} high`,
|
|
188
306
|
);
|
|
189
307
|
|
|
190
308
|
// Summary counts
|
|
@@ -230,7 +348,7 @@ export function renderDepsView(
|
|
|
230
348
|
for (const ms of msDeps) {
|
|
231
349
|
for (const dep of ms.dependsOn) {
|
|
232
350
|
lines.push(
|
|
233
|
-
` ${th.fg("text", dep)} ${th.fg("accent", "
|
|
351
|
+
` ${th.fg("text", dep)} ${th.fg("accent", "\u2500\u2500\u25ba")} ${th.fg("text", ms.id)}`,
|
|
234
352
|
);
|
|
235
353
|
}
|
|
236
354
|
}
|
|
@@ -253,7 +371,7 @@ export function renderDepsView(
|
|
|
253
371
|
for (const sl of slDeps) {
|
|
254
372
|
for (const dep of sl.depends) {
|
|
255
373
|
lines.push(
|
|
256
|
-
` ${th.fg("text", dep)} ${th.fg("accent", "
|
|
374
|
+
` ${th.fg("text", dep)} ${th.fg("accent", "\u2500\u2500\u25ba")} ${th.fg("text", sl.id)}`,
|
|
257
375
|
);
|
|
258
376
|
}
|
|
259
377
|
}
|
|
@@ -265,6 +383,37 @@ export function renderDepsView(
|
|
|
265
383
|
// Critical Path section
|
|
266
384
|
lines.push(...renderCriticalPath(data, th, width));
|
|
267
385
|
|
|
386
|
+
// Data Flow section from slice verifications
|
|
387
|
+
lines.push("");
|
|
388
|
+
lines.push(...renderDataFlow(data, th));
|
|
389
|
+
|
|
390
|
+
return lines;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ─── Data Flow ───────────────────────────────────────────────────────────────
|
|
394
|
+
|
|
395
|
+
function renderDataFlow(data: VisualizerData, th: Theme): string[] {
|
|
396
|
+
const lines: string[] = [];
|
|
397
|
+
const versWithProvides = data.sliceVerifications.filter(v => v.provides.length > 0);
|
|
398
|
+
const versWithRequires = data.sliceVerifications.filter(v => v.requires.length > 0);
|
|
399
|
+
|
|
400
|
+
if (versWithProvides.length === 0 && versWithRequires.length === 0) return lines;
|
|
401
|
+
|
|
402
|
+
lines.push(th.fg("accent", th.bold("Data Flow")));
|
|
403
|
+
lines.push("");
|
|
404
|
+
|
|
405
|
+
for (const v of versWithProvides) {
|
|
406
|
+
for (const artifact of v.provides) {
|
|
407
|
+
lines.push(` ${th.fg("text", v.sliceId)} ${th.fg("accent", "\u2500\u2500\u25ba")} ${th.fg("dim", `[${artifact}]`)}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
for (const v of versWithRequires) {
|
|
412
|
+
for (const req of v.requires) {
|
|
413
|
+
lines.push(` ${th.fg("dim", `[${req.provides}]`)} ${th.fg("accent", "\u25c4\u2500\u2500")} ${th.fg("text", req.slice)}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
268
417
|
return lines;
|
|
269
418
|
}
|
|
270
419
|
|
|
@@ -284,10 +433,9 @@ function renderCriticalPath(data: VisualizerData, th: Theme, _width: number): st
|
|
|
284
433
|
|
|
285
434
|
// Milestone chain
|
|
286
435
|
const chain = cp.milestonePath.map(id => {
|
|
287
|
-
const ms = data.milestones.find(m => m.id === id);
|
|
288
436
|
const badge = th.fg("error", "[CRITICAL]");
|
|
289
437
|
return `${id} ${badge}`;
|
|
290
|
-
}).join(` ${th.fg("accent", "
|
|
438
|
+
}).join(` ${th.fg("accent", "\u2500\u2500\u25ba")} `);
|
|
291
439
|
lines.push(` ${chain}`);
|
|
292
440
|
lines.push("");
|
|
293
441
|
|
|
@@ -304,7 +452,7 @@ function renderCriticalPath(data: VisualizerData, th: Theme, _width: number): st
|
|
|
304
452
|
lines.push(th.fg("accent", th.bold("Slice Critical Path")));
|
|
305
453
|
lines.push("");
|
|
306
454
|
|
|
307
|
-
const sliceChain = cp.slicePath.join(` ${th.fg("accent", "
|
|
455
|
+
const sliceChain = cp.slicePath.join(` ${th.fg("accent", "\u2500\u2500\u25ba")} `);
|
|
308
456
|
lines.push(` ${sliceChain}`);
|
|
309
457
|
|
|
310
458
|
// Bottleneck warnings
|
|
@@ -313,7 +461,7 @@ function renderCriticalPath(data: VisualizerData, th: Theme, _width: number): st
|
|
|
313
461
|
for (const sid of cp.slicePath) {
|
|
314
462
|
const sl = activeMs.slices.find(s => s.id === sid);
|
|
315
463
|
if (sl && !sl.done && !sl.active) {
|
|
316
|
-
lines.push(th.fg("warning", `
|
|
464
|
+
lines.push(th.fg("warning", ` \u26a0 ${sid}: critical but not yet started`));
|
|
317
465
|
}
|
|
318
466
|
}
|
|
319
467
|
}
|
|
@@ -347,6 +495,10 @@ export function renderMetricsView(
|
|
|
347
495
|
`Tokens: ${th.fg("text", formatTokenCount(totals.tokens.total))} ` +
|
|
348
496
|
`Units: ${th.fg("text", String(totals.units))}`,
|
|
349
497
|
);
|
|
498
|
+
lines.push(
|
|
499
|
+
` Tools: ${th.fg("text", String(totals.toolCalls))} ` +
|
|
500
|
+
`Messages: ${th.fg("text", String(totals.assistantMessages))} sent / ${th.fg("text", String(totals.userMessages))} received`,
|
|
501
|
+
);
|
|
350
502
|
lines.push("");
|
|
351
503
|
|
|
352
504
|
const barWidth = Math.max(10, width - 40);
|
|
@@ -365,8 +517,8 @@ export function renderMetricsView(
|
|
|
365
517
|
? Math.round((phase.cost / maxPhaseCost) * barWidth)
|
|
366
518
|
: 0;
|
|
367
519
|
const bar =
|
|
368
|
-
th.fg("accent", "
|
|
369
|
-
th.fg("dim", "
|
|
520
|
+
th.fg("accent", "\u2588".repeat(fillLen)) +
|
|
521
|
+
th.fg("dim", "\u2591".repeat(barWidth - fillLen));
|
|
370
522
|
const label = padRight(phase.phase, 14);
|
|
371
523
|
const costStr = formatCost(phase.cost);
|
|
372
524
|
const pctStr = `${pct.toFixed(1)}%`;
|
|
@@ -391,8 +543,8 @@ export function renderMetricsView(
|
|
|
391
543
|
? Math.round((model.cost / maxModelCost) * barWidth)
|
|
392
544
|
: 0;
|
|
393
545
|
const bar =
|
|
394
|
-
th.fg("accent", "
|
|
395
|
-
th.fg("dim", "
|
|
546
|
+
th.fg("accent", "\u2588".repeat(fillLen)) +
|
|
547
|
+
th.fg("dim", "\u2591".repeat(barWidth - fillLen));
|
|
396
548
|
const label = padRight(model.model, 20);
|
|
397
549
|
const costStr = formatCost(model.cost);
|
|
398
550
|
const pctStr = `${pct.toFixed(1)}%`;
|
|
@@ -402,6 +554,36 @@ export function renderMetricsView(
|
|
|
402
554
|
lines.push("");
|
|
403
555
|
}
|
|
404
556
|
|
|
557
|
+
// By Tier
|
|
558
|
+
if (data.byTier.length > 0) {
|
|
559
|
+
lines.push(th.fg("accent", th.bold("By Tier")));
|
|
560
|
+
lines.push("");
|
|
561
|
+
|
|
562
|
+
const maxTierCost = Math.max(...data.byTier.map((t) => t.cost));
|
|
563
|
+
|
|
564
|
+
for (const tier of data.byTier) {
|
|
565
|
+
const pct = totals.cost > 0 ? (tier.cost / totals.cost) * 100 : 0;
|
|
566
|
+
const fillLen =
|
|
567
|
+
maxTierCost > 0
|
|
568
|
+
? Math.round((tier.cost / maxTierCost) * barWidth)
|
|
569
|
+
: 0;
|
|
570
|
+
const bar =
|
|
571
|
+
th.fg("accent", "\u2588".repeat(fillLen)) +
|
|
572
|
+
th.fg("dim", "\u2591".repeat(barWidth - fillLen));
|
|
573
|
+
const label = padRight(tier.tier, 12);
|
|
574
|
+
const costStr = formatCost(tier.cost);
|
|
575
|
+
const pctStr = `${pct.toFixed(1)}%`;
|
|
576
|
+
const unitsStr = `${tier.units} units`;
|
|
577
|
+
lines.push(` ${label} ${bar} ${costStr} ${pctStr} ${unitsStr}`);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (data.tierSavingsLine) {
|
|
581
|
+
lines.push(` ${th.fg("success", data.tierSavingsLine)}`);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
lines.push("");
|
|
585
|
+
}
|
|
586
|
+
|
|
405
587
|
// Cost Projections
|
|
406
588
|
lines.push(...renderCostProjections(data, th, width));
|
|
407
589
|
|
|
@@ -432,7 +614,7 @@ function renderCostProjections(data: VisualizerData, th: Theme, _width: number):
|
|
|
432
614
|
lines.push(` Avg cost/slice: ${th.fg("text", formatCost(avgCostPerSlice))}`);
|
|
433
615
|
lines.push(
|
|
434
616
|
` Projected remaining: ${th.fg("text", formatCost(projectedRemaining))} ` +
|
|
435
|
-
`(${formatCost(avgCostPerSlice)}/slice
|
|
617
|
+
`(${formatCost(avgCostPerSlice)}/slice \u00d7 ${data.remainingSliceCount} remaining)`,
|
|
436
618
|
);
|
|
437
619
|
|
|
438
620
|
// Burn rate
|
|
@@ -448,10 +630,10 @@ function renderCostProjections(data: VisualizerData, th: Theme, _width: number):
|
|
|
448
630
|
lines.push(` Cost trend: ${spark}`);
|
|
449
631
|
}
|
|
450
632
|
|
|
451
|
-
// Budget warning: projected total >
|
|
633
|
+
// Budget warning: projected total > 2x current spend
|
|
452
634
|
const projectedTotal = data.totals.cost + projectedRemaining;
|
|
453
635
|
if (projectedTotal > 2 * data.totals.cost && data.remainingSliceCount > 0) {
|
|
454
|
-
lines.push(th.fg("warning", `
|
|
636
|
+
lines.push(th.fg("warning", ` \u26a0 Projected total ${formatCost(projectedTotal)} exceeds 2\u00d7 current spend`));
|
|
455
637
|
}
|
|
456
638
|
|
|
457
639
|
return lines;
|
|
@@ -479,6 +661,10 @@ export function renderTimelineView(
|
|
|
479
661
|
return renderTimelineList(data, th, width);
|
|
480
662
|
}
|
|
481
663
|
|
|
664
|
+
function shortenModel(model: string): string {
|
|
665
|
+
return model.replace(/^claude-/, "").slice(0, 12);
|
|
666
|
+
}
|
|
667
|
+
|
|
482
668
|
function renderTimelineList(data: VisualizerData, th: Theme, width: number): string[] {
|
|
483
669
|
const lines: string[] = [];
|
|
484
670
|
|
|
@@ -499,8 +685,8 @@ function renderTimelineList(data: VisualizerData, th: Theme, width: number): str
|
|
|
499
685
|
const duration = unit.finishedAt - unit.startedAt;
|
|
500
686
|
const glyph =
|
|
501
687
|
unit.finishedAt > 0
|
|
502
|
-
? th.fg("success", "
|
|
503
|
-
: th.fg("accent", "
|
|
688
|
+
? th.fg("success", "\u2713")
|
|
689
|
+
: th.fg("accent", "\u25b8");
|
|
504
690
|
|
|
505
691
|
const typeLabel = padRight(unit.type, 16);
|
|
506
692
|
const idLabel = padRight(unit.id, 14);
|
|
@@ -510,13 +696,18 @@ function renderTimelineList(data: VisualizerData, th: Theme, width: number): str
|
|
|
510
696
|
? Math.round((duration / maxDuration) * timeBarWidth)
|
|
511
697
|
: 0;
|
|
512
698
|
const bar =
|
|
513
|
-
th.fg("accent", "
|
|
514
|
-
th.fg("dim", "
|
|
699
|
+
th.fg("accent", "\u2588".repeat(fillLen)) +
|
|
700
|
+
th.fg("dim", "\u2591".repeat(timeBarWidth - fillLen));
|
|
515
701
|
|
|
516
702
|
const durStr = formatDuration(duration);
|
|
517
703
|
const costStr = formatCost(unit.cost);
|
|
518
704
|
|
|
519
|
-
|
|
705
|
+
// Tier and model info
|
|
706
|
+
const tierLabel = unit.tier ? th.fg("dim", `[${unit.tier}]`) : "";
|
|
707
|
+
const modelLabel = th.fg("dim", shortenModel(unit.model));
|
|
708
|
+
const tierModelPart = [tierLabel, modelLabel].filter(Boolean).join(" ");
|
|
709
|
+
|
|
710
|
+
const line = ` ${time} ${glyph} ${typeLabel} ${tierModelPart} ${idLabel} ${bar} ${durStr} ${costStr}`;
|
|
520
711
|
lines.push(truncateToWidth(line, width));
|
|
521
712
|
}
|
|
522
713
|
|
|
@@ -554,7 +745,7 @@ function renderGanttView(data: VisualizerData, th: Theme, width: number): string
|
|
|
554
745
|
for (const unit of recent) {
|
|
555
746
|
const phase = classifyUnitPhase(unit.type);
|
|
556
747
|
if (phase !== lastPhase && lastPhase !== "") {
|
|
557
|
-
lines.push(th.fg("dim", " " + "
|
|
748
|
+
lines.push(th.fg("dim", " " + "\u2500".repeat(width - 4)));
|
|
558
749
|
}
|
|
559
750
|
lastPhase = phase;
|
|
560
751
|
|
|
@@ -571,11 +762,12 @@ function renderGanttView(data: VisualizerData, th: Theme, width: number): string
|
|
|
571
762
|
|
|
572
763
|
const barStr =
|
|
573
764
|
" ".repeat(startPos) +
|
|
574
|
-
th.fg(phaseColor, "
|
|
765
|
+
th.fg(phaseColor, "\u2588".repeat(barLen)) +
|
|
575
766
|
" ".repeat(Math.max(0, barArea - startPos - barLen));
|
|
576
767
|
|
|
768
|
+
const tierTag = unit.tier ? `[${unit.tier[0]}]` : "";
|
|
577
769
|
const gutter = padRight(
|
|
578
|
-
truncateToWidth(`${unit.type.slice(0, 8)} ${unit.id}`, gutterWidth - 1),
|
|
770
|
+
truncateToWidth(`${unit.type.slice(0, 8)} ${unit.id}${tierTag}`, gutterWidth - 1),
|
|
579
771
|
gutterWidth,
|
|
580
772
|
);
|
|
581
773
|
|
|
@@ -611,10 +803,10 @@ export function renderAgentView(
|
|
|
611
803
|
|
|
612
804
|
// Status line
|
|
613
805
|
const statusDot = activity.active
|
|
614
|
-
? th.fg("success", "
|
|
615
|
-
: th.fg("dim", "
|
|
806
|
+
? th.fg("success", "\u25cf")
|
|
807
|
+
: th.fg("dim", "\u25cb");
|
|
616
808
|
const statusText = activity.active ? "ACTIVE" : "IDLE";
|
|
617
|
-
const elapsedStr = activity.active ? formatDuration(activity.elapsed) : "
|
|
809
|
+
const elapsedStr = activity.active ? formatDuration(activity.elapsed) : "\u2014";
|
|
618
810
|
|
|
619
811
|
lines.push(
|
|
620
812
|
joinColumns(
|
|
@@ -640,15 +832,15 @@ export function renderAgentView(
|
|
|
640
832
|
const barW = Math.max(10, Math.min(30, width - 30));
|
|
641
833
|
const fillLen = Math.round(pct * barW);
|
|
642
834
|
const bar =
|
|
643
|
-
th.fg("accent", "
|
|
644
|
-
th.fg("dim", "
|
|
835
|
+
th.fg("accent", "\u2588".repeat(fillLen)) +
|
|
836
|
+
th.fg("dim", "\u2591".repeat(barW - fillLen));
|
|
645
837
|
lines.push(`Progress ${bar} ${completed}/${total} slices`);
|
|
646
838
|
}
|
|
647
839
|
|
|
648
840
|
// Rate and session stats
|
|
649
841
|
const rateStr = activity.completionRate > 0
|
|
650
842
|
? `${activity.completionRate.toFixed(1)} units/hr`
|
|
651
|
-
: "
|
|
843
|
+
: "\u2014";
|
|
652
844
|
lines.push(
|
|
653
845
|
`Rate: ${th.fg("text", rateStr)} ` +
|
|
654
846
|
`Session: ${th.fg("text", formatCost(activity.sessionCost))} ` +
|
|
@@ -657,6 +849,21 @@ export function renderAgentView(
|
|
|
657
849
|
|
|
658
850
|
lines.push("");
|
|
659
851
|
|
|
852
|
+
// Budget pressure
|
|
853
|
+
const health = data.health;
|
|
854
|
+
const truncColor = health.truncationRate < 10 ? "success" : health.truncationRate < 30 ? "warning" : "error";
|
|
855
|
+
const contColor = health.continueHereRate < 10 ? "success" : health.continueHereRate < 30 ? "warning" : "error";
|
|
856
|
+
lines.push(th.fg("accent", th.bold("Pressure")));
|
|
857
|
+
lines.push(` Truncation rate: ${th.fg(truncColor, `${health.truncationRate.toFixed(1)}%`)}`);
|
|
858
|
+
lines.push(` Continue-here rate: ${th.fg(contColor, `${health.continueHereRate.toFixed(1)}%`)}`);
|
|
859
|
+
|
|
860
|
+
// Pending captures
|
|
861
|
+
if (data.captures.pendingCount > 0) {
|
|
862
|
+
lines.push(` Pending captures: ${th.fg("warning", String(data.captures.pendingCount))}`);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
lines.push("");
|
|
866
|
+
|
|
660
867
|
// Recent completed units (last 5)
|
|
661
868
|
const recentUnits = data.units.filter(u => u.finishedAt > 0).slice(-5).reverse();
|
|
662
869
|
if (recentUnits.length > 0) {
|
|
@@ -670,7 +877,7 @@ export function renderAgentView(
|
|
|
670
877
|
const typeLabel = padRight(u.type, 16);
|
|
671
878
|
lines.push(
|
|
672
879
|
truncateToWidth(
|
|
673
|
-
` ${hh}:${mm} ${th.fg("success", "
|
|
880
|
+
` ${hh}:${mm} ${th.fg("success", "\u2713")} ${typeLabel} ${padRight(u.id, 16)} ${dur} ${cost}`,
|
|
674
881
|
width,
|
|
675
882
|
),
|
|
676
883
|
);
|
|
@@ -713,13 +920,30 @@ export function renderChangelogView(
|
|
|
713
920
|
for (const f of entry.filesModified) {
|
|
714
921
|
lines.push(
|
|
715
922
|
truncateToWidth(
|
|
716
|
-
` ${th.fg("success", "
|
|
923
|
+
` ${th.fg("success", "\u2713")} ${f.path} \u2014 ${f.description}`,
|
|
717
924
|
width,
|
|
718
925
|
),
|
|
719
926
|
);
|
|
720
927
|
}
|
|
721
928
|
}
|
|
722
929
|
|
|
930
|
+
// Decisions and patterns from slice verification
|
|
931
|
+
const ver = findVerification(data, entry.milestoneId, entry.sliceId);
|
|
932
|
+
if (ver) {
|
|
933
|
+
if (ver.keyDecisions.length > 0) {
|
|
934
|
+
lines.push(" Decisions:");
|
|
935
|
+
for (const d of ver.keyDecisions) {
|
|
936
|
+
lines.push(` - ${d}`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (ver.patternsEstablished.length > 0) {
|
|
940
|
+
lines.push(" Patterns:");
|
|
941
|
+
for (const p of ver.patternsEstablished) {
|
|
942
|
+
lines.push(` - ${p}`);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
723
947
|
if (entry.completedAt) {
|
|
724
948
|
lines.push(th.fg("dim", ` Completed: ${entry.completedAt}`));
|
|
725
949
|
}
|
|
@@ -742,9 +966,9 @@ export function renderExportView(
|
|
|
742
966
|
|
|
743
967
|
lines.push(th.fg("accent", th.bold("Export Options")));
|
|
744
968
|
lines.push("");
|
|
745
|
-
lines.push(` ${th.fg("accent", "[m]")} Markdown report
|
|
746
|
-
lines.push(` ${th.fg("accent", "[j]")} JSON report
|
|
747
|
-
lines.push(` ${th.fg("accent", "[s]")} Snapshot
|
|
969
|
+
lines.push(` ${th.fg("accent", "[m]")} Markdown report \u2014 full project summary with tables`);
|
|
970
|
+
lines.push(` ${th.fg("accent", "[j]")} JSON report \u2014 machine-readable project data`);
|
|
971
|
+
lines.push(` ${th.fg("accent", "[s]")} Snapshot \u2014 current view as plain text`);
|
|
748
972
|
|
|
749
973
|
if (lastExportPath) {
|
|
750
974
|
lines.push("");
|
|
@@ -753,3 +977,193 @@ export function renderExportView(
|
|
|
753
977
|
|
|
754
978
|
return lines;
|
|
755
979
|
}
|
|
980
|
+
|
|
981
|
+
// ─── Knowledge View ──────────────────────────────────────────────────────────
|
|
982
|
+
|
|
983
|
+
export function renderKnowledgeView(
|
|
984
|
+
data: VisualizerData,
|
|
985
|
+
th: Theme,
|
|
986
|
+
width: number,
|
|
987
|
+
): string[] {
|
|
988
|
+
const lines: string[] = [];
|
|
989
|
+
const knowledge = data.knowledge;
|
|
990
|
+
|
|
991
|
+
if (!knowledge.exists) {
|
|
992
|
+
lines.push(th.fg("dim", "No KNOWLEDGE.md found"));
|
|
993
|
+
return lines;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if (knowledge.rules.length === 0 && knowledge.patterns.length === 0 && knowledge.lessons.length === 0) {
|
|
997
|
+
lines.push(th.fg("dim", "KNOWLEDGE.md exists but is empty"));
|
|
998
|
+
return lines;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// Rules section
|
|
1002
|
+
if (knowledge.rules.length > 0) {
|
|
1003
|
+
lines.push(th.fg("accent", th.bold("Rules")));
|
|
1004
|
+
lines.push("");
|
|
1005
|
+
for (const rule of knowledge.rules) {
|
|
1006
|
+
lines.push(truncateToWidth(
|
|
1007
|
+
` ${th.fg("accent", rule.id)} ${th.fg("dim", `[${rule.scope}]`)} ${rule.content}`,
|
|
1008
|
+
width,
|
|
1009
|
+
));
|
|
1010
|
+
}
|
|
1011
|
+
lines.push("");
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Patterns section
|
|
1015
|
+
if (knowledge.patterns.length > 0) {
|
|
1016
|
+
lines.push(th.fg("accent", th.bold("Patterns")));
|
|
1017
|
+
lines.push("");
|
|
1018
|
+
for (const pattern of knowledge.patterns) {
|
|
1019
|
+
lines.push(truncateToWidth(
|
|
1020
|
+
` ${th.fg("accent", pattern.id)} ${pattern.content}`,
|
|
1021
|
+
width,
|
|
1022
|
+
));
|
|
1023
|
+
}
|
|
1024
|
+
lines.push("");
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// Lessons section
|
|
1028
|
+
if (knowledge.lessons.length > 0) {
|
|
1029
|
+
lines.push(th.fg("accent", th.bold("Lessons Learned")));
|
|
1030
|
+
lines.push("");
|
|
1031
|
+
for (const lesson of knowledge.lessons) {
|
|
1032
|
+
lines.push(truncateToWidth(
|
|
1033
|
+
` ${th.fg("accent", lesson.id)} ${lesson.content}`,
|
|
1034
|
+
width,
|
|
1035
|
+
));
|
|
1036
|
+
}
|
|
1037
|
+
lines.push("");
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
return lines;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// ─── Captures View ───────────────────────────────────────────────────────────
|
|
1044
|
+
|
|
1045
|
+
export function renderCapturesView(
|
|
1046
|
+
data: VisualizerData,
|
|
1047
|
+
th: Theme,
|
|
1048
|
+
width: number,
|
|
1049
|
+
): string[] {
|
|
1050
|
+
const lines: string[] = [];
|
|
1051
|
+
const captures = data.captures;
|
|
1052
|
+
|
|
1053
|
+
// Summary line
|
|
1054
|
+
const resolved = captures.entries.filter(e => e.status === "resolved").length;
|
|
1055
|
+
lines.push(
|
|
1056
|
+
`${th.fg("text", String(captures.totalCount))} total \u00b7 ` +
|
|
1057
|
+
`${th.fg("warning", String(captures.pendingCount))} pending \u00b7 ` +
|
|
1058
|
+
`${th.fg("dim", String(resolved))} resolved`,
|
|
1059
|
+
);
|
|
1060
|
+
lines.push("");
|
|
1061
|
+
|
|
1062
|
+
if (captures.entries.length === 0) {
|
|
1063
|
+
lines.push(th.fg("dim", "No captures recorded."));
|
|
1064
|
+
return lines;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// Group by status: pending first, then triaged, then resolved
|
|
1068
|
+
const statusOrder: Record<string, number> = { pending: 0, triaged: 1, resolved: 2 };
|
|
1069
|
+
const sorted = [...captures.entries].sort((a, b) =>
|
|
1070
|
+
(statusOrder[a.status] ?? 3) - (statusOrder[b.status] ?? 3),
|
|
1071
|
+
);
|
|
1072
|
+
|
|
1073
|
+
for (const entry of sorted) {
|
|
1074
|
+
const statusColor =
|
|
1075
|
+
entry.status === "pending" ? "warning" :
|
|
1076
|
+
entry.status === "triaged" ? "accent" :
|
|
1077
|
+
"dim";
|
|
1078
|
+
|
|
1079
|
+
const classColor =
|
|
1080
|
+
entry.classification === "inject" ? "warning" :
|
|
1081
|
+
entry.classification === "quick-task" ? "accent" :
|
|
1082
|
+
entry.classification === "replan" ? "error" :
|
|
1083
|
+
entry.classification === "defer" ? "text" :
|
|
1084
|
+
"dim";
|
|
1085
|
+
|
|
1086
|
+
const classBadge = entry.classification
|
|
1087
|
+
? th.fg(classColor, `(${entry.classification})`)
|
|
1088
|
+
: "";
|
|
1089
|
+
|
|
1090
|
+
const statusBadge = th.fg(statusColor, `[${entry.status}]`);
|
|
1091
|
+
const textPreview = truncateToWidth(entry.text, Math.max(20, width - 50));
|
|
1092
|
+
|
|
1093
|
+
lines.push(` ${th.fg("accent", entry.id)} ${statusBadge} ${textPreview} ${classBadge}`);
|
|
1094
|
+
if (entry.timestamp) {
|
|
1095
|
+
lines.push(` ${th.fg("dim", entry.timestamp)}`);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return lines;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// ─── Health View ─────────────────────────────────────────────────────────────
|
|
1103
|
+
|
|
1104
|
+
export function renderHealthView(
|
|
1105
|
+
data: VisualizerData,
|
|
1106
|
+
th: Theme,
|
|
1107
|
+
width: number,
|
|
1108
|
+
): string[] {
|
|
1109
|
+
const lines: string[] = [];
|
|
1110
|
+
const health = data.health;
|
|
1111
|
+
|
|
1112
|
+
// Budget section
|
|
1113
|
+
lines.push(th.fg("accent", th.bold("Budget")));
|
|
1114
|
+
lines.push("");
|
|
1115
|
+
if (health.budgetCeiling !== undefined) {
|
|
1116
|
+
const currentSpend = data.totals?.cost ?? 0;
|
|
1117
|
+
const pct = health.budgetCeiling > 0 ? Math.min(1, currentSpend / health.budgetCeiling) : 0;
|
|
1118
|
+
const barW = Math.max(10, Math.min(30, width - 40));
|
|
1119
|
+
const fillLen = Math.round(pct * barW);
|
|
1120
|
+
const budgetColor = pct < 0.7 ? "success" : pct < 0.9 ? "warning" : "error";
|
|
1121
|
+
const bar =
|
|
1122
|
+
th.fg(budgetColor, "\u2588".repeat(fillLen)) +
|
|
1123
|
+
th.fg("dim", "\u2591".repeat(barW - fillLen));
|
|
1124
|
+
lines.push(` Ceiling: ${th.fg("text", formatCost(health.budgetCeiling))}`);
|
|
1125
|
+
lines.push(` Spend: ${bar} ${formatCost(currentSpend)} (${(pct * 100).toFixed(1)}%)`);
|
|
1126
|
+
} else {
|
|
1127
|
+
lines.push(th.fg("dim", " No budget ceiling set"));
|
|
1128
|
+
}
|
|
1129
|
+
lines.push(` Token profile: ${th.fg("text", health.tokenProfile)}`);
|
|
1130
|
+
lines.push("");
|
|
1131
|
+
|
|
1132
|
+
// Pressure section
|
|
1133
|
+
lines.push(th.fg("accent", th.bold("Pressure")));
|
|
1134
|
+
lines.push("");
|
|
1135
|
+
const truncColor = health.truncationRate < 10 ? "success" : health.truncationRate < 30 ? "warning" : "error";
|
|
1136
|
+
const contColor = health.continueHereRate < 10 ? "success" : health.continueHereRate < 30 ? "warning" : "error";
|
|
1137
|
+
const pressBarW = Math.max(10, Math.min(20, width - 50));
|
|
1138
|
+
|
|
1139
|
+
const truncFill = Math.round((Math.min(health.truncationRate, 100) / 100) * pressBarW);
|
|
1140
|
+
const truncBar = th.fg(truncColor, "\u2588".repeat(truncFill)) + th.fg("dim", "\u2591".repeat(pressBarW - truncFill));
|
|
1141
|
+
lines.push(` Truncation: ${truncBar} ${health.truncationRate.toFixed(1)}%`);
|
|
1142
|
+
|
|
1143
|
+
const contFill = Math.round((Math.min(health.continueHereRate, 100) / 100) * pressBarW);
|
|
1144
|
+
const contBar = th.fg(contColor, "\u2588".repeat(contFill)) + th.fg("dim", "\u2591".repeat(pressBarW - contFill));
|
|
1145
|
+
lines.push(` Continue-here: ${contBar} ${health.continueHereRate.toFixed(1)}%`);
|
|
1146
|
+
lines.push("");
|
|
1147
|
+
|
|
1148
|
+
// Routing section
|
|
1149
|
+
if (health.tierBreakdown.length > 0) {
|
|
1150
|
+
lines.push(th.fg("accent", th.bold("Routing")));
|
|
1151
|
+
lines.push("");
|
|
1152
|
+
for (const tier of health.tierBreakdown) {
|
|
1153
|
+
const downTag = tier.downgraded > 0 ? th.fg("warning", ` (${tier.downgraded} downgraded)`) : "";
|
|
1154
|
+
lines.push(` ${padRight(tier.tier, 12)} ${tier.units} units ${formatCost(tier.cost)}${downTag}`);
|
|
1155
|
+
}
|
|
1156
|
+
if (health.tierSavingsLine) {
|
|
1157
|
+
lines.push(` ${th.fg("success", health.tierSavingsLine)}`);
|
|
1158
|
+
}
|
|
1159
|
+
lines.push("");
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Session section
|
|
1163
|
+
lines.push(th.fg("accent", th.bold("Session")));
|
|
1164
|
+
lines.push("");
|
|
1165
|
+
lines.push(` Tool calls: ${th.fg("text", String(health.toolCalls))}`);
|
|
1166
|
+
lines.push(` Messages: ${th.fg("text", String(health.assistantMessages))} sent / ${th.fg("text", String(health.userMessages))} received`);
|
|
1167
|
+
|
|
1168
|
+
return lines;
|
|
1169
|
+
}
|