@mrclrchtr/supi-code-intelligence 6.4.0 → 7.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 (129) hide show
  1. package/README.md +164 -82
  2. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  4. package/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  5. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  6. package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  7. package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  8. package/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  9. package/node_modules/@mrclrchtr/supi-lsp/README.md +35 -13
  10. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +3 -1
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +3 -1
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +1 -1
  20. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  21. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +6 -0
  22. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +43 -35
  23. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +83 -17
  24. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +108 -92
  25. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +44 -82
  26. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +3 -4
  27. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +9 -68
  28. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +45 -58
  29. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +230 -331
  30. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +201 -1
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +129 -70
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-typescript.ts +395 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +8 -12
  34. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +685 -183
  35. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +2 -2
  36. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +60 -60
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-request-enrollment.ts +75 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-barrier.ts +443 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-errors.ts +30 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-file.ts +15 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +290 -37
  42. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +72 -16
  43. package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +1 -0
  44. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +30 -4
  45. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +1 -1
  46. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +10 -4
  47. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +50 -1
  48. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +11 -7
  49. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +608 -45
  50. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +3 -1
  51. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +37 -4
  52. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +31 -11
  53. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +19 -3
  54. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  55. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  62. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +2 -0
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callee-display-name.ts +165 -0
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +67 -9
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +2 -0
  66. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
  67. package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
  68. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  69. package/node_modules/yaml/dist/nodes/Alias.js +25 -23
  70. package/node_modules/yaml/package.json +1 -1
  71. package/package.json +5 -5
  72. package/src/analysis/health/diagnostics.ts +1 -2
  73. package/src/analysis/health/file-scope.ts +4 -1
  74. package/src/analysis/health/server-status.ts +1 -1
  75. package/src/analysis/relations/callees.ts +17 -8
  76. package/src/analysis/relations/implementations.ts +1 -2
  77. package/src/analysis/relations/references.ts +8 -4
  78. package/src/analysis/relations/types.ts +6 -30
  79. package/src/analysis/target/anchored.ts +9 -1
  80. package/src/analysis/target/symbol.ts +29 -11
  81. package/src/analysis/target/types.ts +14 -6
  82. package/src/api.ts +2 -0
  83. package/src/session/graph/collect.ts +18 -3
  84. package/src/session/health-refresh.ts +12 -1
  85. package/src/session/health-types.ts +18 -6
  86. package/src/session/health-workflow.ts +21 -19
  87. package/src/session/orientation-types.ts +5 -6
  88. package/src/session/orientation-workflow.ts +10 -2
  89. package/src/session/target-workflow.ts +9 -6
  90. package/src/substrate/lsp/source-tracking.ts +11 -6
  91. package/src/tool/code_find/guidance.ts +2 -2
  92. package/src/tool/code_find/spec.ts +5 -4
  93. package/src/tool/code_find/tui.ts +7 -1
  94. package/src/tool/code_graph/details.ts +32 -0
  95. package/src/tool/code_graph/display.ts +54 -0
  96. package/src/tool/code_graph/format.ts +60 -0
  97. package/src/tool/code_graph/guidance.ts +1 -1
  98. package/src/tool/code_graph/markdown.ts +76 -198
  99. package/src/tool/code_graph/read-next.ts +65 -0
  100. package/src/tool/code_graph/result.ts +118 -83
  101. package/src/tool/code_graph/tui.ts +216 -54
  102. package/src/tool/code_health/guidance.ts +2 -4
  103. package/src/tool/code_health/markdown.ts +75 -58
  104. package/src/tool/code_health/refresh-outcome.ts +41 -1
  105. package/src/tool/code_health/refresh-status.ts +114 -48
  106. package/src/tool/code_health/scope.ts +78 -0
  107. package/src/tool/code_health/spec.ts +1 -1
  108. package/src/tool/code_health/tui.ts +25 -38
  109. package/src/tool/code_inspect/tui.ts +7 -1
  110. package/src/tool/code_orientation/result.ts +31 -8
  111. package/src/tool/code_orientation/tui.ts +26 -5
  112. package/src/tool/code_refactor_apply/guidance.ts +1 -1
  113. package/src/tool/code_refactor_apply/tui.ts +7 -3
  114. package/src/tool/code_refactor_plan/guidance.ts +1 -1
  115. package/src/tool/code_refactor_plan/tui.ts +7 -1
  116. package/src/tool/code_resolve/guidance.ts +2 -4
  117. package/src/tool/code_resolve/result.ts +13 -10
  118. package/src/tool/code_resolve/tui.ts +18 -1
  119. package/src/tool/infra/truncate.ts +39 -13
  120. package/src/tool/result/candidate-row.ts +27 -0
  121. package/src/tool/result/errors.ts +21 -6
  122. package/src/tool/result/types.ts +7 -3
  123. package/src/tool/schemas.ts +5 -0
  124. package/src/types/execution.ts +2 -0
  125. package/src/types/index.ts +2 -0
  126. package/src/ui/tui/candidate-legacy-body.ts +69 -0
  127. package/src/ui/tui/candidate-selection.ts +366 -0
  128. package/src/ui/tui/common.ts +26 -31
  129. package/src/ui/tui/execution-error.ts +92 -0
@@ -14,6 +14,8 @@ import type {
14
14
  */
15
15
  export interface ProcessCrashDiagnosticDemand {
16
16
  readonly scopes?: readonly string[];
17
+ /** Allow one bounded retry for failed routes during explicit health refresh. */
18
+ readonly explicit?: boolean;
17
19
  }
18
20
 
19
21
  /** Diagnostic and recovery operations owned by a workspace LSP runtime. */
@@ -45,7 +47,7 @@ export interface WorkspaceLspDiagnosticSurface {
45
47
  quietMs?: number;
46
48
  /** Evidence from a refresh the caller already completed; skips this pass's own refresh when no watched-file changes apply. */
47
49
  initialEvidence?: DiagnosticEvidenceSummary;
48
- /** Explicit demand to recover crashed routes with tracked files in scope. */
50
+ /** Explicit demand to retry crashed and failed routes in scope. */
49
51
  processCrashDemand?: ProcessCrashDiagnosticDemand;
50
52
  control?: CodeRequestControl;
51
53
  }): Promise<RecoverDiagnosticsResult>;
@@ -52,8 +52,8 @@ export type ProcessCrashRecoveryOutcome =
52
52
  | "recovery-failed"
53
53
  | "recovery-exhausted";
54
54
 
55
- /** Typed next action for a non-recovered process-crash route. */
56
- export type ProcessCrashRecoveryNextAction = "use-exact-file" | "reload-workspace";
55
+ /** Typed next action for a route that needs another explicit health refresh. */
56
+ export type ProcessCrashRecoveryNextAction = "use-exact-file" | "refresh";
57
57
 
58
58
  /** One bounded route entry in a process-crash recovery report. */
59
59
  export interface ProcessCrashRecoveryEntry {
@@ -84,6 +84,29 @@ export interface ProcessCrashRecoveryReport {
84
84
  readonly omittedEntries: number;
85
85
  }
86
86
 
87
+ /** Stable outcome for one initial-start route handled by explicit refresh. */
88
+ export type StartupRetryOutcome = "recovered" | "retry-failed";
89
+
90
+ /** Typed next action for a failed initial-start retry. */
91
+ export type StartupRetryNextAction = "refresh";
92
+
93
+ /** One bounded route entry in an initial-start retry report. */
94
+ export interface StartupRetryEntry {
95
+ readonly name: string;
96
+ readonly root: string;
97
+ readonly outcome: StartupRetryOutcome;
98
+ readonly nextAction?: StartupRetryNextAction;
99
+ readonly failureMessage?: string;
100
+ }
101
+
102
+ /** Bounded result of explicit retries for initial-start routes. */
103
+ export interface StartupRetryReport {
104
+ readonly recoveredRoutes: number;
105
+ readonly failedRoutes: number;
106
+ readonly entries: readonly StartupRetryEntry[];
107
+ readonly omittedEntries: number;
108
+ }
109
+
87
110
  /** Create the empty outcome for a pass with no process-crash demand. */
88
111
  export function emptyProcessCrashRecoveryReport(): ProcessCrashRecoveryReport {
89
112
  return {
@@ -96,6 +119,11 @@ export function emptyProcessCrashRecoveryReport(): ProcessCrashRecoveryReport {
96
119
  };
97
120
  }
98
121
 
122
+ /** Create the empty outcome for a pass with no initial-start retry. */
123
+ export function emptyStartupRetryReport(): StartupRetryReport {
124
+ return { recoveredRoutes: 0, failedRoutes: 0, entries: [], omittedEntries: 0 };
125
+ }
126
+
99
127
  /** Result from a workspace diagnostic recovery pass. */
100
128
  export interface RecoverDiagnosticsResult {
101
129
  /** Active clients targeted by the best-effort refresh, not confirmed successful refreshes. */
@@ -104,6 +132,8 @@ export interface RecoverDiagnosticsResult {
104
132
  restartedClients: number;
105
133
  /** Separate outcome for process-crash route recovery selected by this pass. */
106
134
  processCrashRecovery: ProcessCrashRecoveryReport;
135
+ /** Separate outcome for explicit retries of failed initial-start routes. */
136
+ startupRetry: StartupRetryReport;
107
137
  /** Evidence collected by the refresh and recovery operations. */
108
138
  diagnosticEvidence: DiagnosticEvidenceSummary;
109
139
  /** Final diagnostic report captured after all refresh and recovery work. */
@@ -128,7 +158,10 @@ export interface ScopeDecisionEntry {
128
158
  basis: string | null;
129
159
  }
130
160
 
131
- /** Aggregate tsconfig scope decisions for all tracked files, for debug telemetry. */
161
+ /**
162
+ * Aggregate tsconfig scope decisions for automatic-scope TypeScript/JavaScript-family
163
+ * tracked files, for debug telemetry.
164
+ */
132
165
  export interface ScopeDecisionSummary {
133
166
  caseSensitiveFileNames: boolean;
134
167
  counts: {
@@ -141,6 +174,6 @@ export interface ScopeDecisionSummary {
141
174
  basisCounts: Record<string, number>;
142
175
  /** Bounded workspace-relative entries (oldest first), not the full set. */
143
176
  entries: ScopeDecisionEntry[];
144
- /** Total number of tracked files the summary was computed over. */
177
+ /** Total number of automatic-scope TypeScript/JavaScript-family tracked files covered. */
145
178
  totalFiles: number;
146
179
  }
@@ -41,6 +41,7 @@ import type {
41
41
  DiagnosticEvidenceSummary,
42
42
  ProcessCrashRecoveryReport,
43
43
  RecoverDiagnosticsResult,
44
+ StartupRetryReport,
44
45
  } from "./runtime-diagnostics.ts";
45
46
  import type {
46
47
  BulkTrackFilesResult,
@@ -59,6 +60,7 @@ export {
59
60
  type DiagnosticEvidenceStatus,
60
61
  type DiagnosticEvidenceSummary,
61
62
  emptyProcessCrashRecoveryReport,
63
+ emptyStartupRetryReport,
62
64
  MAX_PROCESS_CRASH_RECOVERY_ENTRIES,
63
65
  type OutstandingDiagnosticSummaryEntry,
64
66
  type ProcessCrashRecoveryEntry,
@@ -66,6 +68,10 @@ export {
66
68
  type ProcessCrashRecoveryOutcome,
67
69
  type ProcessCrashRecoveryReport,
68
70
  type RecoverDiagnosticsResult,
71
+ type StartupRetryEntry,
72
+ type StartupRetryNextAction,
73
+ type StartupRetryOutcome,
74
+ type StartupRetryReport,
69
75
  type WorkspaceDiagnosticReport,
70
76
  type WorkspaceDiagnosticSnapshot,
71
77
  type WorkspaceDiagnosticSummaryEntry,
@@ -270,11 +276,11 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
270
276
  */
271
277
  async waitUntilReadyForFile(
272
278
  filePath: string,
273
- options: { timeoutMs?: number } = {},
279
+ options: { timeoutMs?: number; retryFailedRoute?: boolean } = {},
274
280
  control?: CodeRequestControl,
275
281
  ): Promise<SemanticReadinessResult> {
276
282
  const resolvedPath = this.resolveFilePath(filePath);
277
- if (!this.manager.canServeFile(resolvedPath)) {
283
+ if (!options.retryFailedRoute && !this.manager.canServeFile(resolvedPath)) {
278
284
  const processCrashRecovery = this.manager.getProcessCrashRecoveryReportForFile(resolvedPath);
279
285
  return {
280
286
  kind: "unavailable",
@@ -284,31 +290,44 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
284
290
  }
285
291
 
286
292
  let eagerProcessCrashRecovery: ProcessCrashRecoveryReport | undefined;
293
+ let eagerStartupRetry: StartupRetryReport | undefined;
287
294
  const readiness = await raceReadinessValue(
288
- this.manager.waitUntilFileReady(resolvedPath, control, (report) => {
289
- eagerProcessCrashRecovery = report;
295
+ this.manager.waitUntilFileReady(resolvedPath, control, {
296
+ retryFailedRoute: options.retryFailedRoute === true,
297
+ onProcessCrashRecovery: (report) => {
298
+ eagerProcessCrashRecovery = report;
299
+ },
300
+ onStartupRetry: (report) => {
301
+ eagerStartupRetry = report;
302
+ },
290
303
  }),
291
304
  options.timeoutMs,
292
305
  control,
293
306
  );
294
307
  if (readiness.kind !== "resolved") {
295
- // A timeout can happen before shared recovery settles. Do not create a
296
- // route outcome for work that has no final result yet.
308
+ // A timeout can happen before shared recovery settles. Keep only route
309
+ // outcomes that completed before the timeout.
297
310
  const currentProcessCrashRecovery =
298
311
  this.manager.getProcessCrashRecoveryReportForFile(resolvedPath);
299
312
  const processCrashRecovery = currentProcessCrashRecovery ?? eagerProcessCrashRecovery;
300
- return processCrashRecovery ? { ...readiness, processCrashRecovery } : readiness;
313
+ return {
314
+ ...readiness,
315
+ ...(eagerStartupRetry ? { startupRetry: eagerStartupRetry } : {}),
316
+ ...(processCrashRecovery ? { processCrashRecovery } : {}),
317
+ };
301
318
  }
302
- const { client, processCrashRecovery } = readiness.value;
319
+ const { client, processCrashRecovery, startupRetry } = readiness.value;
303
320
  const recovery = hasProcessCrashRecovery(processCrashRecovery) ? { processCrashRecovery } : {};
321
+ const startup = startupRetry && startupRetry.entries.length > 0 ? { startupRetry } : {};
304
322
  if (!client) {
305
323
  return {
306
324
  kind: "unavailable",
307
325
  reason: "The routed LSP client could not be started for this file",
326
+ ...startup,
308
327
  ...recovery,
309
328
  };
310
329
  }
311
- return { kind: "ready", ...recovery };
330
+ return { kind: "ready", ...startup, ...recovery };
312
331
  }
313
332
 
314
333
  /**
@@ -433,14 +452,14 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
433
452
  return this.manager.getOutstandingDiagnosticSummarySnapshot(maxSeverity);
434
453
  }
435
454
 
436
- /** Trigger a workspace-wide diagnostics refresh and stale-state recovery pass. */
455
+ /** Retry failed routes and run a workspace-wide diagnostic recovery pass. */
437
456
  async recoverDiagnostics(options?: {
438
457
  restartIfStillStale?: boolean;
439
458
  maxWaitMs?: number;
440
459
  quietMs?: number;
441
460
  /** Evidence from a refresh the caller already completed; skips this pass's own refresh when no watched-file changes apply. */
442
461
  initialEvidence?: DiagnosticEvidenceSummary;
443
- /** Explicit demand to recover crashed routes with tracked files in scope. */
462
+ /** Explicit demand to retry failed startup and crashed routes in scope. */
444
463
  processCrashDemand?: import("./runtime-diagnostic-surface.ts").ProcessCrashDiagnosticDemand;
445
464
  control?: CodeRequestControl;
446
465
  }): Promise<RecoverDiagnosticsResult> {
@@ -460,6 +479,7 @@ class DefaultWorkspaceLspRuntime implements WorkspaceLspRuntime {
460
479
  attemptedClients: result.attemptedClients,
461
480
  restartedClients: result.restartedClients,
462
481
  processCrashRecovery: result.processCrashRecovery,
482
+ startupRetry: result.startupRetry,
463
483
  attemptedServers: boundServerNames(result.attemptedServers ?? []),
464
484
  restartedServers: boundServerNames(result.restartedServers ?? []),
465
485
  ...(result.restartReason ? { reason: result.restartReason } : {}),
@@ -19,7 +19,7 @@ import type {
19
19
  } from "../diagnostics/workspace-sentinels.ts";
20
20
  import type { WorkspaceSourceInventory } from "../diagnostics/workspace-sources.ts";
21
21
  import type { WorkspaceLspDiagnosticSurface } from "./runtime-diagnostic-surface.ts";
22
- import type { ProcessCrashRecoveryReport } from "./runtime-diagnostics.ts";
22
+ import type { ProcessCrashRecoveryReport, StartupRetryReport } from "./runtime-diagnostics.ts";
23
23
 
24
24
  /** Maximum number of files processed by one automatic bulk-tracking call. */
25
25
  export const MAX_BULK_TRACK_FILES = 256;
@@ -34,17 +34,23 @@ export type WorkspaceLspRuntimeState =
34
34
  export type SemanticReadinessResult =
35
35
  | {
36
36
  kind: "ready";
37
+ /** Initial-start retry observed while establishing file readiness. */
38
+ startupRetry?: StartupRetryReport;
37
39
  /** Process-crash route recovery observed while establishing file readiness. */
38
40
  processCrashRecovery?: ProcessCrashRecoveryReport;
39
41
  }
40
42
  | {
41
43
  kind: "timeout";
44
+ /** Initial-start retry observed before the readiness timeout. */
45
+ startupRetry?: StartupRetryReport;
42
46
  /** Process-crash route recovery observed before the readiness timeout. */
43
47
  processCrashRecovery?: ProcessCrashRecoveryReport;
44
48
  }
45
49
  | {
46
50
  kind: "unavailable";
47
51
  reason: string;
52
+ /** Initial-start retry observed before readiness became unavailable. */
53
+ startupRetry?: StartupRetryReport;
48
54
  /** Process-crash route recovery observed before readiness became unavailable. */
49
55
  processCrashRecovery?: ProcessCrashRecoveryReport;
50
56
  };
@@ -61,9 +67,15 @@ export interface RoutedMutationResponse<T> {
61
67
  export type BulkTrackFileOutcome =
62
68
  | { readonly file: string; readonly kind: "tracked" }
63
69
  | { readonly file: string; readonly kind: "already-tracked" }
70
+ /**
71
+ * Automatic tracking skipped this candidate. This includes policy-excluded
72
+ * paths, unsupported automatic routes, missing paths, and paths that are no
73
+ * longer regular. The coarse reason does not prove one cause or that an
74
+ * explicit request cannot be served.
75
+ */
64
76
  | {
65
77
  readonly file: string;
66
- readonly kind: "unsupported";
78
+ readonly kind: "skipped";
67
79
  readonly reason: "missing" | "not-automatic-source";
68
80
  }
69
81
  | { readonly file: string; readonly kind: "unavailable"; readonly reason: string };
@@ -124,9 +136,13 @@ export interface WorkspaceLspRuntime extends WorkspaceLspDiagnosticSurface {
124
136
  control?: CodeRequestControl,
125
137
  ): Promise<RoutedMutationResponse<CodeAction[] | null> | null>;
126
138
  getOpenDocumentVersion(filePath: string): number | null;
139
+ /**
140
+ * Wait for file readiness; an explicit refresh may make one shared route
141
+ * retry for this call without changing ordinary request recovery limits.
142
+ */
127
143
  waitUntilReadyForFile(
128
144
  filePath: string,
129
- options?: { timeoutMs?: number },
145
+ options?: { timeoutMs?: number; retryFailedRoute?: boolean },
130
146
  control?: CodeRequestControl,
131
147
  ): Promise<SemanticReadinessResult>;
132
148
  waitUntilReadyForWorkspace(
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "6.4.0",
3
+ "version": "7.0.1",
4
4
  "description": "Shared workspace context and capability contracts for code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -234,7 +234,14 @@ export type CalleeDepth = "direct" | "deep";
234
234
  */
235
235
  export interface CalleesData {
236
236
  enclosingScope: { name: string; startLine: number; endLine: number };
237
- callees: Array<{ name: string; startLine: number }>;
237
+ /** Distinct call sites with 1-based UTF-16 start coordinates. */
238
+ callees: Array<{
239
+ name: string;
240
+ /** Optional syntax-shortened label for presentation; does not replace `name`. */
241
+ displayName?: string;
242
+ startLine: number;
243
+ startCharacter: number;
244
+ }>;
238
245
  depth: CalleeDepth;
239
246
  }
240
247
 
@@ -21,6 +21,7 @@ pnpm add @mrclrchtr/supi-core
21
21
  ## Package surfaces
22
22
 
23
23
  - `@mrclrchtr/supi-core/api` — reusable helpers for other packages and extensions
24
+ - `@mrclrchtr/supi-core/llm` — PI-owned direct model requests and JSON helpers
24
25
  - `@mrclrchtr/supi-core/report` — shared text/report rendering helpers for TUI and plain-text summaries
25
26
 
26
27
  ## What you get from the API
@@ -48,6 +49,13 @@ Config file locations:
48
49
 
49
50
  - `wrapExtensionContext()` — wrap injected text in SuPi's `<extension-context>` tag
50
51
 
52
+ ### Model requests
53
+
54
+ - `completeModelRequest(ctx, model, context, options)` — complete through PI's model registry with stable feature affinity. PI owns auth and endpoint resolution.
55
+ - `callWithJsonResponse()` — retry a registry request, extract JSON, and validate it with TypeBox.
56
+
57
+ `completeModelRequest()` requires a stable `affinityScope`. It keeps cache retention defaults, does not include prompt content in the affinity ID, and adds OpenCode headers only when the provider or exact model endpoint matches OpenCode. Pass `maxTokens: model.maxTokens` when a caller needs the model's declared output cap without using PI private modules.
58
+
51
59
  ### Shared registries
52
60
 
53
61
  - context-provider registry for `/supi-context`
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "6.4.0",
3
+ "version": "7.0.1",
4
4
  "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -2,17 +2,24 @@
2
2
  //
3
3
  // Extensions register pre-styled text chunks with a placement hint
4
4
  // ("stats" for the metrics line, "status" for the extension status line).
5
- // The custom footer in supi-extras (or PI's built-in footer) reads these
6
- // contributions and renders them alongside the built-in metrics.
5
+ // The custom footer in supi-extras reads these contributions and renders them
6
+ // alongside the built-in metrics. Extensions can use PI's status API as a
7
+ // fallback when the custom footer is not installed.
7
8
 
8
9
  import { createRegistry } from "./registry-utils.ts";
9
10
 
11
+ /** Event emitted when a dynamic footer contribution needs a new render. */
12
+ export const FOOTER_INVALIDATE_EVENT = "supi:footer:invalidate";
13
+
10
14
  /** Where the contribution should appear in the footer. */
11
15
  export type FooterPlacement = "stats" | "stats-end" | "status";
12
16
 
13
17
  /** A single footer contribution registered by an extension. */
14
18
  export interface FooterContribution {
15
- /** Unique key for this contribution. Re-registering with the same key replaces it. */
19
+ /**
20
+ * Unique key for this contribution. Re-registering with the same key replaces it.
21
+ * A same-key Pi status is treated as this contribution's built-in-footer fallback.
22
+ */
16
23
  key: string;
17
24
  /** Which footer line this belongs on. */
18
25
  placement: FooterPlacement;
@@ -15,6 +15,8 @@ export * from "./debug.ts";
15
15
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
16
16
  export * from "./footer-registry.ts";
17
17
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
18
+ export * from "./llm.ts";
19
+ // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
18
20
  export * from "./model-selection.ts";
19
21
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
20
22
  export * from "./path.ts";
@@ -1,12 +1,131 @@
1
- import { complete } from "@earendil-works/pi-ai/compat";
1
+ import { createHash } from "node:crypto";
2
+ import type {
3
+ Api,
4
+ AssistantMessage,
5
+ Context,
6
+ Model,
7
+ ModelsApiStreamOptions,
8
+ ProviderHeaders,
9
+ } from "@earendil-works/pi-ai";
2
10
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
11
  import type { TSchema } from "typebox";
4
12
  import { Value } from "typebox/value";
5
13
 
6
14
  // Shared LLM utilities for SuPi extensions.
7
15
  //
8
- // Provides retry logic, structured LLM call helpers, and other
9
- // common patterns for extensions that interact with AI models.
16
+ // Provides PI-owned model requests, retry logic, structured LLM call helpers,
17
+ // and other common patterns for extensions that interact with AI models.
18
+
19
+ const MODEL_REQUEST_NAMESPACE = "supi-direct-model-request-v1";
20
+
21
+ /**
22
+ * Options for {@link completeModelRequest}.
23
+ *
24
+ * Authentication, provider environment, and session identity stay under PI
25
+ * control. The feature supplies a stable scope for its prompt stream.
26
+ */
27
+ export type CompleteModelRequestOptions<TApi extends Api = Api> = Omit<
28
+ ModelsApiStreamOptions<TApi>,
29
+ "apiKey" | "env" | "sessionId"
30
+ > & {
31
+ /** Stable feature scope. Do not include prompt, turn, or retry data. */
32
+ affinityScope: string;
33
+ /** PI owns these fields, including for APIs with open-ended option types. */
34
+ apiKey?: never;
35
+ env?: never;
36
+ sessionId?: never;
37
+ };
38
+
39
+ function createModelRequestAffinityId(
40
+ sessionId: string,
41
+ affinityScope: string,
42
+ model: Model<Api>,
43
+ ): string {
44
+ const material = JSON.stringify([
45
+ MODEL_REQUEST_NAMESPACE,
46
+ sessionId,
47
+ affinityScope,
48
+ model.provider,
49
+ model.id,
50
+ ]);
51
+ const digest = createHash("sha256").update(material, "utf8").digest("hex");
52
+ return `supi-${digest.slice(0, 56)}`;
53
+ }
54
+
55
+ function isOpenCodeModel(model: Model<Api>): boolean {
56
+ if (model.provider === "opencode" || model.provider === "opencode-go") return true;
57
+
58
+ try {
59
+ return new URL(model.baseUrl).hostname === "opencode.ai";
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ function hasHeader(headers: ProviderHeaders, name: string): boolean {
66
+ const lowerName = name.toLowerCase();
67
+ return Object.keys(headers).some((headerName) => headerName.toLowerCase() === lowerName);
68
+ }
69
+
70
+ function addOpenCodeDefaultHeaders(
71
+ model: Model<Api>,
72
+ affinityId: string,
73
+ headers: ProviderHeaders,
74
+ ): ProviderHeaders {
75
+ if (!isOpenCodeModel(model)) return headers;
76
+
77
+ const result = { ...headers };
78
+ if (!hasHeader(result, "x-opencode-session")) {
79
+ result["x-opencode-session"] = affinityId;
80
+ }
81
+ if (!hasHeader(result, "x-opencode-client")) {
82
+ result["x-opencode-client"] = "pi";
83
+ }
84
+ return result;
85
+ }
86
+
87
+ /**
88
+ * Complete a direct request through PI's model registry.
89
+ *
90
+ * PI resolves authentication, provider headers, environment, and the
91
+ * effective endpoint. This helper adds one stable opaque session identity for
92
+ * the feature prompt stream and applies the OpenCode compatibility defaults.
93
+ * It does not retry, validate output, or present errors.
94
+ *
95
+ * When `maxTokens` is omitted, the underlying registry receives no explicit
96
+ * output cap. A caller that needs the selected model's declared cap can pass
97
+ * `maxTokens: model.maxTokens` without importing PI internals.
98
+ */
99
+ export async function completeModelRequest<TApi extends Api>(
100
+ ctx: ExtensionContext,
101
+ model: Model<TApi>,
102
+ context: Context,
103
+ options: CompleteModelRequestOptions<TApi>,
104
+ ): Promise<AssistantMessage> {
105
+ const { affinityScope, transformHeaders: callerTransformHeaders, ...requestOptions } = options;
106
+ const safeRequestOptions = { ...requestOptions };
107
+ delete safeRequestOptions.apiKey;
108
+ delete safeRequestOptions.env;
109
+ delete safeRequestOptions.sessionId;
110
+
111
+ const affinityId = createModelRequestAffinityId(
112
+ ctx.sessionManager.getSessionId(),
113
+ affinityScope,
114
+ model,
115
+ );
116
+ const transformHeaders = async (headers: ProviderHeaders): Promise<ProviderHeaders> => {
117
+ const transformed = callerTransformHeaders ? await callerTransformHeaders(headers) : headers;
118
+ return addOpenCodeDefaultHeaders(model, affinityId, transformed);
119
+ };
120
+
121
+ // Restore PI's conditional provider-option type after removing owned fields.
122
+ return ctx.modelRegistry.complete(model, context, {
123
+ ...safeRequestOptions,
124
+ signal: safeRequestOptions.signal ?? ctx.signal,
125
+ sessionId: affinityId,
126
+ transformHeaders,
127
+ } as unknown as ModelsApiStreamOptions<TApi>);
128
+ }
10
129
 
11
130
  /**
12
131
  * Options for {@link withRetry}.
@@ -122,6 +241,8 @@ export function extractJsonFromResponse<T extends TSchema>(
122
241
  export interface CallWithJsonResponseOptions {
123
242
  /** The prompt to send to the LLM. */
124
243
  prompt: string;
244
+ /** Stable feature scope for request affinity. Do not include prompt or retry data. */
245
+ affinityScope: string;
125
246
  /** Optional data context appended to the prompt. */
126
247
  dataContext?: string;
127
248
  /** Maximum tokens for the response. Default: 4096 */
@@ -135,8 +256,9 @@ export interface CallWithJsonResponseOptions {
135
256
  /**
136
257
  * Call the LLM with a prompt and validate the JSON response against a TypeBox schema.
137
258
  *
138
- * Handles model resolution, auth, retry via `withRetry`, text extraction,
139
- * JSON regex matching, and TypeBox validation.
259
+ * Handles model resolution, retry via `withRetry`, text extraction, JSON
260
+ * matching, and TypeBox validation. The request itself stays under PI
261
+ * registry authority through {@link completeModelRequest}.
140
262
  *
141
263
  * Returns `null` when:
142
264
  * - No model is available
@@ -145,7 +267,7 @@ export interface CallWithJsonResponseOptions {
145
267
  * - JSON doesn't match the schema
146
268
  * - The request is aborted
147
269
  *
148
- * @param ctx - The extension context for model resolution and auth.
270
+ * @param ctx - The extension context for model selection and PI registry access.
149
271
  * @param options - Call options including prompt, schema, and retry config.
150
272
  * @param schema - TypeBox schema to validate the JSON response against.
151
273
  * @returns The parsed and validated result, or `null`.
@@ -155,14 +277,18 @@ export async function callWithJsonResponse<T extends TSchema>(
155
277
  options: CallWithJsonResponseOptions,
156
278
  schema: T,
157
279
  ): Promise<{ parsed: import("typebox").Static<T> } | null> {
158
- const { prompt, dataContext, maxTokens = 4096, systemPrompt = "", retries = 2 } = options;
280
+ const {
281
+ prompt,
282
+ affinityScope,
283
+ dataContext,
284
+ maxTokens = 4096,
285
+ systemPrompt = "",
286
+ retries = 2,
287
+ } = options;
159
288
 
160
289
  const model = ctx.model ?? ctx.modelRegistry.getAvailable()[0] ?? null;
161
290
  if (!model) return null;
162
291
 
163
- const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
164
- if (!auth.ok || !auth.apiKey) return null;
165
-
166
292
  const fullPrompt = dataContext
167
293
  ? `${prompt}
168
294
 
@@ -171,8 +297,9 @@ ${dataContext}`
171
297
  : prompt;
172
298
 
173
299
  const response = await withRetry(
174
- async () => {
175
- return complete(
300
+ async () =>
301
+ completeModelRequest(
302
+ ctx,
176
303
  model,
177
304
  {
178
305
  systemPrompt,
@@ -185,13 +312,11 @@ ${dataContext}`
185
312
  ],
186
313
  },
187
314
  {
188
- apiKey: auth.apiKey,
189
- headers: auth.headers,
315
+ affinityScope,
190
316
  signal: ctx.signal,
191
317
  maxTokens,
192
318
  },
193
- );
194
- },
319
+ ),
195
320
  { retries, baseDelayMs: 1000, signal: ctx.signal },
196
321
  );
197
322
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-tree-sitter",
3
- "version": "6.4.0",
3
+ "version": "7.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.27.0",
39
- "@mrclrchtr/supi-code-runtime": "6.4.0",
40
- "@mrclrchtr/supi-core": "6.4.0"
39
+ "@mrclrchtr/supi-code-runtime": "7.0.1",
40
+ "@mrclrchtr/supi-core": "7.0.1"
41
41
  },
42
42
  "bundledDependencies": [
43
43
  "@mrclrchtr/supi-code-runtime",
@@ -145,7 +145,9 @@ function mapCalleesAtResult(result: CalleesAtResult): CalleesData {
145
145
  },
146
146
  callees: result.callees.map((c) => ({
147
147
  name: c.name,
148
+ ...(c.displayName === undefined ? {} : { displayName: c.displayName }),
148
149
  startLine: c.range.startLine,
150
+ startCharacter: c.range.startCharacter,
149
151
  })),
150
152
  depth: result.depth,
151
153
  };