gsd-pi 2.19.0 → 2.20.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 +5 -1
- package/dist/cli.js +3 -3
- package/dist/onboarding.d.ts +3 -1
- package/dist/onboarding.js +77 -3
- package/dist/remote-questions-config.d.ts +1 -1
- package/dist/resources/extensions/google-search/index.ts +164 -47
- package/dist/resources/extensions/gsd/auto-prompts.ts +103 -24
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +424 -30
- package/dist/resources/extensions/gsd/commands.ts +518 -36
- package/dist/resources/extensions/gsd/context-budget.ts +243 -0
- package/dist/resources/extensions/gsd/context-store.ts +195 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +41 -3
- package/dist/resources/extensions/gsd/db-writer.ts +341 -0
- package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/dist/resources/extensions/gsd/doctor.ts +283 -2
- package/dist/resources/extensions/gsd/export.ts +81 -2
- package/dist/resources/extensions/gsd/files.ts +39 -9
- package/dist/resources/extensions/gsd/git-service.ts +6 -0
- package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
- package/dist/resources/extensions/gsd/history.ts +0 -1
- package/dist/resources/extensions/gsd/index.ts +277 -1
- package/dist/resources/extensions/gsd/md-importer.ts +526 -0
- package/dist/resources/extensions/gsd/metrics.ts +39 -3
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/dist/resources/extensions/gsd/preferences.ts +125 -150
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/quick.ts +156 -0
- package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/dist/resources/extensions/gsd/skill-health.ts +417 -0
- package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/dist/resources/extensions/gsd/state.ts +30 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/dist/resources/extensions/gsd/types.ts +29 -0
- package/dist/resources/extensions/gsd/undo.ts +0 -1
- package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +352 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
- package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/dist/resources/extensions/remote-questions/config.ts +4 -2
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +2 -4
- package/dist/resources/extensions/remote-questions/format.ts +154 -8
- package/dist/resources/extensions/remote-questions/manager.ts +9 -7
- package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/dist/resources/extensions/remote-questions/types.ts +2 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/dist/resources/extensions/voice/index.ts +4 -3
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -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 +43 -11
- package/packages/pi-coding-agent/dist/core/settings-manager.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 +7 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
- package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
- package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
- package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
- package/src/resources/extensions/google-search/index.ts +164 -47
- package/src/resources/extensions/gsd/auto-prompts.ts +103 -24
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +424 -30
- package/src/resources/extensions/gsd/commands.ts +518 -36
- package/src/resources/extensions/gsd/context-budget.ts +243 -0
- package/src/resources/extensions/gsd/context-store.ts +195 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +41 -3
- package/src/resources/extensions/gsd/db-writer.ts +341 -0
- package/src/resources/extensions/gsd/debug-logger.ts +178 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/src/resources/extensions/gsd/doctor.ts +283 -2
- package/src/resources/extensions/gsd/export.ts +81 -2
- package/src/resources/extensions/gsd/files.ts +39 -9
- package/src/resources/extensions/gsd/git-service.ts +6 -0
- package/src/resources/extensions/gsd/gsd-db.ts +752 -0
- package/src/resources/extensions/gsd/guided-flow.ts +26 -1
- package/src/resources/extensions/gsd/history.ts +0 -1
- package/src/resources/extensions/gsd/index.ts +277 -1
- package/src/resources/extensions/gsd/md-importer.ts +526 -0
- package/src/resources/extensions/gsd/metrics.ts +39 -3
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/src/resources/extensions/gsd/preferences.ts +125 -150
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/quick.ts +156 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/src/resources/extensions/gsd/skill-health.ts +417 -0
- package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/src/resources/extensions/gsd/state.ts +30 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
- package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/src/resources/extensions/gsd/types.ts +29 -0
- package/src/resources/extensions/gsd/undo.ts +0 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +352 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
- package/src/resources/extensions/gsd/worktree-command.ts +18 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/src/resources/extensions/remote-questions/config.ts +4 -2
- package/src/resources/extensions/remote-questions/discord-adapter.ts +2 -4
- package/src/resources/extensions/remote-questions/format.ts +154 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -7
- package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/src/resources/extensions/remote-questions/types.ts +2 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/src/resources/extensions/voice/index.ts +4 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Data loader for workflow visualizer overlay — aggregates state + metrics.
|
|
2
2
|
|
|
3
3
|
import { deriveState } from './state.js';
|
|
4
|
-
import { parseRoadmap, parsePlan, loadFile } from './files.js';
|
|
4
|
+
import { parseRoadmap, parsePlan, parseSummary, loadFile } from './files.js';
|
|
5
5
|
import { findMilestoneIds } from './guided-flow.js';
|
|
6
6
|
import { resolveMilestoneFile, resolveSliceFile } from './paths.js';
|
|
7
7
|
import {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
aggregateBySlice,
|
|
12
12
|
aggregateByModel,
|
|
13
13
|
loadLedgerFromDisk,
|
|
14
|
+
classifyUnitPhase,
|
|
14
15
|
} from './metrics.js';
|
|
15
16
|
|
|
16
17
|
import type { Phase } from './types.js';
|
|
@@ -49,6 +50,37 @@ export interface VisualizerTask {
|
|
|
49
50
|
active: boolean;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
export interface CriticalPathInfo {
|
|
54
|
+
milestonePath: string[];
|
|
55
|
+
slicePath: string[];
|
|
56
|
+
milestoneSlack: Map<string, number>;
|
|
57
|
+
sliceSlack: Map<string, number>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface AgentActivityInfo {
|
|
61
|
+
currentUnit: { type: string; id: string; startedAt: number } | null;
|
|
62
|
+
elapsed: number;
|
|
63
|
+
completedUnits: number;
|
|
64
|
+
totalSlices: number;
|
|
65
|
+
completionRate: number;
|
|
66
|
+
active: boolean;
|
|
67
|
+
sessionCost: number;
|
|
68
|
+
sessionTokens: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ChangelogEntry {
|
|
72
|
+
milestoneId: string;
|
|
73
|
+
sliceId: string;
|
|
74
|
+
title: string;
|
|
75
|
+
oneLiner: string;
|
|
76
|
+
filesModified: { path: string; description: string }[];
|
|
77
|
+
completedAt: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ChangelogInfo {
|
|
81
|
+
entries: ChangelogEntry[];
|
|
82
|
+
}
|
|
83
|
+
|
|
52
84
|
export interface VisualizerData {
|
|
53
85
|
milestones: VisualizerMilestone[];
|
|
54
86
|
phase: Phase;
|
|
@@ -57,6 +89,308 @@ export interface VisualizerData {
|
|
|
57
89
|
bySlice: SliceAggregate[];
|
|
58
90
|
byModel: ModelAggregate[];
|
|
59
91
|
units: UnitMetrics[];
|
|
92
|
+
criticalPath: CriticalPathInfo;
|
|
93
|
+
remainingSliceCount: number;
|
|
94
|
+
agentActivity: AgentActivityInfo | null;
|
|
95
|
+
changelog: ChangelogInfo;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ─── Critical Path ────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
export function computeCriticalPath(milestones: VisualizerMilestone[]): CriticalPathInfo {
|
|
101
|
+
const empty: CriticalPathInfo = {
|
|
102
|
+
milestonePath: [],
|
|
103
|
+
slicePath: [],
|
|
104
|
+
milestoneSlack: new Map(),
|
|
105
|
+
sliceSlack: new Map(),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (milestones.length === 0) return empty;
|
|
109
|
+
|
|
110
|
+
// Milestone-level critical path (weight = number of incomplete slices)
|
|
111
|
+
const msMap = new Map(milestones.map(m => [m.id, m]));
|
|
112
|
+
const msIds = milestones.map(m => m.id);
|
|
113
|
+
const msAdj = new Map<string, string[]>();
|
|
114
|
+
const msWeight = new Map<string, number>();
|
|
115
|
+
|
|
116
|
+
for (const ms of milestones) {
|
|
117
|
+
msAdj.set(ms.id, []);
|
|
118
|
+
const incomplete = ms.slices.filter(s => !s.done).length;
|
|
119
|
+
msWeight.set(ms.id, ms.status === 'complete' ? 0 : Math.max(1, incomplete));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (const ms of milestones) {
|
|
123
|
+
for (const dep of ms.dependsOn) {
|
|
124
|
+
if (msMap.has(dep)) {
|
|
125
|
+
const adj = msAdj.get(dep);
|
|
126
|
+
if (adj) adj.push(ms.id);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Topological sort (Kahn's algorithm)
|
|
132
|
+
const inDegree = new Map<string, number>();
|
|
133
|
+
for (const id of msIds) inDegree.set(id, 0);
|
|
134
|
+
for (const ms of milestones) {
|
|
135
|
+
for (const dep of ms.dependsOn) {
|
|
136
|
+
if (msMap.has(dep)) inDegree.set(ms.id, (inDegree.get(ms.id) ?? 0) + 1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const queue: string[] = [];
|
|
141
|
+
for (const [id, deg] of inDegree) {
|
|
142
|
+
if (deg === 0) queue.push(id);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const topoOrder: string[] = [];
|
|
146
|
+
while (queue.length > 0) {
|
|
147
|
+
const node = queue.shift()!;
|
|
148
|
+
topoOrder.push(node);
|
|
149
|
+
for (const next of (msAdj.get(node) ?? [])) {
|
|
150
|
+
const d = (inDegree.get(next) ?? 1) - 1;
|
|
151
|
+
inDegree.set(next, d);
|
|
152
|
+
if (d === 0) queue.push(next);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Longest path from each root
|
|
157
|
+
const dist = new Map<string, number>();
|
|
158
|
+
const prev = new Map<string, string | null>();
|
|
159
|
+
for (const id of msIds) {
|
|
160
|
+
dist.set(id, 0);
|
|
161
|
+
prev.set(id, null);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const node of topoOrder) {
|
|
165
|
+
const w = msWeight.get(node) ?? 1;
|
|
166
|
+
const nodeDist = dist.get(node)! + w;
|
|
167
|
+
for (const next of (msAdj.get(node) ?? [])) {
|
|
168
|
+
if (nodeDist > dist.get(next)!) {
|
|
169
|
+
dist.set(next, nodeDist);
|
|
170
|
+
prev.set(next, node);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Find the end of the critical path (node with max dist + own weight)
|
|
176
|
+
let maxDist = 0;
|
|
177
|
+
let endNode = msIds[0];
|
|
178
|
+
for (const id of msIds) {
|
|
179
|
+
const totalDist = dist.get(id)! + (msWeight.get(id) ?? 1);
|
|
180
|
+
if (totalDist > maxDist) {
|
|
181
|
+
maxDist = totalDist;
|
|
182
|
+
endNode = id;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Trace back
|
|
187
|
+
const milestonePath: string[] = [];
|
|
188
|
+
let cur: string | null = endNode;
|
|
189
|
+
while (cur !== null) {
|
|
190
|
+
milestonePath.unshift(cur);
|
|
191
|
+
cur = prev.get(cur) ?? null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Compute milestone slack
|
|
195
|
+
const milestoneSlack = new Map<string, number>();
|
|
196
|
+
const criticalSet = new Set(milestonePath);
|
|
197
|
+
for (const id of msIds) {
|
|
198
|
+
if (criticalSet.has(id)) {
|
|
199
|
+
milestoneSlack.set(id, 0);
|
|
200
|
+
} else {
|
|
201
|
+
const nodeTotal = dist.get(id)! + (msWeight.get(id) ?? 1);
|
|
202
|
+
milestoneSlack.set(id, Math.max(0, maxDist - nodeTotal));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Slice-level critical path within active milestone
|
|
207
|
+
const activeMs = milestones.find(m => m.status === 'active');
|
|
208
|
+
let slicePath: string[] = [];
|
|
209
|
+
const sliceSlack = new Map<string, number>();
|
|
210
|
+
|
|
211
|
+
if (activeMs && activeMs.slices.length > 0) {
|
|
212
|
+
const slMap = new Map(activeMs.slices.map(s => [s.id, s]));
|
|
213
|
+
const slAdj = new Map<string, string[]>();
|
|
214
|
+
for (const s of activeMs.slices) slAdj.set(s.id, []);
|
|
215
|
+
for (const s of activeMs.slices) {
|
|
216
|
+
for (const dep of s.depends) {
|
|
217
|
+
if (slMap.has(dep)) {
|
|
218
|
+
const adj = slAdj.get(dep);
|
|
219
|
+
if (adj) adj.push(s.id);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Topo sort slices
|
|
225
|
+
const slIn = new Map<string, number>();
|
|
226
|
+
for (const s of activeMs.slices) slIn.set(s.id, 0);
|
|
227
|
+
for (const s of activeMs.slices) {
|
|
228
|
+
for (const dep of s.depends) {
|
|
229
|
+
if (slMap.has(dep)) slIn.set(s.id, (slIn.get(s.id) ?? 0) + 1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const slQueue: string[] = [];
|
|
234
|
+
for (const [id, d] of slIn) {
|
|
235
|
+
if (d === 0) slQueue.push(id);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const slTopo: string[] = [];
|
|
239
|
+
while (slQueue.length > 0) {
|
|
240
|
+
const n = slQueue.shift()!;
|
|
241
|
+
slTopo.push(n);
|
|
242
|
+
for (const next of (slAdj.get(n) ?? [])) {
|
|
243
|
+
const d = (slIn.get(next) ?? 1) - 1;
|
|
244
|
+
slIn.set(next, d);
|
|
245
|
+
if (d === 0) slQueue.push(next);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const slDist = new Map<string, number>();
|
|
250
|
+
const slPrev = new Map<string, string | null>();
|
|
251
|
+
for (const s of activeMs.slices) {
|
|
252
|
+
const w = s.done ? 0 : 1;
|
|
253
|
+
slDist.set(s.id, 0);
|
|
254
|
+
slPrev.set(s.id, null);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (const n of slTopo) {
|
|
258
|
+
const w = (slMap.get(n)?.done ? 0 : 1);
|
|
259
|
+
const nd = slDist.get(n)! + w;
|
|
260
|
+
for (const next of (slAdj.get(n) ?? [])) {
|
|
261
|
+
if (nd > slDist.get(next)!) {
|
|
262
|
+
slDist.set(next, nd);
|
|
263
|
+
slPrev.set(next, n);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let slMax = 0;
|
|
269
|
+
let slEnd = activeMs.slices[0].id;
|
|
270
|
+
for (const s of activeMs.slices) {
|
|
271
|
+
const totalDist = slDist.get(s.id)! + (s.done ? 0 : 1);
|
|
272
|
+
if (totalDist > slMax) {
|
|
273
|
+
slMax = totalDist;
|
|
274
|
+
slEnd = s.id;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let slCur: string | null = slEnd;
|
|
279
|
+
while (slCur !== null) {
|
|
280
|
+
slicePath.unshift(slCur);
|
|
281
|
+
slCur = slPrev.get(slCur) ?? null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const slCritSet = new Set(slicePath);
|
|
285
|
+
for (const s of activeMs.slices) {
|
|
286
|
+
if (slCritSet.has(s.id)) {
|
|
287
|
+
sliceSlack.set(s.id, 0);
|
|
288
|
+
} else {
|
|
289
|
+
const nodeTotal = slDist.get(s.id)! + (s.done ? 0 : 1);
|
|
290
|
+
sliceSlack.set(s.id, Math.max(0, slMax - nodeTotal));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return { milestonePath, slicePath, milestoneSlack, sliceSlack };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Agent Activity ──────────────────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
function loadAgentActivity(units: UnitMetrics[], milestones: VisualizerMilestone[]): AgentActivityInfo | null {
|
|
301
|
+
if (units.length === 0) return null;
|
|
302
|
+
|
|
303
|
+
// Find currently running unit (finishedAt === 0)
|
|
304
|
+
const running = units.find(u => u.finishedAt === 0);
|
|
305
|
+
const now = Date.now();
|
|
306
|
+
|
|
307
|
+
const completedUnits = units.filter(u => u.finishedAt > 0).length;
|
|
308
|
+
const totalSlices = milestones.reduce((sum, m) => sum + m.slices.length, 0);
|
|
309
|
+
|
|
310
|
+
// Completion rate from finished units
|
|
311
|
+
const finished = units.filter(u => u.finishedAt > 0);
|
|
312
|
+
let completionRate = 0;
|
|
313
|
+
if (finished.length >= 2) {
|
|
314
|
+
const earliest = Math.min(...finished.map(u => u.startedAt));
|
|
315
|
+
const latest = Math.max(...finished.map(u => u.finishedAt));
|
|
316
|
+
const totalHours = (latest - earliest) / 3_600_000;
|
|
317
|
+
completionRate = totalHours > 0 ? finished.length / totalHours : 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const sessionCost = units.reduce((sum, u) => sum + u.cost, 0);
|
|
321
|
+
const sessionTokens = units.reduce((sum, u) => sum + u.tokens.total, 0);
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
currentUnit: running
|
|
325
|
+
? { type: running.type, id: running.id, startedAt: running.startedAt }
|
|
326
|
+
: null,
|
|
327
|
+
elapsed: running ? now - running.startedAt : 0,
|
|
328
|
+
completedUnits,
|
|
329
|
+
totalSlices,
|
|
330
|
+
completionRate,
|
|
331
|
+
active: !!running,
|
|
332
|
+
sessionCost,
|
|
333
|
+
sessionTokens,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ─── Changelog ───────────────────────────────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
const changelogCache = new Map<string, { mtime: number; entry: ChangelogEntry }>();
|
|
340
|
+
|
|
341
|
+
async function loadChangelog(basePath: string, milestones: VisualizerMilestone[]): Promise<ChangelogInfo> {
|
|
342
|
+
const entries: ChangelogEntry[] = [];
|
|
343
|
+
|
|
344
|
+
for (const ms of milestones) {
|
|
345
|
+
for (const sl of ms.slices) {
|
|
346
|
+
if (!sl.done) continue;
|
|
347
|
+
|
|
348
|
+
const summaryFile = resolveSliceFile(basePath, ms.id, sl.id, 'SUMMARY');
|
|
349
|
+
if (!summaryFile) continue;
|
|
350
|
+
|
|
351
|
+
// Check cache by file path
|
|
352
|
+
const cacheKey = `${ms.id}/${sl.id}`;
|
|
353
|
+
const cached = changelogCache.get(cacheKey);
|
|
354
|
+
|
|
355
|
+
// Check mtime for cache invalidation
|
|
356
|
+
let mtime = 0;
|
|
357
|
+
try {
|
|
358
|
+
const { statSync } = await import('node:fs');
|
|
359
|
+
mtime = statSync(summaryFile).mtimeMs;
|
|
360
|
+
} catch {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (cached && cached.mtime === mtime) {
|
|
365
|
+
entries.push(cached.entry);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const content = await loadFile(summaryFile);
|
|
370
|
+
if (!content) continue;
|
|
371
|
+
|
|
372
|
+
const summary = parseSummary(content);
|
|
373
|
+
const entry: ChangelogEntry = {
|
|
374
|
+
milestoneId: ms.id,
|
|
375
|
+
sliceId: sl.id,
|
|
376
|
+
title: sl.title,
|
|
377
|
+
oneLiner: summary.oneLiner,
|
|
378
|
+
filesModified: summary.filesModified.map(f => ({
|
|
379
|
+
path: f.path,
|
|
380
|
+
description: f.description,
|
|
381
|
+
})),
|
|
382
|
+
completedAt: summary.frontmatter.completed_at ?? '',
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
changelogCache.set(cacheKey, { mtime, entry });
|
|
386
|
+
entries.push(entry);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Sort by completedAt descending
|
|
391
|
+
entries.sort((a, b) => (b.completedAt || '').localeCompare(a.completedAt || ''));
|
|
392
|
+
|
|
393
|
+
return { entries };
|
|
60
394
|
}
|
|
61
395
|
|
|
62
396
|
// ─── Loader ───────────────────────────────────────────────────────────────────
|
|
@@ -142,6 +476,19 @@ export async function loadVisualizerData(basePath: string): Promise<VisualizerDa
|
|
|
142
476
|
byModel = aggregateByModel(units);
|
|
143
477
|
}
|
|
144
478
|
|
|
479
|
+
// Compute new fields
|
|
480
|
+
const criticalPath = computeCriticalPath(milestones);
|
|
481
|
+
|
|
482
|
+
let remainingSliceCount = 0;
|
|
483
|
+
for (const ms of milestones) {
|
|
484
|
+
for (const sl of ms.slices) {
|
|
485
|
+
if (!sl.done) remainingSliceCount++;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const agentActivity = loadAgentActivity(units, milestones);
|
|
490
|
+
const changelog = await loadChangelog(basePath, milestones);
|
|
491
|
+
|
|
145
492
|
return {
|
|
146
493
|
milestones,
|
|
147
494
|
phase: state.phase,
|
|
@@ -150,5 +497,9 @@ export async function loadVisualizerData(basePath: string): Promise<VisualizerDa
|
|
|
150
497
|
bySlice,
|
|
151
498
|
byModel,
|
|
152
499
|
units,
|
|
500
|
+
criticalPath,
|
|
501
|
+
remainingSliceCount,
|
|
502
|
+
agentActivity,
|
|
503
|
+
changelog,
|
|
153
504
|
};
|
|
154
505
|
}
|
|
@@ -6,9 +6,23 @@ import {
|
|
|
6
6
|
renderDepsView,
|
|
7
7
|
renderMetricsView,
|
|
8
8
|
renderTimelineView,
|
|
9
|
+
renderAgentView,
|
|
10
|
+
renderChangelogView,
|
|
11
|
+
renderExportView,
|
|
12
|
+
type ProgressFilter,
|
|
9
13
|
} from "./visualizer-views.js";
|
|
14
|
+
import { writeExportFile } from "./export.js";
|
|
10
15
|
|
|
11
|
-
const
|
|
16
|
+
const TAB_COUNT = 7;
|
|
17
|
+
const TAB_LABELS = [
|
|
18
|
+
"1 Progress",
|
|
19
|
+
"2 Deps",
|
|
20
|
+
"3 Metrics",
|
|
21
|
+
"4 Timeline",
|
|
22
|
+
"5 Agent",
|
|
23
|
+
"6 Changes",
|
|
24
|
+
"7 Export",
|
|
25
|
+
];
|
|
12
26
|
|
|
13
27
|
export class GSDVisualizerOverlay {
|
|
14
28
|
private tui: { requestRender: () => void };
|
|
@@ -16,7 +30,7 @@ export class GSDVisualizerOverlay {
|
|
|
16
30
|
private onClose: () => void;
|
|
17
31
|
|
|
18
32
|
activeTab = 0;
|
|
19
|
-
scrollOffsets: number[] =
|
|
33
|
+
scrollOffsets: number[] = new Array(TAB_COUNT).fill(0);
|
|
20
34
|
loading = true;
|
|
21
35
|
disposed = false;
|
|
22
36
|
cachedWidth?: number;
|
|
@@ -25,6 +39,15 @@ export class GSDVisualizerOverlay {
|
|
|
25
39
|
data: VisualizerData | null = null;
|
|
26
40
|
basePath: string;
|
|
27
41
|
|
|
42
|
+
// Filter state (Progress tab)
|
|
43
|
+
filterMode = false;
|
|
44
|
+
filterText = "";
|
|
45
|
+
filterField: "all" | "status" | "risk" | "keyword" = "all";
|
|
46
|
+
|
|
47
|
+
// Export state
|
|
48
|
+
lastExportPath?: string;
|
|
49
|
+
exportStatus?: string;
|
|
50
|
+
|
|
28
51
|
constructor(
|
|
29
52
|
tui: { requestRender: () => void },
|
|
30
53
|
theme: Theme,
|
|
@@ -52,6 +75,37 @@ export class GSDVisualizerOverlay {
|
|
|
52
75
|
}
|
|
53
76
|
|
|
54
77
|
handleInput(data: string): void {
|
|
78
|
+
// Filter mode input routing
|
|
79
|
+
if (this.filterMode) {
|
|
80
|
+
if (matchesKey(data, Key.escape)) {
|
|
81
|
+
this.filterMode = false;
|
|
82
|
+
this.filterText = "";
|
|
83
|
+
this.invalidate();
|
|
84
|
+
this.tui.requestRender();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (matchesKey(data, Key.enter)) {
|
|
88
|
+
this.filterMode = false;
|
|
89
|
+
this.invalidate();
|
|
90
|
+
this.tui.requestRender();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (matchesKey(data, Key.backspace)) {
|
|
94
|
+
this.filterText = this.filterText.slice(0, -1);
|
|
95
|
+
this.invalidate();
|
|
96
|
+
this.tui.requestRender();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Append printable characters
|
|
100
|
+
if (data.length === 1 && data.charCodeAt(0) >= 32) {
|
|
101
|
+
this.filterText += data;
|
|
102
|
+
this.invalidate();
|
|
103
|
+
this.tui.requestRender();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
55
109
|
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
|
|
56
110
|
this.dispose();
|
|
57
111
|
this.onClose();
|
|
@@ -59,19 +113,46 @@ export class GSDVisualizerOverlay {
|
|
|
59
113
|
}
|
|
60
114
|
|
|
61
115
|
if (matchesKey(data, Key.tab)) {
|
|
62
|
-
this.activeTab = (this.activeTab + 1) %
|
|
116
|
+
this.activeTab = (this.activeTab + 1) % TAB_COUNT;
|
|
63
117
|
this.invalidate();
|
|
64
118
|
this.tui.requestRender();
|
|
65
119
|
return;
|
|
66
120
|
}
|
|
67
121
|
|
|
68
|
-
if (
|
|
122
|
+
if ("1234567".includes(data) && data.length === 1) {
|
|
69
123
|
this.activeTab = parseInt(data, 10) - 1;
|
|
70
124
|
this.invalidate();
|
|
71
125
|
this.tui.requestRender();
|
|
72
126
|
return;
|
|
73
127
|
}
|
|
74
128
|
|
|
129
|
+
// "/" enters filter mode on Progress tab
|
|
130
|
+
if (data === "/" && this.activeTab === 0) {
|
|
131
|
+
this.filterMode = true;
|
|
132
|
+
this.filterText = "";
|
|
133
|
+
this.invalidate();
|
|
134
|
+
this.tui.requestRender();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// "f" cycles filter field on Progress tab (when not in filter mode)
|
|
139
|
+
if (data === "f" && this.activeTab === 0) {
|
|
140
|
+
const fields: Array<"all" | "status" | "risk" | "keyword"> = ["all", "status", "risk", "keyword"];
|
|
141
|
+
const idx = fields.indexOf(this.filterField);
|
|
142
|
+
this.filterField = fields[(idx + 1) % fields.length];
|
|
143
|
+
this.invalidate();
|
|
144
|
+
this.tui.requestRender();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Export tab key handling
|
|
149
|
+
if (this.activeTab === 6 && this.data) {
|
|
150
|
+
if (data === "m" || data === "j" || data === "s") {
|
|
151
|
+
this.handleExportKey(data);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
75
156
|
if (matchesKey(data, Key.down) || matchesKey(data, "j")) {
|
|
76
157
|
this.scrollOffsets[this.activeTab]++;
|
|
77
158
|
this.invalidate();
|
|
@@ -101,6 +182,62 @@ export class GSDVisualizerOverlay {
|
|
|
101
182
|
}
|
|
102
183
|
}
|
|
103
184
|
|
|
185
|
+
private handleExportKey(key: "m" | "j" | "s"): void {
|
|
186
|
+
if (!this.data) return;
|
|
187
|
+
|
|
188
|
+
const format = key === "m" ? "markdown" : key === "j" ? "json" : "snapshot";
|
|
189
|
+
|
|
190
|
+
if (format === "snapshot") {
|
|
191
|
+
// Capture current active tab's rendered lines as snapshot
|
|
192
|
+
const snapshotLines = this.renderTabContent(this.activeTab, 80);
|
|
193
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
194
|
+
const { writeFileSync, mkdirSync } = require("node:fs");
|
|
195
|
+
const { join } = require("node:path");
|
|
196
|
+
const { gsdRoot } = require("./paths.js");
|
|
197
|
+
const exportDir = gsdRoot(this.basePath);
|
|
198
|
+
mkdirSync(exportDir, { recursive: true });
|
|
199
|
+
const outPath = join(exportDir, `snapshot-${timestamp}.txt`);
|
|
200
|
+
writeFileSync(outPath, snapshotLines.join("\n") + "\n", "utf-8");
|
|
201
|
+
this.lastExportPath = outPath;
|
|
202
|
+
this.exportStatus = "Snapshot saved";
|
|
203
|
+
} else {
|
|
204
|
+
const result = writeExportFile(this.basePath, format, this.data);
|
|
205
|
+
if (result) {
|
|
206
|
+
this.lastExportPath = result;
|
|
207
|
+
this.exportStatus = `${format} export saved`;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.invalidate();
|
|
212
|
+
this.tui.requestRender();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private renderTabContent(tab: number, width: number): string[] {
|
|
216
|
+
if (!this.data) return [];
|
|
217
|
+
const th = this.theme;
|
|
218
|
+
switch (tab) {
|
|
219
|
+
case 0: {
|
|
220
|
+
const filter: ProgressFilter | undefined =
|
|
221
|
+
this.filterText ? { text: this.filterText, field: this.filterField } : undefined;
|
|
222
|
+
return renderProgressView(this.data, th, width, filter);
|
|
223
|
+
}
|
|
224
|
+
case 1:
|
|
225
|
+
return renderDepsView(this.data, th, width);
|
|
226
|
+
case 2:
|
|
227
|
+
return renderMetricsView(this.data, th, width);
|
|
228
|
+
case 3:
|
|
229
|
+
return renderTimelineView(this.data, th, width);
|
|
230
|
+
case 4:
|
|
231
|
+
return renderAgentView(this.data, th, width);
|
|
232
|
+
case 5:
|
|
233
|
+
return renderChangelogView(this.data, th, width);
|
|
234
|
+
case 6:
|
|
235
|
+
return renderExportView(this.data, th, width, this.lastExportPath);
|
|
236
|
+
default:
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
104
241
|
render(width: number): string[] {
|
|
105
242
|
if (this.cachedLines && this.cachedWidth === width) {
|
|
106
243
|
return this.cachedLines;
|
|
@@ -112,35 +249,42 @@ export class GSDVisualizerOverlay {
|
|
|
112
249
|
|
|
113
250
|
// Tab bar
|
|
114
251
|
const tabs = TAB_LABELS.map((label, i) => {
|
|
252
|
+
let displayLabel = label;
|
|
253
|
+
// Show filter indicator on Progress tab
|
|
254
|
+
if (i === 0 && this.filterText) {
|
|
255
|
+
displayLabel += " ✱";
|
|
256
|
+
}
|
|
115
257
|
if (i === this.activeTab) {
|
|
116
|
-
return th.fg("accent", `[${
|
|
258
|
+
return th.fg("accent", `[${displayLabel}]`);
|
|
117
259
|
}
|
|
118
|
-
return th.fg("dim", `[${
|
|
260
|
+
return th.fg("dim", `[${displayLabel}]`);
|
|
119
261
|
});
|
|
120
|
-
content.push(" " + tabs.join("
|
|
262
|
+
content.push(" " + tabs.join(" "));
|
|
121
263
|
content.push("");
|
|
122
264
|
|
|
265
|
+
// Filter bar (when in filter mode)
|
|
266
|
+
if (this.filterMode && this.activeTab === 0) {
|
|
267
|
+
content.push(
|
|
268
|
+
th.fg("accent", `Filter (${this.filterField}): ${this.filterText}█`),
|
|
269
|
+
);
|
|
270
|
+
content.push("");
|
|
271
|
+
}
|
|
272
|
+
|
|
123
273
|
if (this.loading) {
|
|
124
274
|
const loadingText = "Loading…";
|
|
125
275
|
const vis = visibleWidth(loadingText);
|
|
126
276
|
const leftPad = Math.max(0, Math.floor((innerWidth - vis) / 2));
|
|
127
277
|
content.push(" ".repeat(leftPad) + loadingText);
|
|
128
278
|
} else if (this.data) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
break;
|
|
137
|
-
case 2:
|
|
138
|
-
viewLines = renderMetricsView(this.data, th, innerWidth);
|
|
139
|
-
break;
|
|
140
|
-
case 3:
|
|
141
|
-
viewLines = renderTimelineView(this.data, th, innerWidth);
|
|
142
|
-
break;
|
|
279
|
+
const viewLines = this.renderTabContent(this.activeTab, innerWidth);
|
|
280
|
+
|
|
281
|
+
// Show export status message if present
|
|
282
|
+
if (this.exportStatus && this.activeTab === 6) {
|
|
283
|
+
content.push(th.fg("success", this.exportStatus));
|
|
284
|
+
content.push("");
|
|
285
|
+
this.exportStatus = undefined;
|
|
143
286
|
}
|
|
287
|
+
|
|
144
288
|
content.push(...viewLines);
|
|
145
289
|
}
|
|
146
290
|
|
|
@@ -156,7 +300,7 @@ export class GSDVisualizerOverlay {
|
|
|
156
300
|
const lines = this.wrapInBox(visibleContent, width);
|
|
157
301
|
|
|
158
302
|
// Footer hint
|
|
159
|
-
const hint = th.fg("dim", "Tab/1-
|
|
303
|
+
const hint = th.fg("dim", "Tab/1-7 switch · / filter · ↑↓ scroll · g/G top/end · esc close");
|
|
160
304
|
const hintVis = visibleWidth(hint);
|
|
161
305
|
const hintPad = Math.max(0, Math.floor((width - hintVis) / 2));
|
|
162
306
|
lines.push(" ".repeat(hintPad) + hint);
|