@pellux/goodvibes-tui 1.0.0 → 1.7.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/CHANGELOG.md +130 -127
- package/README.md +32 -14
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/cli/ensure-goodvibes-gitignore.ts +32 -0
- package/src/cli/entrypoint.ts +2 -0
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/conversation-rendering.ts +19 -0
- package/src/core/conversation.ts +58 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +104 -2
- package/src/core/stream-stall-watchdog.ts +41 -17
- package/src/core/system-message-router.ts +37 -51
- package/src/core/turn-cancellation.ts +20 -0
- package/src/core/turn-event-wiring.ts +64 -3
- package/src/export/cost-utils.ts +126 -8
- package/src/input/command-registry.ts +54 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/diff-runtime.ts +61 -30
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +6 -1
- package/src/input/commands/operator-panel-runtime.ts +40 -24
- package/src/input/commands/planning-runtime.ts +26 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +3 -3
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +12 -0
- package/src/input/commands/session-workflow.ts +19 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/share-runtime.ts +9 -2
- package/src/input/commands/shell-core.ts +15 -7
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +338 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +161 -0
- package/src/input/config-modal.ts +377 -0
- package/src/input/feed-context-factory.ts +7 -8
- package/src/input/handler-command-route.ts +27 -17
- package/src/input/handler-content-actions.ts +22 -8
- package/src/input/handler-feed-routes.ts +107 -7
- package/src/input/handler-feed.ts +49 -13
- package/src/input/handler-interactions.ts +3 -3
- package/src/input/handler-modal-routes.ts +65 -0
- package/src/input/handler-modal-stack.ts +3 -5
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-picker-routes.ts +11 -94
- package/src/input/handler-shortcuts.ts +24 -14
- package/src/input/handler-types.ts +3 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +10 -28
- package/src/input/keybindings.ts +22 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +58 -0
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/settings-modal-data.ts +95 -0
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/main.ts +41 -44
- package/src/panels/agent-inspector-shared.ts +35 -11
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +16 -62
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +9 -43
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +63 -14
- package/src/panels/diff-panel.ts +123 -60
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-panel.ts +800 -0
- package/src/panels/fleet-read-model.ts +477 -0
- package/src/panels/fleet-steer.ts +92 -0
- package/src/panels/fleet-stop.ts +125 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/git-panel.ts +9 -9
- package/src/panels/index.ts +7 -31
- package/src/panels/local-auth-panel.ts +10 -0
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +151 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +36 -0
- package/src/panels/modals/pairing-modal.ts +144 -0
- package/src/panels/modals/planning-modal.ts +282 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +149 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +108 -5
- package/src/panels/project-planning-answer-actions.ts +4 -2
- package/src/panels/scrollable-list-panel.ts +9 -0
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/token-budget-panel.ts +5 -3
- package/src/panels/types.ts +26 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +60 -4
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/config-modal.ts +81 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +10 -2
- package/src/renderer/git-status.ts +40 -0
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/model-workspace.ts +44 -1
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/turn-injection.ts +114 -0
- package/src/renderer/ui-factory.ts +91 -7
- package/src/runtime/bootstrap-command-context.ts +24 -1
- package/src/runtime/bootstrap-command-parts.ts +36 -7
- package/src/runtime/bootstrap-core.ts +19 -4
- package/src/runtime/bootstrap-hook-bridge.ts +5 -0
- package/src/runtime/bootstrap-shell.ts +41 -17
- package/src/runtime/bootstrap.ts +11 -17
- package/src/runtime/code-index-services.ts +112 -0
- package/src/runtime/orchestrator-core-services.ts +49 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +91 -43
- package/src/runtime/ui-services.ts +8 -0
- package/src/runtime/workstream-services.ts +195 -0
- package/src/shell/blocking-input.ts +22 -1
- package/src/shell/ui-openers.ts +129 -17
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -233
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -609
- package/src/panels/docs-panel.ts +0 -375
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -664
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -506
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -557
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -721
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -306
- package/src/panels/provider-health-views.ts +0 -560
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -487
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -530
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -465
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -656
- package/src/renderer/qr-renderer.ts +0 -120
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// checkpoint-runtime.ts — /checkpoints, /checkpoint, /rewind
|
|
3
|
+
//
|
|
4
|
+
// UX over WorkspaceCheckpointManager (@pellux/goodvibes-sdk/platform/workspace),
|
|
5
|
+
// the whole-workspace, git-backed rewind engine wired onto RuntimeServices as
|
|
6
|
+
// `workspaceCheckpointManager` (src/runtime/services.ts) and threaded through
|
|
7
|
+
// CommandContext.workspace exactly like FileUndoManager already is.
|
|
8
|
+
//
|
|
9
|
+
// Architecture note (why the confirm step lives in DiffPanel, not here):
|
|
10
|
+
// SlashCommand.handler(args, ctx) is a single-shot call — there is no "await
|
|
11
|
+
// next keypress" primitive in the command layer. So `/rewind <id>` can only
|
|
12
|
+
// resolve the target, load the preview, open+focus the diff panel, and arm
|
|
13
|
+
// its confirm overlay (DiffPanel.confirmOverlay, a PanelConfirmOverlay —
|
|
14
|
+
// see panel-confirm-overlay.ts); the actual y/n/Enter/Esc handling happens
|
|
15
|
+
// in DiffPanel.handleInput() via the project's canonical ConfirmState<T>
|
|
16
|
+
// contract (confirm-state.ts), which every other destructive-action confirm
|
|
17
|
+
// in this codebase already uses (see git-panel.ts).
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
21
|
+
import type { WorkspaceCheckpoint, WorkspaceCheckpointManager } from '@pellux/goodvibes-sdk/platform/workspace';
|
|
22
|
+
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
23
|
+
import { requirePanelManager } from './runtime-services.ts';
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Formatting helpers
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/** Drop the redundant 'wcp_' prefix and cap length for compact listing. Full ids (and any unambiguous prefix of one) are still accepted by /rewind. */
|
|
30
|
+
function shortId(id: string): string {
|
|
31
|
+
const stripped = id.startsWith('wcp_') ? id.slice(4) : id;
|
|
32
|
+
return stripped.length > 12 ? stripped.slice(0, 12) : stripped;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatAge(createdAtMs: number, nowMs: number = Date.now()): string {
|
|
36
|
+
const deltaMs = Math.max(0, nowMs - createdAtMs);
|
|
37
|
+
const seconds = Math.floor(deltaMs / 1000);
|
|
38
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
39
|
+
const minutes = Math.floor(seconds / 60);
|
|
40
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
41
|
+
const hours = Math.floor(minutes / 60);
|
|
42
|
+
if (hours < 24) return `${hours}h ago`;
|
|
43
|
+
const days = Math.floor(hours / 24);
|
|
44
|
+
return `${days}d ago`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function formatBytes(bytes: number): string {
|
|
48
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
49
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
50
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function plural(n: number, word: string): string {
|
|
54
|
+
return `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Checkpoint resolution — shared by /rewind
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
type ResolveResult =
|
|
62
|
+
| { checkpoint: WorkspaceCheckpoint }
|
|
63
|
+
| { error: string };
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a user-supplied checkpoint reference ('last' or an id / id prefix,
|
|
67
|
+
* as shown by /checkpoints) against the live checkpoint list. Resolution
|
|
68
|
+
* happens entirely against list() results — never against
|
|
69
|
+
* WorkspaceCheckpointManager's own requireCheckpoint() — so an unknown ref is
|
|
70
|
+
* caught here, before anything touches the diff panel or arms a confirm.
|
|
71
|
+
*
|
|
72
|
+
* mgr.list() is guarded here (not left to the caller) so every caller gets a
|
|
73
|
+
* plain ResolveResult and can never be handed a rejected promise: if the
|
|
74
|
+
* manager's list() call fails (including a cached init() failure — see
|
|
75
|
+
* services.ts — which makes every WorkspaceCheckpointManager method reject
|
|
76
|
+
* forever), that surfaces as an honest `{ error }` instead of an unhandled
|
|
77
|
+
* rejection.
|
|
78
|
+
*/
|
|
79
|
+
async function resolveCheckpointTarget(
|
|
80
|
+
mgr: WorkspaceCheckpointManager,
|
|
81
|
+
ref: string,
|
|
82
|
+
): Promise<ResolveResult> {
|
|
83
|
+
let all: WorkspaceCheckpoint[];
|
|
84
|
+
try {
|
|
85
|
+
all = await mgr.list();
|
|
86
|
+
} catch (err) {
|
|
87
|
+
return { error: `Failed to list checkpoints: ${summarizeError(err)}` };
|
|
88
|
+
}
|
|
89
|
+
if (ref === 'last') {
|
|
90
|
+
const checkpoint = all[0];
|
|
91
|
+
if (!checkpoint) {
|
|
92
|
+
return { error: 'No checkpoints to rewind to. Use /checkpoint <label> to create one.' };
|
|
93
|
+
}
|
|
94
|
+
return { checkpoint };
|
|
95
|
+
}
|
|
96
|
+
const exact = all.find((c) => c.id === ref);
|
|
97
|
+
if (exact) return { checkpoint: exact };
|
|
98
|
+
|
|
99
|
+
const prefixMatches = all.filter((c) => c.id.startsWith(ref) || shortId(c.id).startsWith(ref));
|
|
100
|
+
if (prefixMatches.length === 1) return { checkpoint: prefixMatches[0]! };
|
|
101
|
+
if (prefixMatches.length > 1) {
|
|
102
|
+
return {
|
|
103
|
+
error: `Checkpoint id "${ref}" is ambiguous — matches ${prefixMatches.length} checkpoints. Use a longer prefix or run /checkpoints for full ids.`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return { error: `Unknown checkpoint id: "${ref}". Run /checkpoints to see available checkpoints.` };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Commands
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
export function registerCheckpointRuntimeCommands(registry: CommandRegistry): void {
|
|
114
|
+
registry.register({
|
|
115
|
+
name: 'checkpoints',
|
|
116
|
+
aliases: ['ckpts'],
|
|
117
|
+
description: 'List workspace checkpoints, newest first',
|
|
118
|
+
handler: async (_args, ctx: CommandContext) => {
|
|
119
|
+
const mgr = ctx.workspace.workspaceCheckpointManager;
|
|
120
|
+
if (!mgr) {
|
|
121
|
+
ctx.print('Checkpoints are not available in this session.');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
let checkpoints: WorkspaceCheckpoint[];
|
|
125
|
+
try {
|
|
126
|
+
checkpoints = await mgr.list();
|
|
127
|
+
} catch (err) {
|
|
128
|
+
ctx.print(`Failed to list checkpoints: ${summarizeError(err)}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (checkpoints.length === 0) {
|
|
132
|
+
ctx.print('No checkpoints yet. /checkpoint <label> creates one manually; turns and agent runs snapshot automatically.');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
ctx.print(`Checkpoints (${checkpoints.length}, newest first):`);
|
|
136
|
+
for (const cp of checkpoints) {
|
|
137
|
+
ctx.print(` ${shortId(cp.id).padEnd(12)} ${cp.kind.padEnd(9)} ${cp.label} ${formatAge(cp.createdAt)} ~${formatBytes(cp.sizeBytes)} new`);
|
|
138
|
+
}
|
|
139
|
+
// Files-changed counts are deliberately NOT shown per-row here: getting
|
|
140
|
+
// them would cost one diff spawn per checkpoint (O(checkpoints)), which
|
|
141
|
+
// stops being cheap once there are more than a handful. /rewind <id>
|
|
142
|
+
// loads exactly one diff, so the exact file list is available there.
|
|
143
|
+
ctx.print('Use /rewind <id|last> to preview the exact file changes and restore a checkpoint.');
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
registry.register({
|
|
148
|
+
name: 'checkpoint',
|
|
149
|
+
description: 'Create a manual workspace checkpoint (forensic retention)',
|
|
150
|
+
usage: '[label]',
|
|
151
|
+
argsHint: '[label]',
|
|
152
|
+
handler: async (args, ctx: CommandContext) => {
|
|
153
|
+
const mgr = ctx.workspace.workspaceCheckpointManager;
|
|
154
|
+
if (!mgr) {
|
|
155
|
+
ctx.print('Checkpoints are not available in this session.');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const label = args.join(' ').trim() || undefined;
|
|
159
|
+
try {
|
|
160
|
+
const checkpoint = await mgr.create({ kind: 'manual', label, retentionClass: 'forensic' });
|
|
161
|
+
if (!checkpoint) {
|
|
162
|
+
ctx.print('No changes since the last checkpoint — nothing to save.');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
ctx.print(`Checkpoint created: ${shortId(checkpoint.id)} "${checkpoint.label}" (forensic retention).`);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
ctx.print(`Checkpoint failed: ${summarizeError(err)}`);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
registry.register({
|
|
173
|
+
name: 'rewind',
|
|
174
|
+
description: 'Preview and restore a workspace checkpoint (files only — conversation history is unchanged)',
|
|
175
|
+
usage: '<id|last>',
|
|
176
|
+
argsHint: '<id|last>',
|
|
177
|
+
handler: async (args, ctx: CommandContext) => {
|
|
178
|
+
const mgr = ctx.workspace.workspaceCheckpointManager;
|
|
179
|
+
if (!mgr) {
|
|
180
|
+
ctx.print('Checkpoints are not available in this session.');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const ref = args[0];
|
|
184
|
+
if (!ref) {
|
|
185
|
+
ctx.print('Usage: /rewind <id|last>. Run /checkpoints to see available checkpoints.');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const resolved = await resolveCheckpointTarget(mgr, ref);
|
|
190
|
+
if ('error' in resolved) {
|
|
191
|
+
ctx.print(resolved.error);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const { checkpoint } = resolved;
|
|
195
|
+
|
|
196
|
+
let diff: Awaited<ReturnType<WorkspaceCheckpointManager['diff']>>;
|
|
197
|
+
try {
|
|
198
|
+
diff = await mgr.diff(checkpoint.id);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
ctx.print(`Could not load checkpoint preview: ${summarizeError(err)}`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const { DiffPanel } = await import('../../panels/diff-panel.ts');
|
|
205
|
+
const pm = requirePanelManager(ctx);
|
|
206
|
+
let panel = pm.getAllOpen().find((p) => p.id === 'diff');
|
|
207
|
+
if (!panel) {
|
|
208
|
+
try {
|
|
209
|
+
panel = pm.open('diff');
|
|
210
|
+
} catch {
|
|
211
|
+
ctx.print('Could not open diff panel.');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
pm.activateById('diff');
|
|
216
|
+
if (!pm.isVisible()) pm.show();
|
|
217
|
+
// Must focus the panel — otherwise the user is left typing at the
|
|
218
|
+
// prompt while the panel silently shows a pending confirm they never
|
|
219
|
+
// see (the single most likely UX bug in this feature).
|
|
220
|
+
ctx.focusPanels?.();
|
|
221
|
+
|
|
222
|
+
const diffPanel = panel as InstanceType<typeof DiffPanel>;
|
|
223
|
+
if (diff.unifiedDiff.trim()) {
|
|
224
|
+
diffPanel.loadRawDiff(diff.unifiedDiff);
|
|
225
|
+
} else {
|
|
226
|
+
diffPanel.showDiff('(no changes)', '@@ -0,0 +0,0 @@\n Working tree already matches this checkpoint.');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
diffPanel.confirmOverlay.arm({
|
|
230
|
+
id: checkpoint.id,
|
|
231
|
+
// renderConfirmLines (panels/confirm-state.ts) builds the on-screen
|
|
232
|
+
// prompt as `${verb} "${label}"?` itself — it supplies both the verb
|
|
233
|
+
// and the surrounding quotes. A label that already contained "Restore
|
|
234
|
+
// "..."" duplicated both, rendering as `Restore "Restore
|
|
235
|
+
// "baseline-pin"…`. label here must be just the descriptive subject,
|
|
236
|
+
// with no verb and no quotes of its own (same convention GitPanel
|
|
237
|
+
// already follows for its own confirms — see git-panel.ts:352,370).
|
|
238
|
+
label: `${checkpoint.label} (${plural(diff.files.length, 'file')} differ) — files only, conversation history is unchanged`,
|
|
239
|
+
verb: 'Restore',
|
|
240
|
+
onConfirm: async () => {
|
|
241
|
+
try {
|
|
242
|
+
const result = await mgr.restore(checkpoint.id, { safetyCheckpoint: true });
|
|
243
|
+
ctx.print(
|
|
244
|
+
`Rewind complete: restored ${plural(result.restoredFiles.length, 'file')}, removed ${plural(result.removedFiles.length, 'file')}. ` +
|
|
245
|
+
`Safety checkpoint: ${result.safetyCheckpointId ? shortId(result.safetyCheckpointId) : '(none — working tree already matched)'}.`,
|
|
246
|
+
);
|
|
247
|
+
// Conversation is NOT rewound: addSystemMessage (not ctx.print)
|
|
248
|
+
// persists this note into real turn/session history so it is
|
|
249
|
+
// indexed by getTranscriptEventIndex() and session save/load,
|
|
250
|
+
// while staying a distinct message kind from user/assistant turns.
|
|
251
|
+
ctx.session.conversationManager.addSystemMessage(
|
|
252
|
+
`[Rewind] Restored checkpoint "${checkpoint.label}" (${plural(result.restoredFiles.length, 'file')} restored, ${plural(result.removedFiles.length, 'file')} removed). ` +
|
|
253
|
+
'Files were rewound — conversation history is unchanged. Use /undo to remove turns separately.',
|
|
254
|
+
);
|
|
255
|
+
// Auto-close the preview: once the restore has actually happened,
|
|
256
|
+
// the diff panel showing the (now-stale) pre-restore preview has
|
|
257
|
+
// nothing left to confirm and no obvious way to dismiss it
|
|
258
|
+
// otherwise (same pm.close(id) + focusPrompt() pattern as
|
|
259
|
+
// /panel close — see operator-panel-runtime.ts).
|
|
260
|
+
pm.close('diff');
|
|
261
|
+
ctx.focusPrompt?.();
|
|
262
|
+
ctx.renderRequest();
|
|
263
|
+
} catch (err) {
|
|
264
|
+
ctx.print(`Rewind failed: ${summarizeError(err)}`);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
onCancel: () => {
|
|
268
|
+
ctx.print('Rewind cancelled — no files changed.');
|
|
269
|
+
// Same auto-close as the confirm path above — a denied restore also
|
|
270
|
+
// leaves nothing actionable in the preview panel.
|
|
271
|
+
pm.close('diff');
|
|
272
|
+
ctx.focusPrompt?.();
|
|
273
|
+
ctx.renderRequest();
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
ctx.print(`Previewing checkpoint ${shortId(checkpoint.id)} "${checkpoint.label}". Confirm in the diff panel: Enter/y to restore, n/Esc to cancel.`);
|
|
277
|
+
ctx.renderRequest();
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// codebase-runtime.ts — /codebase
|
|
3
|
+
//
|
|
4
|
+
// Explicit-query + transparency surface over the repo source-tree code index
|
|
5
|
+
// (CodeIndexStore, @pellux/goodvibes-sdk/platform/state, landed on SDK main
|
|
6
|
+
// as wo802/W5.3 Stage A) via the TUI's own wiring in
|
|
7
|
+
// src/runtime/code-index-services.ts, threaded through
|
|
8
|
+
// CommandContext.session.codeIndexStore exactly like wrfcController/
|
|
9
|
+
// workstreamEngine already are (bootstrap-command-context.ts).
|
|
10
|
+
//
|
|
11
|
+
// Mirrors /recall's transparency idiom (recall-query.ts's handleRecallVector:
|
|
12
|
+
// status/rebuild) and /workstream's registerXRuntimeCommands function-export
|
|
13
|
+
// shape: build (schedules the index build — visible as a fleet 'code-index'
|
|
14
|
+
// node while it runs), status (counts, skips, degradation state, last
|
|
15
|
+
// build), search <query> (explicit retrieval, results labeled
|
|
16
|
+
// 'lexical'|'semantic' honestly — never implied as more precise than they
|
|
17
|
+
// are). This is Stage A only: no auto-injection into coding turns (Stage B,
|
|
18
|
+
// out of scope — see the wo802/W5.3 design doc).
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
import type { CodeIndexStats, CodeContextResult } from '@pellux/goodvibes-sdk/platform/state';
|
|
22
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
23
|
+
import { isCodeIndexAutoStartEnabled, CODE_INDEX_MAX_FILES, CODE_INDEX_MAX_FILE_BYTES, CODE_INDEX_MAX_TOTAL_BYTES } from '../../runtime/code-index-services.ts';
|
|
24
|
+
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Formatting helpers
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
function formatBytes(bytes: number): string {
|
|
31
|
+
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
32
|
+
if (bytes >= 1024) return `${Math.round(bytes / 1024)}KB`;
|
|
33
|
+
return `${bytes}B`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderCodeIndexStatus(stats: CodeIndexStats, configManager: Pick<ConfigManager, 'get'>): string {
|
|
37
|
+
const lines: string[] = [];
|
|
38
|
+
lines.push(`Code index — backend: ${stats.backend}, available: ${stats.available ? 'yes' : 'no'}`);
|
|
39
|
+
lines.push(` path: ${stats.path}`);
|
|
40
|
+
lines.push(` indexed: ${stats.indexedFiles} file(s), ${stats.indexedChunks} chunk(s), ${stats.dimensions} dims`);
|
|
41
|
+
lines.push(` embedding provider: ${stats.embeddingProviderLabel} (${stats.embeddingProviderId})`);
|
|
42
|
+
lines.push(
|
|
43
|
+
stats.semanticRetrievalAvailable
|
|
44
|
+
? ' search results labeled: semantic'
|
|
45
|
+
: ' search results labeled: lexical (hashed fallback — low precision)',
|
|
46
|
+
);
|
|
47
|
+
const autoStart = isCodeIndexAutoStartEnabled(configManager);
|
|
48
|
+
lines.push(
|
|
49
|
+
` auto-build on startup: ${autoStart ? 'on' : 'off'} (storage.codeIndexEnabled, default off — /config to change)`,
|
|
50
|
+
);
|
|
51
|
+
lines.push(
|
|
52
|
+
` bounds: max ${CODE_INDEX_MAX_FILES} files (maxFiles), ${formatBytes(CODE_INDEX_MAX_FILE_BYTES)} per file (maxFileBytes),`
|
|
53
|
+
+ ` ${formatBytes(CODE_INDEX_MAX_TOTAL_BYTES)} total per build (maxTotalBytes)`,
|
|
54
|
+
);
|
|
55
|
+
// Provider-space honesty (SDK finding): vectors embedded under a different
|
|
56
|
+
// provider than the current default disable the vector search path until a
|
|
57
|
+
// rebuild re-embeds — say so, in the store's own words.
|
|
58
|
+
if (stats.embeddingProviderMismatch) {
|
|
59
|
+
lines.push(` provider mismatch: ${stats.embeddingProviderMismatch} (vector search disabled — lexical fallback only)`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (stats.building) {
|
|
63
|
+
lines.push(' build: in progress');
|
|
64
|
+
} else if (stats.lastBuild) {
|
|
65
|
+
const b = stats.lastBuild;
|
|
66
|
+
lines.push(
|
|
67
|
+
` last build: ${b.filesIndexed} indexed, ${b.filesUnchanged} unchanged, ${b.filesRemoved} removed,`
|
|
68
|
+
+ ` ${b.chunksIndexed} chunk(s) embedded (${b.chunksUnchanged} unchanged), ${b.durationMs}ms`,
|
|
69
|
+
);
|
|
70
|
+
const skip = b.skip;
|
|
71
|
+
const skipParts: string[] = [];
|
|
72
|
+
if (skip.tooLarge) skipParts.push(`${skip.tooLarge} too large`);
|
|
73
|
+
if (skip.overFileCap) skipParts.push(`${skip.overFileCap} over file cap (maxFiles)`);
|
|
74
|
+
if (skip.overTotalBytes) skipParts.push(`${skip.overTotalBytes} over total byte budget (maxTotalBytes)`);
|
|
75
|
+
if (skip.binary) skipParts.push(`${skip.binary} binary`);
|
|
76
|
+
if (skip.ignoredByGitignore) skipParts.push(`${skip.ignoredByGitignore} gitignored`);
|
|
77
|
+
if (skip.readErrors) skipParts.push(`${skip.readErrors} read error(s)`);
|
|
78
|
+
if (skip.chunkedByWindow) skipParts.push(`${skip.chunkedByWindow} windowed (no tree-sitter symbols)`);
|
|
79
|
+
lines.push(skipParts.length > 0 ? ` skipped: ${skipParts.join(', ')}` : ' skipped: none');
|
|
80
|
+
} else {
|
|
81
|
+
lines.push(' last build: never — run /codebase build');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (stats.error) lines.push(` error: ${stats.error}`);
|
|
85
|
+
|
|
86
|
+
const degradation = stats.semanticRetrievalAvailable ? null : describeDegradationFallback(stats);
|
|
87
|
+
if (degradation) lines.push(` ${degradation}`);
|
|
88
|
+
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Reuses the exact wording CodeIndexStore.describeDegradation() would return for a hashed-only provider, without requiring a live call the status renderer doesn't otherwise need. */
|
|
93
|
+
function describeDegradationFallback(stats: CodeIndexStats): string | null {
|
|
94
|
+
if (stats.semanticRetrievalAvailable) return null;
|
|
95
|
+
return 'code auto-retrieval disabled: no semantic embedding provider configured';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function renderSearchResults(results: readonly CodeContextResult[]): string {
|
|
99
|
+
return results
|
|
100
|
+
.map((result) => {
|
|
101
|
+
const c = result.chunk;
|
|
102
|
+
const pct = Math.round(result.similarity * 100);
|
|
103
|
+
const symbol = c.symbol ? ` ${c.symbol}` : '';
|
|
104
|
+
return ` ${c.path}:${c.startLine}-${c.endLine} [${result.label}] sim ${pct}% ${c.kind}${symbol}`;
|
|
105
|
+
})
|
|
106
|
+
.join('\n');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function parseLimitArg(args: readonly string[]): { readonly limit: number | undefined; readonly queryTokens: string[] } {
|
|
110
|
+
const limitIdx = args.indexOf('--limit');
|
|
111
|
+
let limit: number | undefined;
|
|
112
|
+
if (limitIdx !== -1 && args[limitIdx + 1]) {
|
|
113
|
+
const parsed = parseInt(args[limitIdx + 1]!, 10);
|
|
114
|
+
if (!Number.isNaN(parsed)) limit = parsed;
|
|
115
|
+
}
|
|
116
|
+
const queryTokens = args.filter((token, index) => {
|
|
117
|
+
if (token === '--limit') return false;
|
|
118
|
+
if (limitIdx !== -1 && index === limitIdx + 1) return false;
|
|
119
|
+
return true;
|
|
120
|
+
});
|
|
121
|
+
return { limit, queryTokens };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const USAGE = 'Usage:\n'
|
|
125
|
+
+ ' /codebase build\n'
|
|
126
|
+
+ ' /codebase status\n'
|
|
127
|
+
+ ' /codebase search <query...> [--limit n]';
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Command
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
export function registerCodebaseRuntimeCommands(registry: CommandRegistry): void {
|
|
134
|
+
registry.register({
|
|
135
|
+
name: 'codebase',
|
|
136
|
+
description: 'Repo source-tree code index — build, inspect, and search (Wave-5 W5.3 Stage A)',
|
|
137
|
+
usage: 'build | status | search <query...> [--limit n]',
|
|
138
|
+
argsHint: 'build | status | search <query>',
|
|
139
|
+
handler(args: string[], ctx: CommandContext) {
|
|
140
|
+
const store = ctx.session.codeIndexStore;
|
|
141
|
+
if (!store) {
|
|
142
|
+
ctx.print('The code index is not available in this session.');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const sub = args[0];
|
|
147
|
+
|
|
148
|
+
if (!sub || sub === 'status') {
|
|
149
|
+
ctx.print(renderCodeIndexStatus(store.stats(), ctx.platform.configManager));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (sub === 'build' || sub === 'reindex') {
|
|
154
|
+
if (store.isBuilding()) {
|
|
155
|
+
const progress = store.buildProgress();
|
|
156
|
+
ctx.print(
|
|
157
|
+
progress
|
|
158
|
+
? `A build is already in progress (${progress.scanned}/${progress.total} files scanned).`
|
|
159
|
+
: 'A build is already in progress.',
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
store.scheduleBuild();
|
|
164
|
+
ctx.print('Build scheduled — track progress with /codebase status or the fleet panel (code-index node).');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (sub === 'search') {
|
|
169
|
+
const { limit, queryTokens } = parseLimitArg(args.slice(1));
|
|
170
|
+
const query = queryTokens.join(' ').trim();
|
|
171
|
+
if (!query) {
|
|
172
|
+
ctx.print('Usage: /codebase search <query...> [--limit n]');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const results = store.search(query, limit !== undefined ? { limit } : undefined);
|
|
176
|
+
if (results.length === 0) {
|
|
177
|
+
const stats = store.stats();
|
|
178
|
+
ctx.print(
|
|
179
|
+
stats.indexedChunks === 0
|
|
180
|
+
? 'No results — the code index is empty. Run /codebase build first.'
|
|
181
|
+
: 'No matching chunks found.',
|
|
182
|
+
);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
ctx.print(`${results.length} result(s):\n${renderSearchResults(results)}`);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
ctx.print(USAGE);
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
3
|
-
import
|
|
3
|
+
import { GitService } from '@pellux/goodvibes-sdk/platform/git';
|
|
4
|
+
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
4
5
|
import { requirePanelManager, requireSessionChangeTracker, requireShellPaths } from './runtime-services.ts';
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Run a git command that lists changed file names, capturing stderr instead
|
|
9
|
+
* of letting it fall through to the process's real stderr (which, for a
|
|
10
|
+
* spawned child with no stdio option, is inherited from the parent — i.e.
|
|
11
|
+
* written straight to the TUI's controlling terminal, corrupting the screen
|
|
12
|
+
* outside the renderer's front/back-buffer diffing). Mirrors the 'staged'
|
|
13
|
+
* subcommand's existing pattern below.
|
|
14
|
+
*/
|
|
15
|
+
async function runGitNameOnly(args: string[], cwd: string): Promise<{ files: string[]; errText: string; ok: boolean }> {
|
|
16
|
+
const proc = Bun.spawn(args, { stdout: 'pipe', stderr: 'pipe', cwd });
|
|
17
|
+
const [out, errRaw] = await Promise.all([
|
|
18
|
+
new Response(proc.stdout).text(),
|
|
19
|
+
new Response(proc.stderr).text(),
|
|
20
|
+
]);
|
|
21
|
+
const exitCode = await proc.exited;
|
|
22
|
+
return {
|
|
23
|
+
files: out.trim().split('\n').filter(Boolean),
|
|
24
|
+
errText: errRaw.trim(),
|
|
25
|
+
ok: exitCode === 0,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Report a failed name-only fetch through the normal print channel and force
|
|
30
|
+
* a full repaint on the next frame as defense-in-depth, in case anything did
|
|
31
|
+
* reach the real tty. */
|
|
32
|
+
function reportGitNameOnlyFailure(ctx: CommandContext, label: string, errText: string): void {
|
|
33
|
+
ctx.print(`${label} failed: ${errText || 'unknown error'}`);
|
|
34
|
+
ctx.requestFullRepaint?.();
|
|
35
|
+
}
|
|
36
|
+
|
|
6
37
|
async function enrichSemanticDiff(
|
|
7
38
|
panel: InstanceType<typeof import('../../panels/diff-panel.ts').DiffPanel>,
|
|
8
39
|
files: string[],
|
|
@@ -12,7 +43,7 @@ async function enrichSemanticDiff(
|
|
|
12
43
|
): Promise<void> {
|
|
13
44
|
const { computeSemanticDiff, formatSemanticDiffSummary } = await import('../../renderer/semantic-diff.ts');
|
|
14
45
|
const { relative: pathRelative } = await import('path');
|
|
15
|
-
const repoRootProc = Bun.spawn(['git', 'rev-parse', '--show-toplevel'], { stdout: 'pipe', cwd: workingDirectory });
|
|
46
|
+
const repoRootProc = Bun.spawn(['git', 'rev-parse', '--show-toplevel'], { stdout: 'pipe', stderr: 'pipe', cwd: workingDirectory });
|
|
16
47
|
await repoRootProc.exited;
|
|
17
48
|
const repoRoot = (await new Response(repoRootProc.stdout).text()).trim() || workingDirectory;
|
|
18
49
|
await Promise.allSettled(
|
|
@@ -57,6 +88,14 @@ export function registerDiffRuntimeCommands(registry: CommandRegistry): void {
|
|
|
57
88
|
async handler(args, ctx) {
|
|
58
89
|
const shellPaths = requireShellPaths(ctx);
|
|
59
90
|
const workingDirectory = shellPaths.workingDirectory;
|
|
91
|
+
// Gate before any git spawn happens (same defensive check /git already
|
|
92
|
+
// has) — every subcommand below shells out to git, and without this
|
|
93
|
+
// they each produced a differently-shaped error for the same
|
|
94
|
+
// not-a-git-repo condition.
|
|
95
|
+
if (!GitService.isGitRepo(workingDirectory)) {
|
|
96
|
+
ctx.print('Not a git repository here. Run /git to initialize one.');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
60
99
|
const { DiffPanel } = await import('../../panels/diff-panel.ts');
|
|
61
100
|
|
|
62
101
|
const pm = requirePanelManager(ctx);
|
|
@@ -81,13 +120,11 @@ export function registerDiffRuntimeCommands(registry: CommandRegistry): void {
|
|
|
81
120
|
ctx.print('Loading working-tree diff...');
|
|
82
121
|
await diffPanel.showGitDiff();
|
|
83
122
|
ctx.print('Diff panel updated: working tree changes.');
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (workingChangedFiles.length > 0) {
|
|
90
|
-
enrichSemanticDiff(diffPanel, workingChangedFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
123
|
+
const workingChanged = await runGitNameOnly(['git', 'diff', '--name-only'], workingDirectory);
|
|
124
|
+
if (!workingChanged.ok) {
|
|
125
|
+
reportGitNameOnlyFailure(ctx, 'git diff --name-only', workingChanged.errText);
|
|
126
|
+
} else if (workingChanged.files.length > 0) {
|
|
127
|
+
enrichSemanticDiff(diffPanel, workingChanged.files, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
91
128
|
}
|
|
92
129
|
break;
|
|
93
130
|
}
|
|
@@ -106,13 +143,11 @@ export function registerDiffRuntimeCommands(registry: CommandRegistry): void {
|
|
|
106
143
|
} else {
|
|
107
144
|
diffPanel.loadRawDiff(raw);
|
|
108
145
|
ctx.print('Diff panel updated: staged changes.');
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (stagedChangedFiles.length > 0) {
|
|
115
|
-
enrichSemanticDiff(diffPanel, stagedChangedFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
146
|
+
const stagedChanged = await runGitNameOnly(['git', 'diff', '--cached', '--name-only'], workingDirectory);
|
|
147
|
+
if (!stagedChanged.ok) {
|
|
148
|
+
reportGitNameOnlyFailure(ctx, 'git diff --cached --name-only', stagedChanged.errText);
|
|
149
|
+
} else if (stagedChanged.files.length > 0) {
|
|
150
|
+
enrichSemanticDiff(diffPanel, stagedChanged.files, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
116
151
|
}
|
|
117
152
|
}
|
|
118
153
|
break;
|
|
@@ -121,13 +156,11 @@ export function registerDiffRuntimeCommands(registry: CommandRegistry): void {
|
|
|
121
156
|
ctx.print('Loading diff vs HEAD...');
|
|
122
157
|
await diffPanel.showGitDiff('HEAD');
|
|
123
158
|
ctx.print('Diff panel updated: all changes vs HEAD.');
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (headChangedFiles.length > 0) {
|
|
130
|
-
enrichSemanticDiff(diffPanel, headChangedFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
159
|
+
const headChanged = await runGitNameOnly(['git', 'diff', 'HEAD', '--name-only'], workingDirectory);
|
|
160
|
+
if (!headChanged.ok) {
|
|
161
|
+
reportGitNameOnlyFailure(ctx, 'git diff HEAD --name-only', headChanged.errText);
|
|
162
|
+
} else if (headChanged.files.length > 0) {
|
|
163
|
+
enrichSemanticDiff(diffPanel, headChanged.files, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
131
164
|
}
|
|
132
165
|
break;
|
|
133
166
|
}
|
|
@@ -143,13 +176,11 @@ export function registerDiffRuntimeCommands(registry: CommandRegistry): void {
|
|
|
143
176
|
ctx.print('No session changes tracked yet. Showing diff vs HEAD...');
|
|
144
177
|
await diffPanel.showGitDiff('HEAD');
|
|
145
178
|
ctx.print('Diff panel updated: all changes vs HEAD.');
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (fallbackFiles.length > 0) {
|
|
152
|
-
enrichSemanticDiff(diffPanel, fallbackFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
179
|
+
const fallback = await runGitNameOnly(['git', 'diff', 'HEAD', '--name-only'], workingDirectory);
|
|
180
|
+
if (!fallback.ok) {
|
|
181
|
+
reportGitNameOnlyFailure(ctx, 'git diff HEAD --name-only', fallback.errText);
|
|
182
|
+
} else if (fallback.files.length > 0) {
|
|
183
|
+
enrichSemanticDiff(diffPanel, fallback.files, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
153
184
|
}
|
|
154
185
|
}
|
|
155
186
|
break;
|
|
@@ -14,7 +14,7 @@ import { EvalRunner } from '@/runtime/index.ts';
|
|
|
14
14
|
import { BUILTIN_SUITES } from '@/runtime/index.ts';
|
|
15
15
|
import { formatScorecard } from '@/runtime/index.ts';
|
|
16
16
|
import { loadBaseline, captureBaseline, formatBaselineComparison, writeBaseline } from '@/runtime/index.ts';
|
|
17
|
-
import type { EvalRegistry } from '../../panels/eval-
|
|
17
|
+
import type { EvalRegistry } from '../../panels/eval-registry.ts';
|
|
18
18
|
import { formatSuiteResult, formatGateResult } from '@/runtime/index.ts';
|
|
19
19
|
import { requireShellPaths } from './runtime-services.ts';
|
|
20
20
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
@@ -45,7 +45,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
45
45
|
const readModels = requireReadModels(ctx);
|
|
46
46
|
|
|
47
47
|
if (sub === 'open' || sub === 'panel' || sub === 'provider') {
|
|
48
|
-
|
|
48
|
+
ctx.openModal?.('providers-modal'); // W6.1: provider-health panel -> config modal
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
|