@mrclrchtr/supi-code-intelligence 5.0.0 → 6.0.1

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.
Files changed (151) hide show
  1. package/README.md +4 -2
  2. package/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  3. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  4. package/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  5. package/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  6. package/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
  7. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -3
  8. package/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
  9. package/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
  10. package/node_modules/@mrclrchtr/supi-lsp/README.md +36 -3
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -3
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
  19. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  20. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +12 -1
  21. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +67 -28
  22. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +63 -6
  23. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +46 -5
  24. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +1 -0
  25. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +214 -0
  26. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +278 -79
  27. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +29 -8
  28. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +101 -19
  29. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +19 -1
  30. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +49 -3
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +73 -15
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +1 -1
  33. package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +2 -2
  34. package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +5 -5
  35. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +3 -0
  36. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-extends.ts +68 -0
  37. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-path.ts +8 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +176 -24
  39. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +4 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +46 -10
  41. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +4 -3
  42. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +65 -17
  43. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +143 -21
  44. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +2 -0
  45. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +43 -1
  46. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +31 -0
  47. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +2 -0
  48. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  49. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  50. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  51. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  52. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
  53. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -3
  54. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
  55. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +4 -4
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +1 -1
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +4 -4
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  62. package/package.json +5 -5
  63. package/src/analysis/health/diagnostics.ts +58 -126
  64. package/src/analysis/health/file-scope.ts +184 -0
  65. package/src/analysis/health/recovery.ts +8 -1
  66. package/src/analysis/readiness.ts +4 -0
  67. package/src/analysis/search/ast-scan.ts +4 -1
  68. package/src/analysis/search/pattern-analysis.ts +10 -3
  69. package/src/analysis/search/pattern.ts +6 -1
  70. package/src/config.ts +35 -1
  71. package/src/extension.ts +57 -14
  72. package/src/overview/overview-data.ts +9 -2
  73. package/src/overview/overview.ts +49 -30
  74. package/src/overview/types.ts +2 -0
  75. package/src/session/find-types.ts +2 -2
  76. package/src/session/find-workflow.ts +3 -2
  77. package/src/session/health-refresh.ts +76 -49
  78. package/src/session/health-types.ts +7 -1
  79. package/src/session/health-workflow.ts +35 -18
  80. package/src/session/input/workflows.ts +2 -2
  81. package/src/session/inspect-workflow.ts +2 -1
  82. package/src/session/orientation/collect.ts +2 -2
  83. package/src/session/orientation-types.ts +2 -0
  84. package/src/session/orientation-workflow.ts +8 -2
  85. package/src/session/refactor-workflow.ts +2 -1
  86. package/src/session/session.ts +25 -1
  87. package/src/substrate/lsp/maintenance.ts +105 -23
  88. package/src/substrate/lsp/recovery.ts +11 -5
  89. package/src/tool/code_find/execute.ts +23 -0
  90. package/src/tool/code_find/guidance.ts +8 -0
  91. package/src/tool/{find → code_find}/render.ts +1 -1
  92. package/src/tool/{result/find.ts → code_find/result.ts} +36 -11
  93. package/src/tool/code_find/spec.ts +37 -0
  94. package/src/tool/code_graph/execute.ts +24 -0
  95. package/src/tool/code_graph/guidance.ts +6 -0
  96. package/src/tool/{graph → code_graph}/markdown.ts +1 -1
  97. package/src/tool/{result/graph.ts → code_graph/result.ts} +69 -4
  98. package/src/tool/code_graph/spec.ts +39 -0
  99. package/src/tool/{health → code_health}/execute.ts +2 -18
  100. package/src/tool/code_health/guidance.ts +8 -0
  101. package/src/tool/{health → code_health}/markdown.ts +68 -18
  102. package/src/tool/{result/health.ts → code_health/result.ts} +29 -4
  103. package/src/tool/code_health/spec.ts +41 -0
  104. package/src/tool/code_inspect/execute.ts +20 -0
  105. package/src/tool/code_inspect/guidance.ts +6 -0
  106. package/src/tool/{inspect → code_inspect}/markdown.ts +1 -1
  107. package/src/tool/{result/inspect.ts → code_inspect/result.ts} +32 -3
  108. package/src/tool/code_inspect/spec.ts +25 -0
  109. package/src/tool/code_orientation/execute.ts +25 -0
  110. package/src/tool/code_orientation/guidance.ts +9 -0
  111. package/src/tool/{orientation → code_orientation}/markdown.ts +1 -1
  112. package/src/tool/{result/orientation.ts → code_orientation/result.ts} +67 -3
  113. package/src/tool/code_orientation/spec.ts +25 -0
  114. package/src/tool/code_refactor_apply/execute.ts +17 -0
  115. package/src/tool/code_refactor_apply/guidance.ts +5 -0
  116. package/src/tool/{refactor-apply/execute.ts → code_refactor_apply/result.ts} +5 -11
  117. package/src/tool/code_refactor_apply/spec.ts +26 -0
  118. package/src/tool/code_refactor_plan/execute.ts +27 -0
  119. package/src/tool/code_refactor_plan/guidance.ts +8 -0
  120. package/src/tool/{refactor-plan/execute.ts → code_refactor_plan/result.ts} +9 -22
  121. package/src/tool/code_refactor_plan/spec.ts +25 -0
  122. package/src/tool/code_resolve/execute.ts +19 -0
  123. package/src/tool/code_resolve/guidance.ts +8 -0
  124. package/src/tool/{resolve → code_resolve}/markdown.ts +1 -1
  125. package/src/tool/{result/resolve.ts → code_resolve/result.ts} +37 -24
  126. package/src/tool/code_resolve/spec.ts +25 -0
  127. package/src/tool/guidance.ts +71 -52
  128. package/src/tool/infra/truncate.ts +40 -0
  129. package/src/tool/{refactor-plan/markdown.ts → refactor-markdown.ts} +4 -7
  130. package/src/tool/register.ts +9 -97
  131. package/src/tool/result/refactor.ts +1 -4
  132. package/src/tool/schemas.ts +26 -157
  133. package/src/tool/specs.ts +40 -126
  134. package/src/tool/find/execute.ts +0 -43
  135. package/src/tool/graph/execute.ts +0 -82
  136. package/src/tool/inspect/execute.ts +0 -40
  137. package/src/tool/orientation/execute.ts +0 -80
  138. package/src/tool/resolve/execute.ts +0 -53
  139. /package/src/tool/{find → code_find}/ast-kinds.ts +0 -0
  140. /package/src/tool/{find → code_find}/markdown.ts +0 -0
  141. /package/src/tool/{find → code_find}/modes.ts +0 -0
  142. /package/src/tool/{find → code_find}/tui.ts +0 -0
  143. /package/src/tool/{graph → code_graph}/tui.ts +0 -0
  144. /package/src/tool/{health → code_health}/refresh-status.ts +0 -0
  145. /package/src/tool/{health → code_health}/semantic-state.ts +0 -0
  146. /package/src/tool/{health → code_health}/tui.ts +0 -0
  147. /package/src/tool/{inspect → code_inspect}/tui.ts +0 -0
  148. /package/src/tool/{orientation → code_orientation}/tui.ts +0 -0
  149. /package/src/tool/{refactor-apply → code_refactor_apply}/tui.ts +0 -0
  150. /package/src/tool/{refactor-plan → code_refactor_plan}/tui.ts +0 -0
  151. /package/src/tool/{resolve → code_resolve}/tui.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-tree-sitter",
3
- "version": "5.0.0",
3
+ "version": "6.0.1",
4
4
  "description": "Structural AST analysis for SuPi code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,8 +36,8 @@
36
36
  "dependencies": {
37
37
  "jiti": "^2.7.0",
38
38
  "web-tree-sitter": "^0.26.8",
39
- "@mrclrchtr/supi-code-runtime": "5.0.0",
40
- "@mrclrchtr/supi-core": "5.0.0"
39
+ "@mrclrchtr/supi-code-runtime": "6.0.1",
40
+ "@mrclrchtr/supi-core": "6.0.1"
41
41
  },
42
42
  "bundledDependencies": [
43
43
  "@mrclrchtr/supi-code-runtime",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-intelligence",
3
- "version": "5.0.0",
3
+ "version": "6.0.1",
4
4
  "description": "LSP and AST navigation, search, diagnostics, and refactoring",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,10 +35,10 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "yaml": "^2.9.0",
38
- "@mrclrchtr/supi-core": "5.0.0",
39
- "@mrclrchtr/supi-lsp": "5.0.0",
40
- "@mrclrchtr/supi-tree-sitter": "5.0.0",
41
- "@mrclrchtr/supi-code-runtime": "5.0.0"
38
+ "@mrclrchtr/supi-code-runtime": "6.0.1",
39
+ "@mrclrchtr/supi-tree-sitter": "6.0.1",
40
+ "@mrclrchtr/supi-lsp": "6.0.1",
41
+ "@mrclrchtr/supi-core": "6.0.1"
42
42
  },
43
43
  "bundledDependencies": [
44
44
  "@mrclrchtr/supi-code-runtime",
@@ -3,25 +3,28 @@
3
3
 
4
4
  import { existsSync, statSync } from "node:fs";
5
5
  import { relative, resolve } from "node:path";
6
- import {
7
- type CodeRequestControl,
8
- isCodeRequestInterruption,
9
- } from "@mrclrchtr/supi-code-runtime/api";
6
+ import type { CodeRequestControl } from "@mrclrchtr/supi-code-runtime/api";
10
7
  import { isWithinOrEqual } from "@mrclrchtr/supi-core/api";
11
8
  import type {
12
- Diagnostic,
13
9
  DiagnosticEvidenceDocument,
14
10
  DiagnosticEvidenceSummary,
11
+ WorkspaceDiagnosticReport,
15
12
  WorkspaceLspRuntime,
16
13
  } from "@mrclrchtr/supi-lsp/api";
17
14
  import { mergeDiagnosticEvidence } from "../../diagnostics/evidence.ts";
18
15
  import type {
19
16
  HealthDiagnosticEntry,
20
- HealthDiagnosticMessage,
21
17
  HealthDiagnosticObservation,
22
18
  HealthDiagnosticScope,
23
19
  HealthSection,
24
20
  } from "../../session/health-types.ts";
21
+ import {
22
+ collectScopedFileDiagnostics,
23
+ fileScopeStatus,
24
+ toFileDiagnosticEntries,
25
+ unavailableDiagnostics,
26
+ unavailableFileEvidence,
27
+ } from "./file-scope.ts";
25
28
 
26
29
  // ── Diagnostics ───────────────────────────────────────────────────────
27
30
 
@@ -47,8 +50,10 @@ interface CollectDiagnosticsOptions {
47
50
  /** Collect individual messages per file (detailed level). */
48
51
  readonly detailed?: boolean;
49
52
  readonly requestControl?: CodeRequestControl;
50
- /** Final evidence from an explicit workspace refresh, when one ran. */
53
+ /** Evidence from an explicit workspace refresh, when one ran. */
51
54
  readonly refreshEvidence?: DiagnosticEvidenceSummary;
55
+ /** Coherent final report from an explicit workspace refresh, when one ran. */
56
+ readonly refreshReport?: WorkspaceDiagnosticReport;
52
57
  }
53
58
 
54
59
  /** Collect diagnostics without widening the requested evidence scope. */
@@ -61,75 +66,49 @@ export async function collectDiagnostics(
61
66
  return unavailableDiagnostics(
62
67
  scope,
63
68
  unavailableReason,
64
- unavailableScopeEvidence(scope, options.refreshEvidence, options.evidenceService, cwd),
69
+ unavailableScopeEvidence({
70
+ scope,
71
+ refreshEvidence: options.refreshEvidence,
72
+ refreshReport: options.refreshReport,
73
+ evidenceService: options.evidenceService,
74
+ cwd,
75
+ }),
76
+ scope.kind === "file" ? fileScopeStatus(scope.path, cwd) : undefined,
65
77
  );
66
78
  }
67
79
 
68
80
  return scope.kind === "file"
69
- ? collectScopedFileDiagnostics(service, scope, detailed, requestControl)
81
+ ? collectScopedFileDiagnostics({ service, scope, cwd, detailed, requestControl })
70
82
  : collectTrackedFileDiagnostics({
71
83
  service,
72
84
  scope,
73
85
  cwd,
74
86
  detailed,
75
87
  refreshEvidence: options.refreshEvidence,
88
+ refreshReport: options.refreshReport,
76
89
  });
77
90
  }
78
91
 
79
- async function collectScopedFileDiagnostics(
80
- service: WorkspaceLspRuntime,
81
- scope: Extract<HealthDiagnosticScope, { kind: "file" }>,
82
- detailed?: boolean,
83
- requestControl?: CodeRequestControl,
84
- ): Promise<HealthDiagnosticObservation> {
85
- try {
86
- const result = await service.fileDiagnostics(scope.path, 4, requestControl);
87
- if (result.kind === "unavailable") {
88
- return unavailableDiagnostics(scope, result.reason, unavailableFileEvidence(scope.path));
89
- }
90
-
91
- const entries = toFileDiagnosticEntries(scope.path, result.data, detailed);
92
- return result.kind === "partial"
93
- ? {
94
- kind: "partial",
95
- scope,
96
- entries,
97
- evidence: singleFileEvidence(scope.path, "unconfirmed"),
98
- reason: result.reason,
99
- }
100
- : {
101
- kind: "completed",
102
- scope,
103
- entries,
104
- evidence: singleFileEvidence(scope.path, "confirmed"),
105
- };
106
- } catch (error) {
107
- if (isCodeRequestInterruption(error, requestControl)) throw error;
108
- return unavailableDiagnostics(
109
- scope,
110
- errorMessage(error, "File diagnostic request failed."),
111
- unavailableFileEvidence(scope.path),
112
- );
113
- }
114
- }
115
-
116
92
  interface TrackedFileDiagnosticOptions {
117
93
  readonly service: WorkspaceLspRuntime;
118
94
  readonly scope: Extract<HealthDiagnosticScope, { kind: "tracked-files" }>;
119
95
  readonly cwd: string;
120
96
  readonly detailed?: boolean;
121
97
  readonly refreshEvidence?: DiagnosticEvidenceSummary;
98
+ readonly refreshReport?: WorkspaceDiagnosticReport;
122
99
  }
123
100
 
124
101
  function collectTrackedFileDiagnostics(
125
102
  options: TrackedFileDiagnosticOptions,
126
103
  ): HealthDiagnosticObservation {
127
- const { service, scope, cwd, detailed, refreshEvidence } = options;
104
+ const { service, scope, cwd, detailed, refreshEvidence, refreshReport } = options;
128
105
  try {
129
106
  const snapshot = detailed
130
- ? collectWorkspaceDiagnosticsDetailed(service, scope.filter, cwd)
131
- : collectWorkspaceDiagnostics(service, scope.filter, cwd);
132
- const sourceEvidence = mergeEvidence(refreshEvidence, snapshot.evidence, cwd);
107
+ ? collectWorkspaceDiagnosticsDetailed(service, scope.filter, cwd, refreshReport)
108
+ : collectWorkspaceDiagnostics(service, scope.filter, cwd, refreshReport);
109
+ const sourceEvidence = refreshReport
110
+ ? snapshot.evidence
111
+ : mergeEvidence(refreshEvidence, snapshot.evidence, cwd);
133
112
  const scopedEvidence = selectTrackedFileEvidence(sourceEvidence, scope.filter, cwd);
134
113
  const evidence = ensureEvidenceCoversEntries(scopedEvidence, snapshot.entries, cwd);
135
114
  return evidenceIsComplete(evidence, snapshot.entries.length > 0, snapshot.current)
@@ -142,8 +121,9 @@ function collectTrackedFileDiagnostics(
142
121
  reason: diagnosticEvidenceReason(evidence),
143
122
  };
144
123
  } catch (error) {
145
- const evidence = options.refreshEvidence
146
- ? selectTrackedFileEvidence(options.refreshEvidence, scope.filter, cwd)
124
+ const evidenceSource = options.refreshReport?.summary.evidence ?? options.refreshEvidence;
125
+ const evidence = evidenceSource
126
+ ? selectTrackedFileEvidence(evidenceSource, scope.filter, cwd)
147
127
  : emptyEvidence();
148
128
  return unavailableDiagnostics(
149
129
  scope,
@@ -153,31 +133,17 @@ function collectTrackedFileDiagnostics(
153
133
  }
154
134
  }
155
135
 
156
- function toFileDiagnosticEntries(
157
- file: string,
158
- diagnostics: ReadonlyArray<Pick<Diagnostic, "severity" | "message" | "source" | "range">>,
159
- detailed?: boolean,
160
- ): HealthDiagnosticEntry[] {
161
- const errors = diagnostics.filter((diagnostic) => (diagnostic.severity ?? 1) === 1).length;
162
- const warnings = diagnostics.filter((diagnostic) => (diagnostic.severity ?? 1) === 2).length;
163
- if (!hasIssueCounts(errors, warnings)) return [];
164
- const entry: HealthDiagnosticEntry = { file, errors, warnings };
165
- if (detailed) {
166
- return [{ ...entry, messages: extractMessages(diagnostics) }];
167
- }
168
- return [entry];
169
- }
170
-
171
136
  function collectWorkspaceDiagnostics(
172
137
  service: WorkspaceLspRuntime,
173
138
  scopeFilter: string | null,
174
139
  cwd: string,
140
+ report?: WorkspaceDiagnosticReport,
175
141
  ): {
176
142
  entries: HealthDiagnosticEntry[];
177
143
  current: boolean;
178
144
  evidence: DiagnosticEvidenceSummary;
179
145
  } {
180
- const summary = service.getWorkspaceDiagnosticSummary();
146
+ const summary = report?.summary ?? service.getWorkspaceDiagnosticSummary();
181
147
  const result: HealthDiagnosticEntry[] = [];
182
148
 
183
149
  for (const entry of summary.entries) {
@@ -195,12 +161,13 @@ function collectWorkspaceDiagnosticsDetailed(
195
161
  service: WorkspaceLspRuntime,
196
162
  scopeFilter: string | null,
197
163
  cwd: string,
164
+ report?: WorkspaceDiagnosticReport,
198
165
  ): {
199
166
  entries: HealthDiagnosticEntry[];
200
167
  current: boolean;
201
168
  evidence: DiagnosticEvidenceSummary;
202
169
  } {
203
- const outstanding = service.getOutstandingDiagnostics(2);
170
+ const outstanding = report?.outstanding ?? service.getOutstandingDiagnostics(2);
204
171
  const result: HealthDiagnosticEntry[] = [];
205
172
 
206
173
  for (const { file, diagnostics } of outstanding.entries) {
@@ -213,37 +180,24 @@ function collectWorkspaceDiagnosticsDetailed(
213
180
  return { entries: result, current: outstanding.current, evidence: outstanding.evidence };
214
181
  }
215
182
 
216
- // ── Message extraction ────────────────────────────────────────────────
217
-
218
- const MAX_MESSAGES_PER_FILE = 3;
219
-
220
- function extractMessages(
221
- diagnostics: ReadonlyArray<Pick<Diagnostic, "severity" | "message" | "source" | "range">>,
222
- ): HealthDiagnosticMessage[] {
223
- const errorsAndWarnings = diagnostics
224
- .filter((d) => (d.severity ?? 1) <= 2)
225
- .sort(
226
- (a, b) => (a.severity ?? 1) - (b.severity ?? 1) || a.range.start.line - b.range.start.line,
227
- );
228
-
229
- return errorsAndWarnings.slice(0, MAX_MESSAGES_PER_FILE).map((d) => ({
230
- line: d.range.start.line + 1,
231
- severity: (d.severity ?? 1) === 1 ? ("error" as const) : ("warning" as const),
232
- message: typeof d.message === "string" ? d.message : d.message.value,
233
- ...(d.source ? { source: d.source } : {}),
234
- }));
183
+ interface UnavailableScopeEvidenceOptions {
184
+ readonly scope: HealthDiagnosticScope;
185
+ readonly refreshEvidence?: DiagnosticEvidenceSummary;
186
+ readonly refreshReport?: WorkspaceDiagnosticReport;
187
+ readonly evidenceService?: WorkspaceLspRuntime | null;
188
+ readonly cwd: string;
235
189
  }
236
190
 
237
191
  function unavailableScopeEvidence(
238
- scope: HealthDiagnosticScope,
239
- refreshEvidence: DiagnosticEvidenceSummary | undefined,
240
- evidenceService: WorkspaceLspRuntime | null | undefined,
241
- cwd: string,
192
+ options: UnavailableScopeEvidenceOptions,
242
193
  ): DiagnosticEvidenceSummary {
243
- const runtimeEvidence = readRuntimeEvidence(evidenceService);
244
- const knownEvidence = refreshEvidence
245
- ? mergeDiagnosticEvidence(refreshEvidence, runtimeEvidence, cwd, "conservative")
246
- : runtimeEvidence;
194
+ const { scope, refreshEvidence, refreshReport, evidenceService, cwd } = options;
195
+ const runtimeEvidence = refreshReport?.summary.evidence ?? readRuntimeEvidence(evidenceService);
196
+ const knownEvidence = refreshReport
197
+ ? runtimeEvidence
198
+ : refreshEvidence
199
+ ? mergeDiagnosticEvidence(refreshEvidence, runtimeEvidence, cwd, "conservative")
200
+ : runtimeEvidence;
247
201
  if (knownEvidence.requested === 0 && knownEvidence.documents.length === 0) {
248
202
  return scope.kind === "file" ? unavailableFileEvidence(scope.path) : emptyEvidence();
249
203
  }
@@ -256,10 +210,6 @@ function unavailableScopeEvidence(
256
210
  return scope.kind === "file" ? unavailableFileEvidence(scope.path) : scoped;
257
211
  }
258
212
 
259
- function unavailableFileEvidence(file: string): DiagnosticEvidenceSummary {
260
- return singleFileEvidence(file, existsSync(file) ? "failed" : "removed");
261
- }
262
-
263
213
  function readRuntimeEvidence(
264
214
  service: WorkspaceLspRuntime | null | undefined,
265
215
  ): DiagnosticEvidenceSummary {
@@ -271,14 +221,6 @@ function readRuntimeEvidence(
271
221
  }
272
222
  }
273
223
 
274
- function unavailableDiagnostics(
275
- scope: HealthDiagnosticScope,
276
- reason: string,
277
- evidence: DiagnosticEvidenceSummary = emptyEvidence(),
278
- ): HealthDiagnosticObservation {
279
- return { kind: "unavailable", scope, entries: [], evidence, reason };
280
- }
281
-
282
224
  function errorMessage(error: unknown, fallback: string): string {
283
225
  return error instanceof Error && error.message ? error.message : fallback;
284
226
  }
@@ -298,20 +240,6 @@ function emptyEvidence(): DiagnosticEvidenceSummary {
298
240
  };
299
241
  }
300
242
 
301
- function singleFileEvidence(
302
- file: string,
303
- status: DiagnosticEvidenceDocument["status"],
304
- ): DiagnosticEvidenceSummary {
305
- return {
306
- requested: 1,
307
- confirmed: status === "confirmed" ? 1 : 0,
308
- unconfirmed: status === "unconfirmed" ? 1 : 0,
309
- failed: status === "failed" ? 1 : 0,
310
- removed: status === "removed" ? 1 : 0,
311
- documents: [{ file, status }],
312
- };
313
- }
314
-
315
243
  function mergeEvidence(
316
244
  refreshEvidence: DiagnosticEvidenceSummary | undefined,
317
245
  snapshotEvidence: DiagnosticEvidenceSummary,
@@ -375,15 +303,19 @@ function evidenceIsComplete(
375
303
  if (!snapshotCurrent && evidence.requested === 0) return false;
376
304
  if (evidence.requested === 0) return !hasDiagnosticEntries;
377
305
  return (
378
- evidence.confirmed === evidence.requested &&
306
+ evidence.confirmed + evidence.removed === evidence.requested &&
379
307
  evidence.unconfirmed === 0 &&
380
- evidence.failed === 0 &&
381
- evidence.removed === 0
308
+ evidence.failed === 0
382
309
  );
383
310
  }
384
311
 
385
312
  function diagnosticEvidenceReason(evidence: DiagnosticEvidenceSummary): string {
386
- return `Diagnostic evidence is partial: ${evidence.requested} requested, ${evidence.confirmed} confirmed, ${evidence.unconfirmed} unconfirmed, ${evidence.failed} failed, ${evidence.removed} removed.`;
313
+ let reason = `Diagnostic evidence is partial: ${evidence.requested} requested, ${evidence.confirmed} confirmed, ${evidence.unconfirmed} unconfirmed, ${evidence.failed} failed, ${evidence.removed} removed.`;
314
+ if (evidence.unconfirmed > 0) {
315
+ reason +=
316
+ " Unconfirmed documents await a later diagnostic republish before their evidence can be confirmed (ADR 0021).";
317
+ }
318
+ return reason;
387
319
  }
388
320
 
389
321
  export function isDirectory(filePath: string): boolean {
@@ -0,0 +1,184 @@
1
+ // Single-file diagnostics collection for code_health.
2
+ //
3
+ // Owns the file-scope health request: the live pull, the tsconfig scope
4
+ // decision for the requested file, and the evidence factories shared by the
5
+ // file-scope outcomes. Kept separate from the workspace-tracked collection
6
+ // (diagnostics.ts) so the scope-decision surface stays in one place.
7
+
8
+ import { existsSync } from "node:fs";
9
+ import {
10
+ type CodeRequestControl,
11
+ isCodeRequestInterruption,
12
+ } from "@mrclrchtr/supi-code-runtime/api";
13
+ import {
14
+ type Diagnostic,
15
+ type DiagnosticEvidenceDocument,
16
+ type DiagnosticEvidenceSummary,
17
+ type FileScopeDecision,
18
+ getFileScopeDecision,
19
+ TENTATIVE_PUSH_UNAVAILABLE_REASON,
20
+ type WorkspaceLspRuntime,
21
+ } from "@mrclrchtr/supi-lsp/api";
22
+ import type {
23
+ HealthDiagnosticEntry,
24
+ HealthDiagnosticMessage,
25
+ HealthDiagnosticObservation,
26
+ HealthDiagnosticScope,
27
+ } from "../../session/health-types.ts";
28
+
29
+ /** Collect a live per-file diagnostic pull with its tsconfig scope decision. */
30
+ export async function collectScopedFileDiagnostics(options: {
31
+ service: WorkspaceLspRuntime;
32
+ scope: Extract<HealthDiagnosticScope, { kind: "file" }>;
33
+ cwd: string;
34
+ detailed?: boolean;
35
+ requestControl?: CodeRequestControl;
36
+ }): Promise<HealthDiagnosticObservation> {
37
+ const { service, scope, cwd, detailed, requestControl } = options;
38
+ try {
39
+ const result = await service.fileDiagnostics(scope.path, 4, requestControl);
40
+ if (result.kind === "unavailable") {
41
+ const evidence =
42
+ result.reason === TENTATIVE_PUSH_UNAVAILABLE_REASON
43
+ ? singleFileEvidence(scope.path, "unconfirmed")
44
+ : unavailableFileEvidence(scope.path);
45
+ return unavailableDiagnostics(
46
+ scope,
47
+ result.reason,
48
+ evidence,
49
+ fileScopeStatus(scope.path, cwd),
50
+ );
51
+ }
52
+
53
+ const entries = toFileDiagnosticEntries(scope.path, result.data, detailed);
54
+ const status = fileScopeStatus(scope.path, cwd);
55
+ return result.kind === "partial"
56
+ ? {
57
+ kind: "partial",
58
+ scope,
59
+ entries,
60
+ evidence: singleFileEvidence(scope.path, "unconfirmed"),
61
+ reason: result.reason,
62
+ ...(status ? { scopeStatus: status } : {}),
63
+ }
64
+ : {
65
+ kind: "completed",
66
+ scope,
67
+ entries,
68
+ evidence: singleFileEvidence(scope.path, "confirmed"),
69
+ ...(status ? { scopeStatus: status } : {}),
70
+ };
71
+ } catch (error) {
72
+ if (isCodeRequestInterruption(error, requestControl)) throw error;
73
+ return unavailableDiagnostics(
74
+ scope,
75
+ errorMessage(error, "File diagnostic request failed."),
76
+ unavailableFileEvidence(scope.path),
77
+ fileScopeStatus(scope.path, cwd),
78
+ );
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Tsconfig scope decision for the requested file, when it can be computed.
84
+ *
85
+ * Uses the same decision model the diagnostic filter delegates to, so the
86
+ * rendered status line cannot drift from filter behavior. Telemetry-only:
87
+ * a failure to compute never fails the diagnostic request.
88
+ */
89
+ export function fileScopeStatus(filePath: string, cwd: string): FileScopeDecision | null {
90
+ try {
91
+ return getFileScopeDecision(filePath, cwd);
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+
97
+ export function toFileDiagnosticEntries(
98
+ file: string,
99
+ diagnostics: ReadonlyArray<Pick<Diagnostic, "severity" | "message" | "source" | "range">>,
100
+ detailed?: boolean,
101
+ ): HealthDiagnosticEntry[] {
102
+ const errors = diagnostics.filter((diagnostic) => (diagnostic.severity ?? 1) === 1).length;
103
+ const warnings = diagnostics.filter((diagnostic) => (diagnostic.severity ?? 1) === 2).length;
104
+ if (!hasIssueCounts(errors, warnings)) return [];
105
+ const entry: HealthDiagnosticEntry = { file, errors, warnings };
106
+ if (detailed) {
107
+ return [{ ...entry, messages: extractMessages(diagnostics) }];
108
+ }
109
+ return [entry];
110
+ }
111
+
112
+ // ── Message extraction ────────────────────────────────────────────────
113
+
114
+ const MAX_MESSAGES_PER_FILE = 3;
115
+
116
+ function extractMessages(
117
+ diagnostics: ReadonlyArray<Pick<Diagnostic, "severity" | "message" | "source" | "range">>,
118
+ ): HealthDiagnosticMessage[] {
119
+ const errorsAndWarnings = diagnostics
120
+ .filter((d) => (d.severity ?? 1) <= 2)
121
+ .sort(
122
+ (a, b) => (a.severity ?? 1) - (b.severity ?? 1) || a.range.start.line - b.range.start.line,
123
+ );
124
+
125
+ return errorsAndWarnings.slice(0, MAX_MESSAGES_PER_FILE).map((d) => ({
126
+ line: d.range.start.line + 1,
127
+ severity: (d.severity ?? 1) === 1 ? ("error" as const) : ("warning" as const),
128
+ message: typeof d.message === "string" ? d.message : d.message.value,
129
+ ...(d.source ? { source: d.source } : {}),
130
+ }));
131
+ }
132
+
133
+ export function unavailableDiagnostics(
134
+ scope: HealthDiagnosticScope,
135
+ reason: string,
136
+ evidence: DiagnosticEvidenceSummary = emptyEvidence(),
137
+ scopeStatus?: FileScopeDecision | null,
138
+ ): HealthDiagnosticObservation {
139
+ return {
140
+ kind: "unavailable",
141
+ scope,
142
+ entries: [],
143
+ evidence,
144
+ reason,
145
+ ...(scopeStatus ? { scopeStatus } : {}),
146
+ };
147
+ }
148
+
149
+ export function unavailableFileEvidence(file: string): DiagnosticEvidenceSummary {
150
+ return singleFileEvidence(file, existsSync(file) ? "failed" : "removed");
151
+ }
152
+
153
+ export function singleFileEvidence(
154
+ file: string,
155
+ status: DiagnosticEvidenceDocument["status"],
156
+ ): DiagnosticEvidenceSummary {
157
+ return {
158
+ requested: 1,
159
+ confirmed: status === "confirmed" ? 1 : 0,
160
+ unconfirmed: status === "unconfirmed" ? 1 : 0,
161
+ failed: status === "failed" ? 1 : 0,
162
+ removed: status === "removed" ? 1 : 0,
163
+ documents: [{ file, status }],
164
+ };
165
+ }
166
+
167
+ function emptyEvidence(): DiagnosticEvidenceSummary {
168
+ return {
169
+ requested: 0,
170
+ confirmed: 0,
171
+ unconfirmed: 0,
172
+ failed: 0,
173
+ removed: 0,
174
+ documents: [],
175
+ };
176
+ }
177
+
178
+ function hasIssueCounts(errors: number, warnings: number): boolean {
179
+ return errors > 0 || warnings > 0;
180
+ }
181
+
182
+ function errorMessage(error: unknown, fallback: string): string {
183
+ return error instanceof Error && error.message ? error.message : fallback;
184
+ }
@@ -2,7 +2,11 @@
2
2
  // Extracted from orchestrate.ts.
3
3
 
4
4
  import type { CapabilityState, CodeRequestControl } from "@mrclrchtr/supi-code-runtime/api";
5
- import type { RecoverDiagnosticsResult, WorkspaceLspRuntime } from "@mrclrchtr/supi-lsp/api";
5
+ import type {
6
+ DiagnosticEvidenceSummary,
7
+ RecoverDiagnosticsResult,
8
+ WorkspaceLspRuntime,
9
+ } from "@mrclrchtr/supi-lsp/api";
6
10
 
7
11
  // ── Recovery ──────────────────────────────────────────────────────────
8
12
 
@@ -10,6 +14,8 @@ interface RecoverOptions {
10
14
  service: WorkspaceLspRuntime;
11
15
  progress?: () => void;
12
16
  control?: CodeRequestControl;
17
+ /** Evidence from the maintenance refresh, so the recovery pass skips its own refresh. */
18
+ initialEvidence?: DiagnosticEvidenceSummary;
13
19
  }
14
20
 
15
21
  /** Run the runtime's best-effort recovery and preserve its established outcome. */
@@ -19,6 +25,7 @@ export async function recoverDiagnosticRuntime(
19
25
  opts.progress?.();
20
26
  return opts.service.recoverDiagnostics({
21
27
  restartIfStillStale: true,
28
+ ...(opts.initialEvidence !== undefined ? { initialEvidence: opts.initialEvidence } : {}),
22
29
  control: opts.control,
23
30
  });
24
31
  }
@@ -12,6 +12,10 @@ import {
12
12
 
13
13
  export const DEFAULT_SEMANTIC_STARTUP_TIMEOUT_MS = 15_000;
14
14
 
15
+ /** Neutral reason for a typed semantic readiness timeout. */
16
+ export const SEMANTIC_READINESS_TIMEOUT_REASON =
17
+ "Semantic provider did not become ready within the wait window; retry shortly.";
18
+
15
19
  export type SemanticStartupScope = { kind: "workspace" } | { kind: "file"; file: string };
16
20
 
17
21
  export async function ensureSemanticReadiness(
@@ -5,7 +5,7 @@ import {
5
5
  getSupportedExtensions,
6
6
  type StructuralSearchOperation,
7
7
  } from "@mrclrchtr/supi-tree-sitter/api";
8
- import { type DeadlineOutcome, settleByDeadline } from "./deadline.ts";
8
+ import { type DeadlineOutcome, type ScheduleDeadline, settleByDeadline } from "./deadline.ts";
9
9
  import { relativeDisplayPath } from "./paths.ts";
10
10
 
11
11
  /** Directory names excluded below an AST Scan root. */
@@ -83,6 +83,8 @@ export interface EnumerateAstFilesOptions {
83
83
  readonly timeoutMs?: number;
84
84
  readonly signal?: AbortSignal;
85
85
  readonly now?: () => number;
86
+ /** Timer seam for deterministic deadline tests; defaults to wall-clock timers. */
87
+ readonly schedule?: ScheduleDeadline;
86
88
  readonly operations?: AstScanOperations;
87
89
  }
88
90
 
@@ -302,6 +304,7 @@ class AstFileEnumerator {
302
304
  deadline: this.options.deadline,
303
305
  now: this.#now,
304
306
  signal: this.options.signal,
307
+ schedule: this.options.schedule,
305
308
  });
306
309
  if (outcome.kind === "timeout") this.#markTimeout();
307
310
  return outcome;
@@ -6,10 +6,10 @@ import {
6
6
  type OutlineData,
7
7
  type StructuralProvider as StructuralSubstrate,
8
8
  } from "@mrclrchtr/supi-code-runtime/api";
9
- import type { CodeFindAstKind } from "../../tool/find/ast-kinds.ts";
9
+ import type { CodeFindAstKind } from "../../tool/code_find/ast-kinds.ts";
10
10
  import type { AstScanLimitation } from "./ast-scan.ts";
11
11
  import { callableExpressionForMatching } from "./call-name.ts";
12
- import { type DeadlineOutcome, settleByDeadline } from "./deadline.ts";
12
+ import { type DeadlineOutcome, type ScheduleDeadline, settleByDeadline } from "./deadline.ts";
13
13
  import { relativeDisplayPath } from "./paths.ts";
14
14
 
15
15
  export interface StructuredPatternParams {
@@ -55,6 +55,8 @@ interface AnalyzeStructuredFilesOptions {
55
55
  readonly deadline: number;
56
56
  readonly now: () => number;
57
57
  readonly signal?: AbortSignal;
58
+ /** Timer seam for deterministic deadline tests; defaults to wall-clock timers. */
59
+ readonly schedule?: ScheduleDeadline;
58
60
  readonly requestControl: CodeRequestControl;
59
61
  readonly initialLimitations: readonly AstScanLimitation[];
60
62
  }
@@ -101,7 +103,12 @@ export async function analyzeStructuredFiles(
101
103
  });
102
104
  }
103
105
  },
104
- { deadline: options.deadline, now: options.now, signal: options.signal },
106
+ {
107
+ deadline: options.deadline,
108
+ now: options.now,
109
+ signal: options.signal,
110
+ schedule: options.schedule,
111
+ },
105
112
  );
106
113
  } catch (error) {
107
114
  if (!isCodeRequestDeadlineError(error)) throw error;