@mrclrchtr/supi-code-intelligence 6.2.0 → 6.4.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 +2 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/README.md +52 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +785 -129
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +660 -98
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +10 -4
- package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +16 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +4 -32
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sources.ts +108 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +106 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +4 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +513 -113
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +43 -15
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +40 -33
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +36 -6
- package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/workspace-path-policy.ts +18 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/README.md +29 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs +99 -19
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js +94 -18
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +14 -14
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +13 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs +97 -19
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts +28 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts.map +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts +28 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts.map +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js +92 -18
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +17 -17
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +7 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +2 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +2 -2
- package/package.json +5 -5
- package/src/session/health-refresh.ts +19 -12
- package/src/session/health-types.ts +8 -3
- package/src/session/health-workflow.ts +7 -3
- package/src/session/session.ts +12 -5
- package/src/substrate/lsp/maintenance.ts +53 -47
- package/src/substrate/lsp/settings.ts +2 -1
- package/src/substrate/lsp/source-tracking.ts +247 -0
- package/src/tool/code_find/guidance.ts +3 -3
- package/src/tool/code_graph/guidance.ts +2 -2
- package/src/tool/code_health/guidance.ts +3 -3
- package/src/tool/code_health/markdown.ts +21 -5
- package/src/tool/code_health/refresh-outcome.ts +48 -7
- package/src/tool/code_health/refresh-status.ts +83 -16
- package/src/tool/code_health/tui.ts +4 -0
- package/src/tool/code_inspect/guidance.ts +5 -3
- package/src/tool/code_orientation/guidance.ts +3 -4
- package/src/tool/code_refactor_apply/guidance.ts +1 -1
- package/src/tool/code_refactor_plan/guidance.ts +3 -5
- package/src/tool/code_resolve/guidance.ts +3 -3
- package/src/tool/guidance.ts +11 -9
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
type CodeRequestControl,
|
|
5
|
+
throwIfCodeRequestInterrupted,
|
|
6
|
+
} from "@mrclrchtr/supi-code-runtime/api";
|
|
7
|
+
import { isWithinOrEqual } from "@mrclrchtr/supi-core/api";
|
|
8
|
+
import type {
|
|
9
|
+
BulkTrackFileOutcome,
|
|
10
|
+
WorkspaceLspRuntime,
|
|
11
|
+
WorkspaceSourceInventory,
|
|
12
|
+
} from "@mrclrchtr/supi-lsp/api";
|
|
13
|
+
import { isMissingFileError, MAX_BULK_TRACK_FILES } from "@mrclrchtr/supi-lsp/api";
|
|
14
|
+
|
|
15
|
+
/** Typed session state for sentinel maintenance and automatic source tracking. */
|
|
16
|
+
export interface LspMaintenanceState {
|
|
17
|
+
readonly sentinelSnapshot: Map<string, number>;
|
|
18
|
+
readonly sourceBaseline: ReadonlySet<string> | null;
|
|
19
|
+
readonly createdSourceQueue: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Source discovery and tracking facts from one broad diagnostic refresh. */
|
|
23
|
+
export interface SourceTrackingReport {
|
|
24
|
+
readonly status: WorkspaceSourceInventory["status"];
|
|
25
|
+
readonly reason: WorkspaceSourceInventory["reason"];
|
|
26
|
+
readonly observedFileCount: number;
|
|
27
|
+
readonly discovered: readonly string[];
|
|
28
|
+
readonly tracked: readonly string[];
|
|
29
|
+
readonly unsupported: readonly string[];
|
|
30
|
+
readonly unavailable: readonly string[];
|
|
31
|
+
readonly deferred: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Create empty source state with an optional lifecycle-seeded sentinel snapshot. */
|
|
35
|
+
export function createLspMaintenanceState(
|
|
36
|
+
sentinelSnapshot: Map<string, number> = new Map(),
|
|
37
|
+
): LspMaintenanceState {
|
|
38
|
+
return {
|
|
39
|
+
sentinelSnapshot,
|
|
40
|
+
sourceBaseline: null,
|
|
41
|
+
createdSourceQueue: [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Replace the sentinel portion of maintenance state without source state. */
|
|
46
|
+
export function withSentinelSnapshot(
|
|
47
|
+
state: LspMaintenanceState,
|
|
48
|
+
sentinelSnapshot: Map<string, number>,
|
|
49
|
+
): LspMaintenanceState {
|
|
50
|
+
return { ...state, sentinelSnapshot };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Discover source additions and process a bounded queue for one refresh. */
|
|
54
|
+
export async function trackCreatedSources(options: {
|
|
55
|
+
readonly runtime: WorkspaceLspRuntime;
|
|
56
|
+
readonly cwd: string;
|
|
57
|
+
readonly state: LspMaintenanceState;
|
|
58
|
+
readonly scope?: string | null;
|
|
59
|
+
readonly control?: CodeRequestControl;
|
|
60
|
+
}): Promise<{ state: LspMaintenanceState; report: SourceTrackingReport }> {
|
|
61
|
+
const inventory = await options.runtime.scanWorkspaceSources(options.control);
|
|
62
|
+
throwIfCodeRequestInterrupted(options.control);
|
|
63
|
+
if (inventory.status === "limited") {
|
|
64
|
+
return {
|
|
65
|
+
state: options.state,
|
|
66
|
+
report: createReport({
|
|
67
|
+
cwd: options.cwd,
|
|
68
|
+
inventory,
|
|
69
|
+
discovered: [],
|
|
70
|
+
tracked: [],
|
|
71
|
+
unsupported: [],
|
|
72
|
+
unavailable: [],
|
|
73
|
+
deferred: options.state.createdSourceQueue,
|
|
74
|
+
control: options.control,
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const discovered = discoverCreatedPaths(
|
|
80
|
+
options.state.sourceBaseline,
|
|
81
|
+
inventory.files,
|
|
82
|
+
options.control,
|
|
83
|
+
);
|
|
84
|
+
const queue = removeMissingPaths(
|
|
85
|
+
options.state.sourceBaseline === null
|
|
86
|
+
? []
|
|
87
|
+
: deduplicatePaths([...options.state.createdSourceQueue, ...discovered], options.control),
|
|
88
|
+
options.control,
|
|
89
|
+
);
|
|
90
|
+
const selected = selectScopedPaths(queue, options.cwd, options.scope, options.control).slice(
|
|
91
|
+
0,
|
|
92
|
+
MAX_BULK_TRACK_FILES,
|
|
93
|
+
);
|
|
94
|
+
const batch =
|
|
95
|
+
selected.length === 0
|
|
96
|
+
? { outcomes: [] as const }
|
|
97
|
+
: await options.runtime.bulkTrackFiles(selected, options.control);
|
|
98
|
+
const nextQueue = retainQueuePaths(queue, selected, batch.outcomes, options.control);
|
|
99
|
+
const nextState: LspMaintenanceState = {
|
|
100
|
+
sentinelSnapshot: options.state.sentinelSnapshot,
|
|
101
|
+
sourceBaseline: new Set(inventory.files),
|
|
102
|
+
createdSourceQueue: nextQueue,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const tracked = pathsForOutcome(batch.outcomes, "tracked");
|
|
106
|
+
const unsupported = pathsForOutcome(batch.outcomes, "unsupported");
|
|
107
|
+
const unavailable = pathsForOutcome(batch.outcomes, "unavailable");
|
|
108
|
+
return {
|
|
109
|
+
state: nextState,
|
|
110
|
+
report: createReport({
|
|
111
|
+
cwd: options.cwd,
|
|
112
|
+
inventory,
|
|
113
|
+
discovered,
|
|
114
|
+
tracked,
|
|
115
|
+
unsupported,
|
|
116
|
+
unavailable,
|
|
117
|
+
deferred: nextQueue,
|
|
118
|
+
control: options.control,
|
|
119
|
+
}),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function discoverCreatedPaths(
|
|
124
|
+
baseline: ReadonlySet<string> | null,
|
|
125
|
+
current: readonly string[],
|
|
126
|
+
control?: CodeRequestControl,
|
|
127
|
+
): string[] {
|
|
128
|
+
if (baseline === null) return [];
|
|
129
|
+
const discovered: string[] = [];
|
|
130
|
+
for (const filePath of current) {
|
|
131
|
+
throwIfCodeRequestInterrupted(control);
|
|
132
|
+
if (!baseline.has(filePath)) discovered.push(filePath);
|
|
133
|
+
}
|
|
134
|
+
return discovered;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function selectScopedPaths(
|
|
138
|
+
queue: readonly string[],
|
|
139
|
+
cwd: string,
|
|
140
|
+
scope: string | null | undefined,
|
|
141
|
+
control?: CodeRequestControl,
|
|
142
|
+
): string[] {
|
|
143
|
+
const resolvedScope = scope === null || scope === undefined ? null : path.resolve(cwd, scope);
|
|
144
|
+
const selected: string[] = [];
|
|
145
|
+
for (const filePath of queue) {
|
|
146
|
+
throwIfCodeRequestInterrupted(control);
|
|
147
|
+
if (resolvedScope === null || isWithinOrEqual(resolvedScope, filePath)) selected.push(filePath);
|
|
148
|
+
}
|
|
149
|
+
return selected;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function retainQueuePaths(
|
|
153
|
+
queue: readonly string[],
|
|
154
|
+
selected: readonly string[],
|
|
155
|
+
outcomes: readonly BulkTrackFileOutcome[],
|
|
156
|
+
control?: CodeRequestControl,
|
|
157
|
+
): string[] {
|
|
158
|
+
const selectedSet = new Set(selected);
|
|
159
|
+
const outcomeByPath = new Map(outcomes.map((outcome) => [outcome.file, outcome] as const));
|
|
160
|
+
const unstarted: string[] = [];
|
|
161
|
+
const unavailable: string[] = [];
|
|
162
|
+
|
|
163
|
+
for (const filePath of queue) {
|
|
164
|
+
throwIfCodeRequestInterrupted(control);
|
|
165
|
+
if (!selectedSet.has(filePath)) {
|
|
166
|
+
unstarted.push(filePath);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const outcome = outcomeByPath.get(filePath);
|
|
170
|
+
if (!outcome) {
|
|
171
|
+
unstarted.push(filePath);
|
|
172
|
+
} else if (outcome.kind === "unavailable") {
|
|
173
|
+
unavailable.push(filePath);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return [...unstarted, ...unavailable];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function pathsForOutcome(
|
|
180
|
+
outcomes: readonly BulkTrackFileOutcome[],
|
|
181
|
+
kind: BulkTrackFileOutcome["kind"],
|
|
182
|
+
): string[] {
|
|
183
|
+
return outcomes.filter((outcome) => outcome.kind === kind).map((outcome) => outcome.file);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function createReport(options: {
|
|
187
|
+
cwd: string;
|
|
188
|
+
inventory: WorkspaceSourceInventory;
|
|
189
|
+
discovered: readonly string[];
|
|
190
|
+
tracked: readonly string[];
|
|
191
|
+
unsupported: readonly string[];
|
|
192
|
+
unavailable: readonly string[];
|
|
193
|
+
deferred: readonly string[];
|
|
194
|
+
control?: CodeRequestControl;
|
|
195
|
+
}): SourceTrackingReport {
|
|
196
|
+
return {
|
|
197
|
+
status: options.inventory.status,
|
|
198
|
+
reason: options.inventory.reason,
|
|
199
|
+
observedFileCount: options.inventory.observedFileCount,
|
|
200
|
+
discovered: displayPaths(options.cwd, options.discovered, options.control),
|
|
201
|
+
tracked: displayPaths(options.cwd, options.tracked, options.control),
|
|
202
|
+
unsupported: displayPaths(options.cwd, options.unsupported, options.control),
|
|
203
|
+
unavailable: displayPaths(options.cwd, options.unavailable, options.control),
|
|
204
|
+
deferred: options.deferred.length,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function displayPaths(
|
|
209
|
+
cwd: string,
|
|
210
|
+
filePaths: readonly string[],
|
|
211
|
+
control?: CodeRequestControl,
|
|
212
|
+
): string[] {
|
|
213
|
+
const displayed: string[] = [];
|
|
214
|
+
for (const filePath of filePaths) {
|
|
215
|
+
throwIfCodeRequestInterrupted(control);
|
|
216
|
+
const relativePath = path.relative(cwd, filePath);
|
|
217
|
+
displayed.push((relativePath || path.basename(filePath)).replaceAll(path.sep, "/"));
|
|
218
|
+
}
|
|
219
|
+
return displayed;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function deduplicatePaths(filePaths: readonly string[], control?: CodeRequestControl): string[] {
|
|
223
|
+
const unique = new Set<string>();
|
|
224
|
+
for (const filePath of filePaths) {
|
|
225
|
+
throwIfCodeRequestInterrupted(control);
|
|
226
|
+
unique.add(filePath);
|
|
227
|
+
}
|
|
228
|
+
return [...unique];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function removeMissingPaths(filePaths: readonly string[], control?: CodeRequestControl): string[] {
|
|
232
|
+
const existing: string[] = [];
|
|
233
|
+
for (const filePath of filePaths) {
|
|
234
|
+
throwIfCodeRequestInterrupted(control);
|
|
235
|
+
if (!isMissingFile(filePath)) existing.push(filePath);
|
|
236
|
+
}
|
|
237
|
+
return existing;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isMissingFile(filePath: string): boolean {
|
|
241
|
+
try {
|
|
242
|
+
statSync(filePath);
|
|
243
|
+
return false;
|
|
244
|
+
} catch (error) {
|
|
245
|
+
return isMissingFileError(error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const toolDescription =
|
|
2
|
-
|
|
2
|
+
"Search code structure or workspace symbols for structural or semantic matches. It never silently falls back to another mode or to text search.";
|
|
3
3
|
|
|
4
|
-
export const promptSnippet = "
|
|
4
|
+
export const promptSnippet = "search code structure or workspace symbols";
|
|
5
5
|
|
|
6
6
|
export const promptGuidelines = [
|
|
7
|
-
"Use code_find for structural or semantic search
|
|
7
|
+
"Use code_find for structural or semantic search, grep for text search, and code_graph for symbol relationships.",
|
|
8
8
|
];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const toolDescription =
|
|
2
|
-
"
|
|
2
|
+
"Find symbol references and implementations, and list calls from a target's enclosing scope. Callee results match source shape, not symbol identity.";
|
|
3
3
|
|
|
4
|
-
export const promptSnippet = "
|
|
4
|
+
export const promptSnippet = "trace code relationships";
|
|
5
5
|
|
|
6
6
|
export const promptGuidelines: string[] = [];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const toolDescription =
|
|
2
|
-
"Report live diagnostics
|
|
2
|
+
"Report live diagnostics and language-server health. Diagnostic snapshots do not prove that the whole workspace is clean; server inventory and route-status counts are always workspace-wide.";
|
|
3
3
|
|
|
4
|
-
export const promptSnippet = "live
|
|
4
|
+
export const promptSnippet = "check live diagnostics and language-server health";
|
|
5
5
|
|
|
6
6
|
export const promptGuidelines = [
|
|
7
|
-
"Use code_health with refresh:true before relying on
|
|
7
|
+
"Use code_health with `refresh: true` before relying on diagnostics that can be stale.",
|
|
8
8
|
];
|
|
@@ -15,6 +15,7 @@ import { makeRelative, renderFileScopeStatus } from "./file-scope-markdown.ts";
|
|
|
15
15
|
import {
|
|
16
16
|
formatProcessCrashRecovery,
|
|
17
17
|
formatRefreshElapsed,
|
|
18
|
+
formatSourceTracking,
|
|
18
19
|
formatStaleDiagnosticRestarts as formatStaleDiagnosticRestartsText,
|
|
19
20
|
isFileReadinessPending,
|
|
20
21
|
} from "./refresh-outcome.ts";
|
|
@@ -57,8 +58,9 @@ function renderRefreshStatus(
|
|
|
57
58
|
case "completed": {
|
|
58
59
|
const fileReadinessPending = isFileReadinessPending(data.refresh, data.semanticState);
|
|
59
60
|
lines.push(
|
|
60
|
-
`**${refreshAttemptLabel(data.refresh)}**: ${asSentence(completedRefreshText(data.refresh, fileReadinessPending))}`,
|
|
61
|
+
`**${refreshAttemptLabel(data.refresh)}**: ${asSentence(completedRefreshText(data.refresh, fileReadinessPending, false))}`,
|
|
61
62
|
);
|
|
63
|
+
renderSourceTracking(lines, data.refresh);
|
|
62
64
|
lines.push(`**Stale assessment**: ${asSentence(staleAssessmentText(data.refresh))}`);
|
|
63
65
|
lines.push("");
|
|
64
66
|
return;
|
|
@@ -74,6 +76,7 @@ function renderRefreshStatus(
|
|
|
74
76
|
lines.push(
|
|
75
77
|
`**${refreshAttemptLabel(data.refresh)}**: failed — ${data.refresh.reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${evidence}`,
|
|
76
78
|
);
|
|
79
|
+
renderSourceTracking(lines, data.refresh);
|
|
77
80
|
lines.push("");
|
|
78
81
|
return;
|
|
79
82
|
}
|
|
@@ -97,6 +100,7 @@ function renderRefreshStatus(
|
|
|
97
100
|
function completedRefreshText(
|
|
98
101
|
attempt: Extract<HealthRefreshAttempt, { kind: "completed" }>,
|
|
99
102
|
fileReadinessPending = attempt.fileReadiness === "pending",
|
|
103
|
+
includeSourceTracking = true,
|
|
100
104
|
): string {
|
|
101
105
|
const processCrashRecovery = formatProcessCrashRecovery(attempt.processCrashRecovery);
|
|
102
106
|
const noOp =
|
|
@@ -110,15 +114,26 @@ function completedRefreshText(
|
|
|
110
114
|
? "completed no-op — no active clients were targeted"
|
|
111
115
|
: `completed — ${attempt.attemptedActiveClients} active client${plural(attempt.attemptedActiveClients)} targeted`;
|
|
112
116
|
const staleRestart = noOp ? null : formatStaleDiagnosticRestarts(attempt);
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
const sourceTracking = formatSourceTracking(attempt.sourceTracking);
|
|
118
|
+
const outcome = [
|
|
119
|
+
includeSourceTracking ? sourceTracking : null,
|
|
120
|
+
staleRestart,
|
|
121
|
+
processCrashRecovery,
|
|
122
|
+
].filter((value): value is string => value !== null);
|
|
116
123
|
const withOutcome = outcome.length > 0 ? `${base}; ${outcome.join("; ")}` : base;
|
|
117
124
|
return attempt.operationScope === "workspace-runtime"
|
|
118
125
|
? `${withOutcome}; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
|
|
119
126
|
: withOutcome;
|
|
120
127
|
}
|
|
121
128
|
|
|
129
|
+
function renderSourceTracking(
|
|
130
|
+
lines: string[],
|
|
131
|
+
attempt: Pick<HealthRefreshAttempt, "sourceTracking">,
|
|
132
|
+
): void {
|
|
133
|
+
const sourceTracking = formatSourceTracking(attempt.sourceTracking);
|
|
134
|
+
if (sourceTracking) lines.push(`**Source discovery**: ${asSentence(sourceTracking)}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
122
137
|
function refreshAttemptLabel(attempt: Pick<HealthRefreshAttempt, "operationScope">): string {
|
|
123
138
|
return attempt.operationScope === "file-runtime"
|
|
124
139
|
? "File LSP maintenance attempt"
|
|
@@ -160,10 +175,11 @@ function refreshAttemptOutcome(attempt: HealthRefreshAttempt): string {
|
|
|
160
175
|
? formatProcessCrashRecovery(attempt.processCrashRecovery)
|
|
161
176
|
: null;
|
|
162
177
|
const staleRestart = formatStaleDiagnosticRestarts(attempt);
|
|
178
|
+
const sourceTracking = formatSourceTracking(attempt.sourceTracking);
|
|
163
179
|
const evidence = attempt.diagnosticEvidence
|
|
164
180
|
? `; ${formatDiagnosticEvidence(attempt.diagnosticEvidence)}`
|
|
165
181
|
: "";
|
|
166
|
-
return `failed — ${attempt.reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${evidence}`;
|
|
182
|
+
return `failed — ${attempt.reason}${staleRestart ? `; ${staleRestart}` : ""}${processCrashRecovery ? `; ${processCrashRecovery}` : ""}${sourceTracking ? `; ${sourceTracking}` : ""}${evidence}`;
|
|
167
183
|
}
|
|
168
184
|
|
|
169
185
|
function refreshOperationScopeText(attempt: HealthRefreshAttempt): string {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ProcessCrashRecoveryEntry,
|
|
3
|
+
ProcessCrashRecoveryReport,
|
|
4
|
+
} from "@mrclrchtr/supi-lsp/api";
|
|
2
5
|
import type { SemanticHealthState } from "../../session/health-types.ts";
|
|
6
|
+
import type { SourceTrackingReport } from "../../substrate/lsp/source-tracking.ts";
|
|
3
7
|
|
|
4
8
|
/** Report whether a file health result is waiting for semantic readiness. */
|
|
5
9
|
export function isFileReadinessPending(
|
|
@@ -21,17 +25,54 @@ export function formatStaleDiagnosticRestarts(restartedClients: number): string
|
|
|
21
25
|
return `stale diagnostic restarts: ${restartedClients} client${plural(restartedClients)} restarted`;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
/** Format
|
|
25
|
-
export function
|
|
26
|
-
|
|
28
|
+
/** Format process-crash counts for the compact health view. */
|
|
29
|
+
export function formatCompactProcessCrashRecovery(
|
|
30
|
+
report: ProcessCrashRecoveryReport | null | undefined,
|
|
27
31
|
): string | null {
|
|
28
32
|
if (
|
|
29
|
-
!
|
|
30
|
-
(
|
|
33
|
+
!report ||
|
|
34
|
+
(report.recoveredRoutes === 0 &&
|
|
35
|
+
report.skippedRoutes === 0 &&
|
|
36
|
+
report.failedRoutes === 0 &&
|
|
37
|
+
report.exhaustedRoutes === 0)
|
|
31
38
|
) {
|
|
32
39
|
return null;
|
|
33
40
|
}
|
|
34
|
-
return `process-crash recovery: ${
|
|
41
|
+
return `process-crash recovery: ${report.recoveredRoutes} route${plural(report.recoveredRoutes)} recovered (${report.skippedRoutes} skipped, ${report.failedRoutes} failed, ${report.exhaustedRoutes} exhausted)`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Format process-crash counts and bounded route entries for detailed health output. */
|
|
45
|
+
export function formatProcessCrashRecovery(
|
|
46
|
+
report: ProcessCrashRecoveryReport | null | undefined,
|
|
47
|
+
): string | null {
|
|
48
|
+
const summary = formatCompactProcessCrashRecovery(report);
|
|
49
|
+
if (!summary || !report) return summary;
|
|
50
|
+
const entries = report.entries.map(formatProcessCrashRecoveryEntry);
|
|
51
|
+
const omitted =
|
|
52
|
+
report.omittedEntries > 0
|
|
53
|
+
? `${report.omittedEntries} more route${plural(report.omittedEntries)}`
|
|
54
|
+
: null;
|
|
55
|
+
const details = [...entries, omitted]
|
|
56
|
+
.filter((value): value is string => value !== null)
|
|
57
|
+
.join("; ");
|
|
58
|
+
return details ? `${summary}; ${details}` : summary;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function formatProcessCrashRecoveryEntry(entry: ProcessCrashRecoveryEntry): string {
|
|
62
|
+
const outcome = entry.outcome.replaceAll("-", " ");
|
|
63
|
+
const action = entry.nextAction ? `; next: ${entry.nextAction.replaceAll("-", " ")}` : "";
|
|
64
|
+
const failure = entry.failureMessage ? `; ${entry.failureMessage}` : "";
|
|
65
|
+
return `${entry.name} @ ${entry.root}: ${outcome}${action}${failure}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Format source discovery and bounded tracking counts separately from diagnostics. */
|
|
69
|
+
export function formatSourceTracking(
|
|
70
|
+
report: SourceTrackingReport | null | undefined,
|
|
71
|
+
): string | null {
|
|
72
|
+
if (!report) return null;
|
|
73
|
+
const inventory =
|
|
74
|
+
report.status === "complete" ? "complete" : `limited (${report.reason ?? "unknown reason"})`;
|
|
75
|
+
return `source discovery: ${inventory}; ${report.observedFileCount} source files observed, ${report.discovered.length} discovered, ${report.tracked.length} tracked, ${report.unsupported.length} unsupported, ${report.unavailable.length} unavailable, ${report.deferred} deferred`;
|
|
35
76
|
}
|
|
36
77
|
|
|
37
78
|
/** Format the age of a retained refresh attempt for a status line. */
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import type { ProcessCrashRecoverySummary } from "@mrclrchtr/supi-lsp/api";
|
|
2
1
|
import {
|
|
2
|
+
MAX_PROCESS_CRASH_RECOVERY_ENTRIES,
|
|
3
|
+
type ProcessCrashRecoveryEntry,
|
|
4
|
+
type ProcessCrashRecoveryOutcome,
|
|
5
|
+
type ProcessCrashRecoveryReport,
|
|
6
|
+
} from "@mrclrchtr/supi-lsp/api";
|
|
7
|
+
import {
|
|
8
|
+
formatCompactProcessCrashRecovery,
|
|
3
9
|
formatProcessCrashRecovery,
|
|
4
10
|
formatStaleDiagnosticRestarts,
|
|
5
11
|
isFileReadinessPending,
|
|
@@ -8,6 +14,14 @@ import { readSemanticHealthState } from "./semantic-state.ts";
|
|
|
8
14
|
|
|
9
15
|
/** Render current and retained health refresh status for the TUI. */
|
|
10
16
|
|
|
17
|
+
/** Read source discovery counts from structured health details. */
|
|
18
|
+
export function readSourceTrackingStatus(source: Record<string, unknown> | null): string | null {
|
|
19
|
+
if (!source || (source.status !== "complete" && source.status !== "limited")) return null;
|
|
20
|
+
const reason =
|
|
21
|
+
source.status === "limited" && typeof source.reason === "string" ? ` (${source.reason})` : "";
|
|
22
|
+
return `${source.status}${reason}: ${readNumber(source.observedFileCount)} observed, ${readArrayLength(source.discovered)} discovered, ${readArrayLength(source.tracked)} tracked, ${readArrayLength(source.unsupported)} unsupported, ${readArrayLength(source.unavailable)} unavailable, ${readNumber(source.deferred)} deferred`;
|
|
23
|
+
}
|
|
24
|
+
|
|
11
25
|
export function readRefreshStatus(data: Record<string, unknown> | null): string | null {
|
|
12
26
|
const refresh = readRecord(data?.refresh);
|
|
13
27
|
if (!refresh) return null;
|
|
@@ -82,19 +96,21 @@ export function readCompactRefreshStatus(data: Record<string, unknown> | null):
|
|
|
82
96
|
const refresh = readRecord(data?.refresh);
|
|
83
97
|
if (!refresh || (refresh.kind !== "completed" && refresh.kind !== "failed")) return null;
|
|
84
98
|
|
|
85
|
-
const
|
|
99
|
+
const sourceTracking = formatSourceTracking(readRecord(refresh.sourceTracking));
|
|
100
|
+
const processCrashRecovery = formatCompactProcessCrashRecovery(
|
|
86
101
|
readProcessCrashRecovery(refresh.processCrashRecovery),
|
|
87
102
|
);
|
|
88
103
|
if (refresh.kind === "failed") {
|
|
89
104
|
const staleRestart = formatStaleDiagnosticRestartsForRecord(refresh);
|
|
90
105
|
return (
|
|
91
|
-
[staleRestart, processCrashRecovery]
|
|
106
|
+
[sourceTracking, staleRestart, processCrashRecovery]
|
|
92
107
|
.filter((value): value is string => value !== null)
|
|
93
108
|
.join("; ") || null
|
|
94
109
|
);
|
|
95
110
|
}
|
|
96
111
|
const fileReadinessPending = isPendingFileReadiness(data);
|
|
97
112
|
if (
|
|
113
|
+
!sourceTracking &&
|
|
98
114
|
!processCrashRecovery &&
|
|
99
115
|
readNumber(refresh.restartedClients) === 0 &&
|
|
100
116
|
!fileReadinessPending
|
|
@@ -104,7 +120,7 @@ export function readCompactRefreshStatus(data: Record<string, unknown> | null):
|
|
|
104
120
|
|
|
105
121
|
const staleRestart = formatStaleDiagnosticRestartsForRecord(refresh);
|
|
106
122
|
const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
|
|
107
|
-
return [staleRestart, processCrashRecovery, readiness]
|
|
123
|
+
return [sourceTracking, staleRestart, processCrashRecovery, readiness]
|
|
108
124
|
.filter((value): value is string => value !== null)
|
|
109
125
|
.join("; ");
|
|
110
126
|
}
|
|
@@ -117,7 +133,8 @@ export function readPreviousRefreshStatus(data: Record<string, unknown> | null):
|
|
|
117
133
|
const attempt = readRecord(refresh.lastAttempt);
|
|
118
134
|
if (!attempt) return null;
|
|
119
135
|
const evidence = formatCompactDiagnosticEvidence(readRecord(attempt.diagnosticEvidence));
|
|
120
|
-
const
|
|
136
|
+
const sourceTracking = formatSourceTracking(readRecord(attempt.sourceTracking));
|
|
137
|
+
const processCrashRecovery = formatCompactProcessCrashRecovery(
|
|
121
138
|
readProcessCrashRecovery(attempt.processCrashRecovery),
|
|
122
139
|
);
|
|
123
140
|
const staleRestart = formatStaleDiagnosticRestartsForRecord(attempt);
|
|
@@ -125,7 +142,7 @@ export function readPreviousRefreshStatus(data: Record<string, unknown> | null):
|
|
|
125
142
|
attempt.kind === "completed" &&
|
|
126
143
|
isFileReadinessPending(attempt, readSemanticHealthState(data?.semanticState));
|
|
127
144
|
const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
|
|
128
|
-
const outcome = [readiness, staleRestart, processCrashRecovery, evidence]
|
|
145
|
+
const outcome = [readiness, sourceTracking, staleRestart, processCrashRecovery, evidence]
|
|
129
146
|
.filter((value): value is string => value !== null)
|
|
130
147
|
.join("; ");
|
|
131
148
|
if (attempt.kind === "failed") {
|
|
@@ -164,6 +181,7 @@ function formatLastRefreshSuffix(refresh: Record<string, unknown>): string {
|
|
|
164
181
|
const attempt = readRecord(refresh.lastAttempt);
|
|
165
182
|
if (!attempt) return "";
|
|
166
183
|
const evidence = formatDiagnosticEvidence(readRecord(attempt.diagnosticEvidence));
|
|
184
|
+
const sourceTracking = formatSourceTracking(readRecord(attempt.sourceTracking));
|
|
167
185
|
const staleRestart = formatStaleDiagnosticRestartsForRecord(attempt);
|
|
168
186
|
const processCrashRecovery = formatProcessCrashRecovery(
|
|
169
187
|
readProcessCrashRecovery(attempt.processCrashRecovery),
|
|
@@ -171,7 +189,7 @@ function formatLastRefreshSuffix(refresh: Record<string, unknown>): string {
|
|
|
171
189
|
const fileReadinessPending =
|
|
172
190
|
attempt.kind === "completed" && isFileReadinessPending(attempt, null);
|
|
173
191
|
const readiness = fileReadinessPending ? "LSP may still be warming; retry shortly" : null;
|
|
174
|
-
const outcome = [readiness, staleRestart, processCrashRecovery, evidence]
|
|
192
|
+
const outcome = [readiness, sourceTracking, staleRestart, processCrashRecovery, evidence]
|
|
175
193
|
.filter((value): value is string => value !== null)
|
|
176
194
|
.join("; ");
|
|
177
195
|
if (attempt.kind === "failed") {
|
|
@@ -185,6 +203,15 @@ function formatLastRefreshSuffix(refresh: Record<string, unknown>): string {
|
|
|
185
203
|
return "";
|
|
186
204
|
}
|
|
187
205
|
|
|
206
|
+
function formatSourceTracking(source: Record<string, unknown> | null): string | null {
|
|
207
|
+
const status = readSourceTrackingStatus(source);
|
|
208
|
+
return status ? `source discovery ${status}` : null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function readArrayLength(value: unknown): number {
|
|
212
|
+
return Array.isArray(value) ? value.length : 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
188
215
|
function formatCompactDiagnosticEvidence(evidence: Record<string, unknown> | null): string | null {
|
|
189
216
|
const counts = readEvidenceCounts(evidence);
|
|
190
217
|
if (!counts) return null;
|
|
@@ -207,20 +234,60 @@ export function isPendingFileReadiness(data: Record<string, unknown> | null): bo
|
|
|
207
234
|
return isFileReadinessPending(attempt, readSemanticHealthState(data?.semanticState));
|
|
208
235
|
}
|
|
209
236
|
|
|
210
|
-
function readProcessCrashRecovery(value: unknown):
|
|
211
|
-
const
|
|
212
|
-
if (!
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
const failedRoutes =
|
|
237
|
+
function readProcessCrashRecovery(value: unknown): ProcessCrashRecoveryReport | null {
|
|
238
|
+
const report = readRecord(value);
|
|
239
|
+
if (!report) return null;
|
|
240
|
+
const recoveredRoutes = report.recoveredRoutes;
|
|
241
|
+
const skippedRoutes = report.skippedRoutes;
|
|
242
|
+
const failedRoutes = report.failedRoutes;
|
|
243
|
+
const exhaustedRoutes = report.exhaustedRoutes;
|
|
244
|
+
const omittedEntries = report.omittedEntries;
|
|
216
245
|
if (
|
|
217
|
-
!isEvidenceCount(attemptedRoutes) ||
|
|
218
246
|
!isEvidenceCount(recoveredRoutes) ||
|
|
219
|
-
!isEvidenceCount(
|
|
247
|
+
!isEvidenceCount(skippedRoutes) ||
|
|
248
|
+
!isEvidenceCount(failedRoutes) ||
|
|
249
|
+
!isEvidenceCount(exhaustedRoutes) ||
|
|
250
|
+
!isEvidenceCount(omittedEntries) ||
|
|
251
|
+
!Array.isArray(report.entries) ||
|
|
252
|
+
report.entries.length > MAX_PROCESS_CRASH_RECOVERY_ENTRIES
|
|
220
253
|
) {
|
|
221
254
|
return null;
|
|
222
255
|
}
|
|
223
|
-
|
|
256
|
+
const entries = report.entries
|
|
257
|
+
.map(readProcessCrashRecoveryEntry)
|
|
258
|
+
.filter((entry): entry is ProcessCrashRecoveryEntry => entry !== null);
|
|
259
|
+
if (entries.length !== report.entries.length) return null;
|
|
260
|
+
return { recoveredRoutes, skippedRoutes, failedRoutes, exhaustedRoutes, entries, omittedEntries };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function readProcessCrashRecoveryEntry(value: unknown): ProcessCrashRecoveryEntry | null {
|
|
264
|
+
const entry = readRecord(value);
|
|
265
|
+
if (!entry || typeof entry.name !== "string" || typeof entry.root !== "string") return null;
|
|
266
|
+
if (!isProcessCrashRecoveryOutcome(entry.outcome)) return null;
|
|
267
|
+
if (
|
|
268
|
+
entry.nextAction !== undefined &&
|
|
269
|
+
entry.nextAction !== "use-exact-file" &&
|
|
270
|
+
entry.nextAction !== "reload-workspace"
|
|
271
|
+
) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
if (entry.failureMessage !== undefined && typeof entry.failureMessage !== "string") return null;
|
|
275
|
+
return {
|
|
276
|
+
name: entry.name,
|
|
277
|
+
root: entry.root,
|
|
278
|
+
outcome: entry.outcome,
|
|
279
|
+
...(entry.nextAction !== undefined ? { nextAction: entry.nextAction } : {}),
|
|
280
|
+
...(entry.failureMessage !== undefined ? { failureMessage: entry.failureMessage } : {}),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function isProcessCrashRecoveryOutcome(value: unknown): value is ProcessCrashRecoveryOutcome {
|
|
285
|
+
return (
|
|
286
|
+
value === "recovered" ||
|
|
287
|
+
value === "skipped-no-retained-file" ||
|
|
288
|
+
value === "recovery-failed" ||
|
|
289
|
+
value === "recovery-exhausted"
|
|
290
|
+
);
|
|
224
291
|
}
|
|
225
292
|
|
|
226
293
|
function readEvidenceCounts(evidence: Record<string, unknown> | null): number[] | null {
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
readCompactRefreshStatus,
|
|
30
30
|
readPreviousRefreshStatus,
|
|
31
31
|
readRefreshStatus,
|
|
32
|
+
readSourceTrackingStatus,
|
|
32
33
|
} from "./refresh-status.ts";
|
|
33
34
|
import { formatSemanticHealthState, readSemanticHealthState } from "./semantic-state.ts";
|
|
34
35
|
|
|
@@ -192,6 +193,8 @@ function buildStatusBar(data: Record<string, unknown> | null, theme: Theme): Tex
|
|
|
192
193
|
const semanticStatus = readSemanticStatus(data);
|
|
193
194
|
const structuralStatus = readString(data, "structuralStatus");
|
|
194
195
|
const refreshStatus = readRefreshStatus(data);
|
|
196
|
+
const refresh = readRecord(data?.refresh);
|
|
197
|
+
const sourceTracking = readSourceTrackingStatus(readRecord(refresh?.sourceTracking));
|
|
195
198
|
|
|
196
199
|
const lspColor = semanticStatus.startsWith("ready") ? "success" : "warning";
|
|
197
200
|
const structuralColor = structuralStatus === "ready" ? "success" : "muted";
|
|
@@ -204,6 +207,7 @@ function buildStatusBar(data: Record<string, unknown> | null, theme: Theme): Tex
|
|
|
204
207
|
lines.push(`Tree-sitter: ${theme.fg(structuralColor, structuralStatus)}`);
|
|
205
208
|
}
|
|
206
209
|
if (refreshStatus) lines.push(`Diagnostics: ${theme.fg("dim", refreshStatus)}`);
|
|
210
|
+
if (sourceTracking) lines.push(`Source discovery: ${theme.fg("dim", sourceTracking)}`);
|
|
207
211
|
|
|
208
212
|
return new Text(lines.join(" "), 0, 0);
|
|
209
213
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export const toolDescription =
|
|
2
|
-
"Inspect one
|
|
2
|
+
"Inspect one source location for syntax, its enclosing declaration, hover information, definitions, and nearby diagnostics. Use it for point-local facts, not broad code context.";
|
|
3
3
|
|
|
4
|
-
export const promptSnippet = "
|
|
4
|
+
export const promptSnippet = "inspect a source location";
|
|
5
5
|
|
|
6
|
-
export const promptGuidelines
|
|
6
|
+
export const promptGuidelines = [
|
|
7
|
+
"Use code_inspect for diagnostics near a source location; use code_health for broader diagnostics or language-server status.",
|
|
8
|
+
];
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
export const toolDescription =
|
|
2
|
-
"
|
|
2
|
+
"Return observed workspace or focused code context to choose what source to inspect next. Omit `focus` for workspace context. A directory focus can surface local instruction files.";
|
|
3
3
|
|
|
4
|
-
export const promptSnippet = "workspace
|
|
4
|
+
export const promptSnippet = "orient around workspace or code context";
|
|
5
5
|
|
|
6
6
|
export const promptGuidelines = [
|
|
7
|
-
"Use code_orientation before broad
|
|
8
|
-
"Use code_graph for relationships and code_health for provider or diagnostic state.",
|
|
7
|
+
"Use code_orientation before broad source reading when workspace or path context can narrow the files.",
|
|
9
8
|
];
|