@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ignore",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.8",
|
|
4
4
|
"description": "Ignore is a manager and filter for .gitignore rules, the one used by eslint, gitbook and many others.",
|
|
5
5
|
"types": "index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -25,9 +25,15 @@
|
|
|
25
25
|
"test:mjs:16": "ts-node --compilerOptions '{\"moduleResolution\": \"Node16\", \"module\": \"Node16\"}' ./test/import/simple.mjs && babel -o ./test/import/simple-mjs.js ./test/import/simple.mjs && node ./test/import/simple-mjs.js",
|
|
26
26
|
|
|
27
27
|
"===================== cases =======================": "",
|
|
28
|
-
"test:cases": "npm run tap test/*.test.js -- --coverage",
|
|
28
|
+
"test:cases": "npm run tap test/*.test.js -- --coverage --100",
|
|
29
29
|
"tap": "tap --reporter classic",
|
|
30
30
|
|
|
31
|
+
"=================== benchmark =====================": "",
|
|
32
|
+
"benchmark": "node benchmark",
|
|
33
|
+
|
|
34
|
+
"==================== compat =======================": "",
|
|
35
|
+
"compat": "node compat && node compat --win32",
|
|
36
|
+
|
|
31
37
|
"===================== debug =======================": "",
|
|
32
38
|
"test:git": "npm run tap test/git-check-ignore.test.js",
|
|
33
39
|
"test:ignore": "npm run tap test/ignore.test.js",
|
|
@@ -35,13 +41,13 @@
|
|
|
35
41
|
"test:others": "npm run tap test/others.test.js",
|
|
36
42
|
"test:no-coverage": "npm run tap test/*.test.js -- --no-check-coverage",
|
|
37
43
|
|
|
38
|
-
"test": "npm run lint && npm run ts && npm run build && npm run test:cases",
|
|
44
|
+
"test": "npm run lint && npm run ts && npm run build && npm run test:cases && npm run compat",
|
|
39
45
|
"test:win32": "IGNORE_TEST_WIN32=1 npm run test",
|
|
40
46
|
"report": "tap --coverage-report=html"
|
|
41
47
|
},
|
|
42
48
|
"repository": {
|
|
43
49
|
"type": "git",
|
|
44
|
-
"url": "
|
|
50
|
+
"url": "https://github.com/kaelzhang/node-ignore.git"
|
|
45
51
|
},
|
|
46
52
|
"keywords": [
|
|
47
53
|
"ignore",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-lsp",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"description": "Language Server Protocol runtime for SuPi code intelligence",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"vscode-jsonrpc": "^9.0.0",
|
|
38
38
|
"vscode-languageserver-protocol": "^3.18.0",
|
|
39
39
|
"vscode-languageserver-types": "^3.18.0",
|
|
40
|
-
"@mrclrchtr/supi-code-runtime": "6.
|
|
41
|
-
"@mrclrchtr/supi-core": "6.
|
|
40
|
+
"@mrclrchtr/supi-code-runtime": "6.4.0",
|
|
41
|
+
"@mrclrchtr/supi-core": "6.4.0"
|
|
42
42
|
},
|
|
43
43
|
"bundledDependencies": [
|
|
44
44
|
"@mrclrchtr/supi-code-runtime",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Public API surface for the LSP session-scoped service.
|
|
2
2
|
|
|
3
|
+
export { isMissingFileError } from "./client/client-file-state.ts";
|
|
3
4
|
export { type LoadConfigOptions, loadConfig } from "./config/config.ts";
|
|
4
5
|
export type { LspSettings } from "./config/lsp-settings.ts";
|
|
5
6
|
export { loadLspSettings } from "./config/lsp-settings.ts";
|
|
@@ -41,6 +42,12 @@ export {
|
|
|
41
42
|
type WorkspaceSentinelScanOptions,
|
|
42
43
|
type WorkspaceSentinelSyncResult,
|
|
43
44
|
} from "./diagnostics/workspace-sentinels.ts";
|
|
45
|
+
export {
|
|
46
|
+
scanWorkspaceSources,
|
|
47
|
+
type WorkspaceSourceInventory,
|
|
48
|
+
type WorkspaceSourceInventoryReason,
|
|
49
|
+
type WorkspaceSourceScanOptions,
|
|
50
|
+
} from "./diagnostics/workspace-sources.ts";
|
|
44
51
|
export { raceReadinessValue, raceRequestControl } from "./session/readiness.ts";
|
|
45
52
|
export type {
|
|
46
53
|
LspControllerState,
|
|
@@ -51,12 +58,17 @@ export type {
|
|
|
51
58
|
} from "./session/runtime-controller.ts";
|
|
52
59
|
export { LspRuntimeController } from "./session/runtime-controller.ts";
|
|
53
60
|
export type {
|
|
61
|
+
BulkTrackFileOutcome,
|
|
62
|
+
BulkTrackFilesResult,
|
|
54
63
|
DiagnosticEvidenceDocument,
|
|
55
64
|
DiagnosticEvidenceStatus,
|
|
56
65
|
DiagnosticEvidenceSummary,
|
|
57
66
|
OutstandingDiagnosticSummaryEntry,
|
|
58
67
|
ProcessCrashDiagnosticDemand,
|
|
59
|
-
|
|
68
|
+
ProcessCrashRecoveryEntry,
|
|
69
|
+
ProcessCrashRecoveryNextAction,
|
|
70
|
+
ProcessCrashRecoveryOutcome,
|
|
71
|
+
ProcessCrashRecoveryReport,
|
|
60
72
|
RecoverDiagnosticsResult,
|
|
61
73
|
RoutedMutationResponse,
|
|
62
74
|
SemanticReadinessResult,
|
|
@@ -69,12 +81,14 @@ export type {
|
|
|
69
81
|
} from "./session/runtime-registry.ts";
|
|
70
82
|
export {
|
|
71
83
|
clearWorkspaceLspRuntime,
|
|
72
|
-
|
|
84
|
+
emptyProcessCrashRecoveryReport,
|
|
73
85
|
getWorkspaceLspRuntime,
|
|
86
|
+
MAX_PROCESS_CRASH_RECOVERY_ENTRIES,
|
|
74
87
|
setWorkspaceLspRuntimeState,
|
|
75
88
|
waitForWorkspaceLspRuntime,
|
|
76
89
|
} from "./session/runtime-registry.ts";
|
|
77
90
|
export { scanMissingServers } from "./session/scanner.ts";
|
|
91
|
+
export { MAX_BULK_TRACK_FILES } from "./session/workspace-lsp-runtime.ts";
|
|
78
92
|
export {
|
|
79
93
|
AUTOMATIC_LSP_EXCLUDED_DIRECTORIES,
|
|
80
94
|
type AutomaticLspPathPolicy,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { fileToUri
|
|
3
|
+
import { fileToUri } from "@mrclrchtr/supi-core/path";
|
|
4
4
|
import { FileChangeType, type FileEvent } from "../config/types.ts";
|
|
5
5
|
import {
|
|
6
6
|
type AutomaticLspPathPolicy,
|
|
@@ -11,12 +11,6 @@ import {
|
|
|
11
11
|
const ROOT_LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"];
|
|
12
12
|
|
|
13
13
|
export interface WorkspaceSentinelScanOptions {
|
|
14
|
-
/**
|
|
15
|
-
* Also track every regular source file (not just sentinels). The workspace
|
|
16
|
-
* refresh path uses this to discover files created since the last refresh;
|
|
17
|
-
* the diff then reports them as `sourceChanges` so the caller can pull them.
|
|
18
|
-
*/
|
|
19
|
-
includeSourceFiles?: boolean;
|
|
20
14
|
/** Runtime-owned automatic path policy. */
|
|
21
15
|
policy?: AutomaticLspPathPolicy;
|
|
22
16
|
}
|
|
@@ -36,7 +30,7 @@ export function scanWorkspaceSentinels(
|
|
|
36
30
|
for (const entry of entries) {
|
|
37
31
|
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
38
32
|
const filePath = path.join(directory, entry.name);
|
|
39
|
-
if (!
|
|
33
|
+
if (!isWorkspaceSentinelPath(filePath, resolvedCwd)) continue;
|
|
40
34
|
try {
|
|
41
35
|
snapshot.set(filePath, fs.statSync(filePath).mtimeMs);
|
|
42
36
|
} catch {
|
|
@@ -78,38 +72,16 @@ export function diffWorkspaceSentinelSnapshot(
|
|
|
78
72
|
export interface WorkspaceSentinelSyncResult {
|
|
79
73
|
snapshot: Map<string, number>;
|
|
80
74
|
changes: FileEvent[];
|
|
81
|
-
sourceChanges: FileEvent[];
|
|
82
75
|
}
|
|
83
76
|
|
|
84
|
-
/**
|
|
85
|
-
* Refresh a previous snapshot and return the new snapshot plus change events.
|
|
86
|
-
*
|
|
87
|
-
* When `includeSourceFiles` is set, the returned `changes` list keeps
|
|
88
|
-
* sentinel-file events only; source-file events are returned separately in
|
|
89
|
-
* `sourceChanges` so the caller can act on them without interpreting every
|
|
90
|
-
* regular file edit as a config-level workspace change.
|
|
91
|
-
*/
|
|
77
|
+
/** Refresh a previous sentinel snapshot and return its change events. */
|
|
92
78
|
export function syncWorkspaceSentinelSnapshot(
|
|
93
79
|
cwd: string,
|
|
94
80
|
previous: Map<string, number>,
|
|
95
81
|
options: WorkspaceSentinelScanOptions = {},
|
|
96
82
|
): WorkspaceSentinelSyncResult {
|
|
97
83
|
const snapshot = scanWorkspaceSentinels(cwd, options);
|
|
98
|
-
|
|
99
|
-
if (!options.includeSourceFiles) return { snapshot, changes, sourceChanges: [] };
|
|
100
|
-
|
|
101
|
-
const sourceChanges = changes.filter((change) => {
|
|
102
|
-
const filePath = uriToFile(change.uri);
|
|
103
|
-
return !isWorkspaceSentinelPath(filePath, cwd);
|
|
104
|
-
});
|
|
105
|
-
return {
|
|
106
|
-
snapshot,
|
|
107
|
-
changes: changes.filter((change) => {
|
|
108
|
-
const filePath = uriToFile(change.uri);
|
|
109
|
-
return isWorkspaceSentinelPath(filePath, cwd);
|
|
110
|
-
}),
|
|
111
|
-
sourceChanges,
|
|
112
|
-
};
|
|
84
|
+
return { snapshot, changes: diffWorkspaceSentinelSnapshot(previous, snapshot) };
|
|
113
85
|
}
|
|
114
86
|
|
|
115
87
|
function isWorkspaceSentinelPath(filePath: string, root: string): boolean {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { type Dirent, promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
type CodeRequestControl,
|
|
5
|
+
isCodeRequestInterruption,
|
|
6
|
+
throwIfCodeRequestInterrupted,
|
|
7
|
+
} from "@mrclrchtr/supi-code-runtime/api";
|
|
8
|
+
import { raceRequestControl } from "../session/readiness.ts";
|
|
9
|
+
import type { AutomaticLspPathPolicy } from "../workspace-path-policy.ts";
|
|
10
|
+
|
|
11
|
+
const MAX_SOURCE_INVENTORY_FILES = 50_000;
|
|
12
|
+
|
|
13
|
+
export type WorkspaceSourceInventoryReason = "file-limit" | "filesystem-error";
|
|
14
|
+
|
|
15
|
+
/** Complete or safety-limited automatic source inventory. */
|
|
16
|
+
export interface WorkspaceSourceInventory {
|
|
17
|
+
readonly status: "complete" | "limited";
|
|
18
|
+
/** The reason is null only when the inventory is complete. */
|
|
19
|
+
readonly reason: WorkspaceSourceInventoryReason | null;
|
|
20
|
+
/** Number of configured-extension files observed before the scan stopped. */
|
|
21
|
+
readonly observedFileCount: number;
|
|
22
|
+
/** Paths are present only as a usable complete inventory. */
|
|
23
|
+
readonly files: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Inputs for one automatic source inventory. */
|
|
27
|
+
export interface WorkspaceSourceScanOptions {
|
|
28
|
+
readonly fileTypes: readonly string[];
|
|
29
|
+
readonly policy: AutomaticLspPathPolicy;
|
|
30
|
+
readonly control?: CodeRequestControl;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Scan one workspace for automatic source paths.
|
|
35
|
+
*
|
|
36
|
+
* The scan uses the runtime path policy and configured LSP extensions. A
|
|
37
|
+
* limited result is not a source baseline and must not be diffed. Caller
|
|
38
|
+
* interruption is rethrown so a health call does not retain partial state.
|
|
39
|
+
*/
|
|
40
|
+
export async function scanWorkspaceSources(
|
|
41
|
+
cwd: string,
|
|
42
|
+
options: WorkspaceSourceScanOptions,
|
|
43
|
+
): Promise<WorkspaceSourceInventory> {
|
|
44
|
+
const extensionSet = new Set(
|
|
45
|
+
options.fileTypes.map((fileType) => fileType.replace(/^\./, "").toLowerCase()),
|
|
46
|
+
);
|
|
47
|
+
const files: string[] = [];
|
|
48
|
+
let observedFileCount = 0;
|
|
49
|
+
let reason: WorkspaceSourceInventoryReason | null = null;
|
|
50
|
+
|
|
51
|
+
const readEntries = async (directory: string): Promise<Dirent[] | null> => {
|
|
52
|
+
try {
|
|
53
|
+
return await raceRequestControl(
|
|
54
|
+
fs.readdir(directory, { withFileTypes: true }) as Promise<Dirent[]>,
|
|
55
|
+
options.control,
|
|
56
|
+
);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (isCodeRequestInterruption(error, options.control)) throw error;
|
|
59
|
+
reason = "filesystem-error";
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const visitEntry = async (directory: string, entry: Dirent): Promise<void> => {
|
|
65
|
+
const filePath = path.join(directory, entry.name);
|
|
66
|
+
const kind = entry.isDirectory() ? "directory" : "file";
|
|
67
|
+
if (!options.policy.isEligible(filePath, kind)) return;
|
|
68
|
+
|
|
69
|
+
if (entry.isDirectory()) {
|
|
70
|
+
await walk(filePath);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!entry.isFile() && !entry.isSymbolicLink()) return;
|
|
74
|
+
if (!extensionSet.has(path.extname(entry.name).slice(1).toLowerCase())) return;
|
|
75
|
+
|
|
76
|
+
observedFileCount++;
|
|
77
|
+
if (observedFileCount > MAX_SOURCE_INVENTORY_FILES) {
|
|
78
|
+
reason = "file-limit";
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
files.push(filePath);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const walk = async (directory: string): Promise<void> => {
|
|
85
|
+
if (reason !== null) return;
|
|
86
|
+
throwIfCodeRequestInterrupted(options.control);
|
|
87
|
+
if (!options.policy.isEligible(directory, "directory")) return;
|
|
88
|
+
|
|
89
|
+
const entries = await readEntries(directory);
|
|
90
|
+
if (!entries) return;
|
|
91
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
throwIfCodeRequestInterrupted(options.control);
|
|
94
|
+
await visitEntry(directory, entry);
|
|
95
|
+
if (reason !== null) return;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
await walk(path.resolve(cwd));
|
|
100
|
+
throwIfCodeRequestInterrupted(options.control);
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
status: reason === null ? "complete" : "limited",
|
|
104
|
+
reason,
|
|
105
|
+
observedFileCount,
|
|
106
|
+
files: reason === null ? files.sort((a, b) => a.localeCompare(b)) : [],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
MAX_PROCESS_CRASH_RECOVERY_ENTRIES,
|
|
4
|
+
type ProcessCrashRecoveryEntry,
|
|
5
|
+
type ProcessCrashRecoveryNextAction,
|
|
6
|
+
type ProcessCrashRecoveryOutcome,
|
|
7
|
+
type ProcessCrashRecoveryReport,
|
|
8
|
+
} from "../session/runtime-diagnostics.ts";
|
|
9
|
+
|
|
10
|
+
/** One route result before its root is projected to a workspace-relative path. */
|
|
11
|
+
export interface ProcessCrashRecoveryRouteResult {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly root: string;
|
|
14
|
+
readonly outcome: ProcessCrashRecoveryOutcome;
|
|
15
|
+
/** The caught Error.message from a failed replacement, when available. */
|
|
16
|
+
readonly failureMessage?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Maximum failure detail retained in a process-crash report. */
|
|
20
|
+
export const MAX_PROCESS_CRASH_FAILURE_MESSAGE_LENGTH = 512;
|
|
21
|
+
|
|
22
|
+
const OUTCOME_ORDER: Record<ProcessCrashRecoveryOutcome, number> = {
|
|
23
|
+
"recovery-failed": 0,
|
|
24
|
+
"recovery-exhausted": 1,
|
|
25
|
+
"skipped-no-retained-file": 2,
|
|
26
|
+
recovered: 3,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Build the bounded, deterministic report for one process-crash demand. */
|
|
30
|
+
export function buildProcessCrashRecoveryReport(
|
|
31
|
+
routes: readonly ProcessCrashRecoveryRouteResult[],
|
|
32
|
+
cwd: string,
|
|
33
|
+
): ProcessCrashRecoveryReport {
|
|
34
|
+
const entries = routes.map((route) => toReportEntry(route, cwd)).sort(compareEntries);
|
|
35
|
+
const visibleEntries = entries.slice(0, MAX_PROCESS_CRASH_RECOVERY_ENTRIES);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
recoveredRoutes: countRoutes(routes, "recovered"),
|
|
39
|
+
skippedRoutes: countRoutes(routes, "skipped-no-retained-file"),
|
|
40
|
+
failedRoutes: countRoutes(routes, "recovery-failed"),
|
|
41
|
+
exhaustedRoutes: countRoutes(routes, "recovery-exhausted"),
|
|
42
|
+
entries: visibleEntries,
|
|
43
|
+
omittedEntries: Math.max(0, entries.length - visibleEntries.length),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Limit one caught Error.message without retaining other error fields. */
|
|
48
|
+
export function boundProcessCrashFailureMessage(message: string): string {
|
|
49
|
+
if (message.length <= MAX_PROCESS_CRASH_FAILURE_MESSAGE_LENGTH) return message;
|
|
50
|
+
return `${message.slice(0, MAX_PROCESS_CRASH_FAILURE_MESSAGE_LENGTH - 1)}…`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toReportEntry(
|
|
54
|
+
route: ProcessCrashRecoveryRouteResult,
|
|
55
|
+
cwd: string,
|
|
56
|
+
): ProcessCrashRecoveryEntry {
|
|
57
|
+
const action = nextAction(route.outcome);
|
|
58
|
+
const entry: ProcessCrashRecoveryEntry = {
|
|
59
|
+
name: route.name,
|
|
60
|
+
root: relativeRoot(cwd, route.root),
|
|
61
|
+
outcome: route.outcome,
|
|
62
|
+
...(action ? { nextAction: action } : {}),
|
|
63
|
+
...(route.outcome === "recovery-failed" && route.failureMessage
|
|
64
|
+
? { failureMessage: boundProcessCrashFailureMessage(route.failureMessage) }
|
|
65
|
+
: {}),
|
|
66
|
+
};
|
|
67
|
+
return entry;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function relativeRoot(cwd: string, root: string): string {
|
|
71
|
+
return (path.relative(path.resolve(cwd), path.resolve(cwd, root)) || ".").replaceAll("\\", "/");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function nextAction(outcome: ProcessCrashRecoveryOutcome): ProcessCrashRecoveryNextAction | null {
|
|
75
|
+
switch (outcome) {
|
|
76
|
+
case "skipped-no-retained-file":
|
|
77
|
+
return "use-exact-file";
|
|
78
|
+
case "recovery-failed":
|
|
79
|
+
case "recovery-exhausted":
|
|
80
|
+
return "reload-workspace";
|
|
81
|
+
case "recovered":
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function compareEntries(
|
|
87
|
+
first: ProcessCrashRecoveryEntry,
|
|
88
|
+
second: ProcessCrashRecoveryEntry,
|
|
89
|
+
): number {
|
|
90
|
+
return (
|
|
91
|
+
OUTCOME_ORDER[first.outcome] - OUTCOME_ORDER[second.outcome] ||
|
|
92
|
+
compareStrings(first.root, second.root) ||
|
|
93
|
+
compareStrings(first.name, second.name)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function compareStrings(first: string, second: string): number {
|
|
98
|
+
return first < second ? -1 : first > second ? 1 : 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function countRoutes(
|
|
102
|
+
routes: readonly ProcessCrashRecoveryRouteResult[],
|
|
103
|
+
outcome: ProcessCrashRecoveryOutcome,
|
|
104
|
+
): number {
|
|
105
|
+
return routes.filter((route) => route.outcome === outcome).length;
|
|
106
|
+
}
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
type StaleDiagnosticAssessment,
|
|
18
18
|
} from "../diagnostics/stale-diagnostics.ts";
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
type
|
|
20
|
+
emptyProcessCrashRecoveryReport,
|
|
21
|
+
type ProcessCrashRecoveryReport,
|
|
22
22
|
type WorkspaceDiagnosticReport,
|
|
23
23
|
} from "../session/runtime-diagnostics.ts";
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ export interface WorkspaceRecoveryResult {
|
|
|
28
28
|
/** Clients restarted by stale-diagnostic recovery, not process-crash recovery. */
|
|
29
29
|
restartedClients: number;
|
|
30
30
|
/** No process-crash demand is handled by this generic recovery pass. */
|
|
31
|
-
processCrashRecovery:
|
|
31
|
+
processCrashRecovery: ProcessCrashRecoveryReport;
|
|
32
32
|
/** Server names of the active clients targeted by this pass, for telemetry identity. */
|
|
33
33
|
attemptedServers: string[];
|
|
34
34
|
/** Server names of the clients restarted during this pass, for telemetry identity. */
|
|
@@ -164,7 +164,7 @@ export async function recoverWorkspaceDiagnostics(
|
|
|
164
164
|
return {
|
|
165
165
|
attemptedClients,
|
|
166
166
|
restartedClients,
|
|
167
|
-
processCrashRecovery:
|
|
167
|
+
processCrashRecovery: emptyProcessCrashRecoveryReport(),
|
|
168
168
|
attemptedServers,
|
|
169
169
|
restartedServers: restartServerNames,
|
|
170
170
|
...(restartReason ? { restartReason } : {}),
|