@mrclrchtr/supi-code-intelligence 6.3.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.
Files changed (34) hide show
  1. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  2. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  4. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  5. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  6. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +10 -0
  7. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +4 -32
  8. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sources.ts +108 -0
  9. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +165 -14
  10. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +17 -1
  11. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +32 -2
  12. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  13. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  14. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  15. package/package.json +5 -5
  16. package/src/session/health-refresh.ts +15 -10
  17. package/src/session/health-types.ts +5 -0
  18. package/src/session/health-workflow.ts +7 -3
  19. package/src/session/session.ts +12 -5
  20. package/src/substrate/lsp/maintenance.ts +53 -47
  21. package/src/substrate/lsp/source-tracking.ts +247 -0
  22. package/src/tool/code_find/guidance.ts +3 -3
  23. package/src/tool/code_graph/guidance.ts +2 -2
  24. package/src/tool/code_health/guidance.ts +3 -3
  25. package/src/tool/code_health/markdown.ts +21 -5
  26. package/src/tool/code_health/refresh-outcome.ts +11 -0
  27. package/src/tool/code_health/refresh-status.ts +25 -4
  28. package/src/tool/code_health/tui.ts +4 -0
  29. package/src/tool/code_inspect/guidance.ts +5 -3
  30. package/src/tool/code_orientation/guidance.ts +3 -4
  31. package/src/tool/code_refactor_apply/guidance.ts +1 -1
  32. package/src/tool/code_refactor_plan/guidance.ts +3 -5
  33. package/src/tool/code_resolve/guidance.ts +3 -3
  34. package/src/tool/guidance.ts +11 -9
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-tree-sitter",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
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.3.0",
40
- "@mrclrchtr/supi-core": "6.3.0"
39
+ "@mrclrchtr/supi-code-runtime": "6.4.0",
40
+ "@mrclrchtr/supi-core": "6.4.0"
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": "6.3.0",
3
+ "version": "6.4.0",
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-code-runtime": "6.3.0",
39
- "@mrclrchtr/supi-core": "6.3.0",
40
- "@mrclrchtr/supi-tree-sitter": "6.3.0",
41
- "@mrclrchtr/supi-lsp": "6.3.0"
38
+ "@mrclrchtr/supi-lsp": "6.4.0",
39
+ "@mrclrchtr/supi-tree-sitter": "6.4.0",
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",
@@ -10,6 +10,7 @@ import {
10
10
  import { recoverDiagnosticRuntime } from "../analysis/health/recovery.ts";
11
11
  import { mergeDiagnosticEvidence } from "../diagnostics/evidence.ts";
12
12
  import { refreshFileLspMaintenance, refreshLspMaintenance } from "../substrate/lsp/maintenance.ts";
13
+ import type { LspMaintenanceState } from "../substrate/lsp/source-tracking.ts";
13
14
  import type {
14
15
  HealthDiagnosticScope,
15
16
  HealthFileReadiness,
@@ -21,13 +22,15 @@ interface HealthRefreshAttemptOptions {
21
22
  readonly diagnosticsScope: HealthDiagnosticScope;
22
23
  readonly attemptedAt: number;
23
24
  readonly cwd: string;
24
- readonly sentinelSnapshot: Map<string, number>;
25
+ readonly maintenanceState: LspMaintenanceState;
25
26
  readonly control?: CodeRequestControl;
26
27
  readonly reportRecoveryProgress?: () => void;
27
28
  }
28
29
 
29
30
  export interface HealthRefreshCollection {
30
31
  readonly attempt: HealthRefreshAttempt;
32
+ /** Next typed maintenance state, committed only after the attempt returns. */
33
+ readonly maintenanceState: LspMaintenanceState;
31
34
  /** Final report from the runtime, retained only for the current workflow. */
32
35
  readonly diagnosticReport?: WorkspaceDiagnosticReport;
33
36
  }
@@ -49,11 +52,10 @@ async function collectFileRefreshAttempt(
49
52
  const maintenance = await refreshFileLspMaintenance({
50
53
  runtime: options.runtime,
51
54
  cwd: options.cwd,
52
- sentinelSnapshot: options.sentinelSnapshot,
55
+ maintenanceState: options.maintenanceState,
53
56
  filePath: scope.path,
54
57
  control: options.control,
55
58
  });
56
- updateSentinelSnapshot(options.sentinelSnapshot, maintenance.snapshot);
57
59
  const readiness = await options.runtime.waitUntilReadyForFile(
58
60
  scope.path,
59
61
  undefined,
@@ -62,6 +64,7 @@ async function collectFileRefreshAttempt(
62
64
  const fileReadiness: HealthFileReadiness =
63
65
  readiness.kind === "ready" ? "ready" : readiness.kind === "timeout" ? "pending" : "unavailable";
64
66
  return {
67
+ maintenanceState: maintenance.maintenanceState,
65
68
  attempt: {
66
69
  kind: "completed",
67
70
  attemptedAt: options.attemptedAt,
@@ -90,16 +93,16 @@ async function collectWorkspaceRefreshAttempt(
90
93
  const maintenance = await refreshLspMaintenance(
91
94
  options.runtime,
92
95
  options.cwd,
93
- options.sentinelSnapshot,
96
+ options.maintenanceState,
94
97
  {
95
98
  control: options.control,
96
99
  scope: workspaceScope?.filter ?? null,
97
100
  trackSources: workspaceScope !== null,
98
101
  },
99
102
  );
100
- updateSentinelSnapshot(options.sentinelSnapshot, maintenance.snapshot);
101
103
  if (maintenance.failureReason) {
102
104
  return {
105
+ maintenanceState: maintenance.maintenanceState,
103
106
  attempt: {
104
107
  kind: "failed",
105
108
  attemptedAt: options.attemptedAt,
@@ -108,6 +111,7 @@ async function collectWorkspaceRefreshAttempt(
108
111
  operationScope: "workspace-runtime",
109
112
  diagnosticEvidence: maintenance.diagnosticEvidence,
110
113
  processCrashRecovery: emptyProcessCrashRecoveryReport(),
114
+ ...(maintenance.sourceTracking ? { sourceTracking: maintenance.sourceTracking } : {}),
111
115
  reason: maintenance.failureReason,
112
116
  },
113
117
  };
@@ -133,6 +137,7 @@ async function collectWorkspaceRefreshAttempt(
133
137
  );
134
138
  if (recovery.refreshFailureReason) {
135
139
  return {
140
+ maintenanceState: maintenance.maintenanceState,
136
141
  attempt: {
137
142
  kind: "failed",
138
143
  attemptedAt: options.attemptedAt,
@@ -149,12 +154,14 @@ async function collectWorkspaceRefreshAttempt(
149
154
  },
150
155
  diagnosticEvidence,
151
156
  processCrashRecovery: recovery.processCrashRecovery,
157
+ ...(maintenance.sourceTracking ? { sourceTracking: maintenance.sourceTracking } : {}),
152
158
  reason: recovery.refreshFailureReason,
153
159
  },
154
160
  diagnosticReport: recovery.diagnosticReport,
155
161
  };
156
162
  }
157
163
  return {
164
+ maintenanceState: maintenance.maintenanceState,
158
165
  attempt: {
159
166
  kind: "completed",
160
167
  attemptedAt: options.attemptedAt,
@@ -165,6 +172,7 @@ async function collectWorkspaceRefreshAttempt(
165
172
  restartedClients: recovery.restartedClients,
166
173
  processCrashRecovery: recovery.processCrashRecovery,
167
174
  diagnosticEvidence,
175
+ ...(maintenance.sourceTracking ? { sourceTracking: maintenance.sourceTracking } : {}),
168
176
  staleAssessment: {
169
177
  scope: "workspace",
170
178
  suspected: recovery.staleAssessment.suspected,
@@ -179,6 +187,7 @@ async function collectWorkspaceRefreshAttempt(
179
187
  // recorded failed attempt.
180
188
  if (isCodeRequestInterruption(error, options.control)) throw error;
181
189
  return {
190
+ maintenanceState: maintenance.maintenanceState,
182
191
  attempt: {
183
192
  kind: "failed",
184
193
  attemptedAt: options.attemptedAt,
@@ -187,17 +196,13 @@ async function collectWorkspaceRefreshAttempt(
187
196
  operationScope: "workspace-runtime",
188
197
  diagnosticEvidence: maintenance.diagnosticEvidence,
189
198
  processCrashRecovery: emptyProcessCrashRecoveryReport(),
199
+ ...(maintenance.sourceTracking ? { sourceTracking: maintenance.sourceTracking } : {}),
190
200
  reason: errorMessage(error),
191
201
  },
192
202
  };
193
203
  }
194
204
  }
195
205
 
196
- function updateSentinelSnapshot(target: Map<string, number>, next: Map<string, number>): void {
197
- target.clear();
198
- for (const [key, value] of next) target.set(key, value);
199
- }
200
-
201
206
  function errorMessage(error: unknown): string {
202
207
  return error instanceof Error && error.message ? error.message : "Diagnostic refresh failed.";
203
208
  }
@@ -6,6 +6,7 @@ import type {
6
6
  ProjectServerStatusReason,
7
7
  } from "@mrclrchtr/supi-lsp/api";
8
8
  import type { CapabilityWarningReport } from "../analysis/capability/capability-warnings.ts";
9
+ import type { SourceTrackingReport } from "../substrate/lsp/source-tracking.ts";
9
10
 
10
11
  export type HealthSection = "diagnostics" | "servers";
11
12
 
@@ -114,6 +115,8 @@ interface CompletedHealthRefreshAttempt {
114
115
  /** Separate outcome for process-crash route recovery. */
115
116
  readonly processCrashRecovery: ProcessCrashRecoveryReport;
116
117
  readonly staleAssessment: HealthStaleAssessment;
118
+ /** Source discovery and bounded tracking facts for broad refreshes. */
119
+ readonly sourceTracking?: SourceTrackingReport;
117
120
  }
118
121
 
119
122
  /** A diagnostic refresh attempt against an explicit LSP runtime scope. */
@@ -144,6 +147,8 @@ export type HealthRefreshAttempt =
144
147
  readonly diagnosticEvidence?: DiagnosticEvidenceSummary;
145
148
  /** Process-crash outcome, when the recovery pass returned one. */
146
149
  readonly processCrashRecovery?: ProcessCrashRecoveryReport;
150
+ /** Source discovery and bounded tracking facts for broad refreshes. */
151
+ readonly sourceTracking?: SourceTrackingReport;
147
152
  /** File readiness outcome, when a file-scoped attempt failed. */
148
153
  readonly fileReadiness?: HealthFileReadiness;
149
154
  readonly reason: string;
@@ -21,6 +21,7 @@ import { describeStructuralState } from "../analysis/health/recovery.ts";
21
21
  import { collectServers } from "../analysis/health/signals.ts";
22
22
  import { SEMANTIC_READINESS_TIMEOUT_REASON } from "../analysis/readiness.ts";
23
23
  import { resolveScope } from "../analysis/search/paths.ts";
24
+ import type { LspMaintenanceState } from "../substrate/lsp/source-tracking.ts";
24
25
  import type { CapabilityAdapter } from "./capability-adapter.ts";
25
26
  import { collectHealthRefreshAttempt } from "./health-refresh.ts";
26
27
  import type {
@@ -45,8 +46,10 @@ export interface HealthWorkflowDeps {
45
46
  readonly lspController: LspRuntimeController | null;
46
47
  readonly lastRefreshAttempt: HealthRefreshAttempt | null;
47
48
  readonly trackRefreshAttempt: (attempt: HealthRefreshAttempt) => void;
48
- /** Workspace sentinel snapshot for change detection. */
49
- readonly sentinelSnapshot: Map<string, number>;
49
+ /** Typed sentinel, source-baseline, and created-source queue state. */
50
+ readonly maintenanceState: LspMaintenanceState;
51
+ /** Commit maintenance state only after a health attempt completes. */
52
+ readonly updateMaintenanceState: (state: LspMaintenanceState) => void;
50
53
  }
51
54
 
52
55
  /** Collect health facts without rendering a public result. */
@@ -282,7 +285,7 @@ async function collectRefreshState(
282
285
  diagnosticsScope,
283
286
  attemptedAt,
284
287
  cwd: options.deps.cwd,
285
- sentinelSnapshot: options.deps.sentinelSnapshot,
288
+ maintenanceState: options.deps.maintenanceState,
286
289
  control: options.control,
287
290
  reportRecoveryProgress: () =>
288
291
  reportProgress(options.control, {
@@ -291,6 +294,7 @@ async function collectRefreshState(
291
294
  message: "Refreshing diagnostics and recovery state",
292
295
  }),
293
296
  });
297
+ deps.updateMaintenanceState(attempt.maintenanceState);
294
298
  deps.trackRefreshAttempt(attempt.attempt);
295
299
  return attempt;
296
300
  } catch (error) {
@@ -20,6 +20,10 @@ import { realpathSync } from "node:fs";
20
20
  import * as path from "node:path";
21
21
  import type { SessionEntry } from "@earendil-works/pi-coding-agent";
22
22
  import type { LspRuntimeController } from "@mrclrchtr/supi-lsp/api";
23
+ import {
24
+ createLspMaintenanceState,
25
+ type LspMaintenanceState,
26
+ } from "../substrate/lsp/source-tracking.ts";
23
27
  import { type CapabilityAdapter, WorkspaceCapabilityAdapter } from "./capability-adapter.ts";
24
28
  import type { FindWorkflowInput, FindWorkflowOutcome } from "./find-types.ts";
25
29
  import { runFindWorkflow } from "./find-workflow.ts";
@@ -162,8 +166,8 @@ export class WorkspaceCodeIntelligenceSession {
162
166
  */
163
167
  #lspController: LspRuntimeController | null = null;
164
168
 
165
- /** Workspace sentinel snapshot for change detection across explicit queries. */
166
- #sentinelSnapshot: Map<string, number> = new Map();
169
+ /** Typed sentinel, source-baseline, and created-source queue state. */
170
+ #maintenanceState: LspMaintenanceState = createLspMaintenanceState();
167
171
 
168
172
  /** Whether the project owning this session is trusted for config loading. */
169
173
  #projectTrusted = false;
@@ -196,7 +200,7 @@ export class WorkspaceCodeIntelligenceSession {
196
200
  * Called by the extension entry point after LSP initialization.
197
201
  */
198
202
  seedSentinelSnapshot(snapshot: Map<string, number>): void {
199
- this.#sentinelSnapshot = snapshot;
203
+ this.#maintenanceState = createLspMaintenanceState(snapshot);
200
204
  }
201
205
 
202
206
  /** Restore overview state from the active session branch. */
@@ -283,7 +287,10 @@ export class WorkspaceCodeIntelligenceSession {
283
287
  trackRefreshAttempt: (attempt) => {
284
288
  this.#lastHealthRefreshAttempt = attempt;
285
289
  },
286
- sentinelSnapshot: this.#sentinelSnapshot,
290
+ maintenanceState: this.#maintenanceState,
291
+ updateMaintenanceState: (state) => {
292
+ this.#maintenanceState = state;
293
+ },
287
294
  },
288
295
  control,
289
296
  );
@@ -429,7 +436,7 @@ export class WorkspaceCodeIntelligenceSession {
429
436
  this.#workflowTargets.clear();
430
437
  this.#surfacedInstructionDirs.clear();
431
438
  this.#nativeInstructionPaths.clear();
432
- this.#sentinelSnapshot.clear();
439
+ this.#maintenanceState = createLspMaintenanceState();
433
440
  this.#lspController = null;
434
441
  this.#projectTrusted = false;
435
442
  }
@@ -13,7 +13,6 @@ import {
13
13
  isCodeRequestInterruption,
14
14
  throwIfCodeRequestInterrupted,
15
15
  } from "@mrclrchtr/supi-code-runtime/api";
16
- import { isWithinOrEqual } from "@mrclrchtr/supi-core/api";
17
16
  import { uriToFile } from "@mrclrchtr/supi-core/path";
18
17
  import type { DiagnosticEvidenceSummary } from "@mrclrchtr/supi-lsp/api";
19
18
  import {
@@ -27,6 +26,12 @@ import {
27
26
  type WorkspaceLspRuntime,
28
27
  } from "@mrclrchtr/supi-lsp/api";
29
28
  import { mergeDiagnosticEvidence } from "../../diagnostics/evidence.ts";
29
+ import {
30
+ type LspMaintenanceState,
31
+ type SourceTrackingReport,
32
+ trackCreatedSources,
33
+ withSentinelSnapshot,
34
+ } from "./source-tracking.ts";
30
35
 
31
36
  /** Options shared by the workspace and file-scoped maintenance passes. */
32
37
  export interface LspMaintenanceOptions {
@@ -36,11 +41,7 @@ export interface LspMaintenanceOptions {
36
41
  * tracks created files inside this prefix; null disables the scope bound.
37
42
  */
38
43
  scope?: string | null;
39
- /**
40
- * Track files created since the last refresh so the following refresh pull
41
- * includes them. Only the workspace-runtime path sets this; the snapshot is
42
- * still widened on file-scoped passes so later diffs stay correct.
43
- */
44
+ /** Track source additions only for a broad tracked-file refresh. */
44
45
  trackSources?: boolean;
45
46
  }
46
47
 
@@ -52,10 +53,11 @@ export interface LspMaintenanceOptions {
52
53
  export async function refreshLspMaintenance(
53
54
  runtime: WorkspaceLspRuntime,
54
55
  cwd: string,
55
- sentinelSnapshot: Map<string, number>,
56
+ maintenanceState: LspMaintenanceState,
56
57
  options: LspMaintenanceOptions = {},
57
58
  ): Promise<WorkspaceLspMaintenanceResult> {
58
- const { snapshot } = await synchronizeSentinels(runtime, sentinelSnapshot, options);
59
+ const synchronized = await synchronizeSentinels(runtime, cwd, maintenanceState, options);
60
+ const { snapshot, nextState, sourceTracking } = synchronized;
59
61
  let diagnosticEvidence = emptyEvidence();
60
62
  let failureReason: string | undefined;
61
63
 
@@ -80,15 +82,21 @@ export async function refreshLspMaintenance(
80
82
 
81
83
  return {
82
84
  snapshot,
85
+ maintenanceState: nextState,
83
86
  diagnosticEvidence,
87
+ ...(sourceTracking ? { sourceTracking } : {}),
84
88
  ...(failureReason ? { failureReason } : {}),
85
89
  };
86
90
  }
87
91
 
88
92
  /** Sentinel state and diagnostic evidence retained from one workspace pass. */
89
93
  export interface WorkspaceLspMaintenanceResult {
94
+ /** The next typed maintenance state for the session. */
95
+ maintenanceState: LspMaintenanceState;
90
96
  /** The next sentinel snapshot for the session-owned maintenance state. */
91
97
  snapshot: Map<string, number>;
98
+ /** Source discovery and bounded tracking facts for broad refreshes. */
99
+ sourceTracking?: SourceTrackingReport;
92
100
  /** Evidence from every diagnostic refresh performed by this pass. */
93
101
  diagnosticEvidence: DiagnosticEvidenceSummary;
94
102
  /** Failure from the first workspace refresh, when no fresh pass completed. */
@@ -97,6 +105,8 @@ export interface WorkspaceLspMaintenanceResult {
97
105
 
98
106
  /** Sentinel state and stale-file facts from one file-scoped pass. */
99
107
  export interface FileLspMaintenanceResult {
108
+ /** The next typed maintenance state for the session. */
109
+ maintenanceState: LspMaintenanceState;
100
110
  /** The next sentinel snapshot for the session-owned maintenance state. */
101
111
  snapshot: Map<string, number>;
102
112
  /** Number of stale files that matched the requested file. */
@@ -107,14 +117,14 @@ export interface FileLspMaintenanceResult {
107
117
  export async function refreshFileLspMaintenance(options: {
108
118
  runtime: WorkspaceLspRuntime;
109
119
  cwd: string;
110
- sentinelSnapshot: Map<string, number>;
120
+ maintenanceState: LspMaintenanceState;
111
121
  filePath: string;
112
122
  control?: CodeRequestControl;
113
123
  }): Promise<FileLspMaintenanceResult> {
114
- const { runtime, cwd, sentinelSnapshot, filePath, control } = options;
124
+ const { runtime, cwd, maintenanceState, filePath, control } = options;
115
125
  // A cancelled caller stops before any maintenance work starts.
116
126
  throwIfCodeRequestInterrupted(control);
117
- const { snapshot } = await synchronizeSentinels(runtime, sentinelSnapshot);
127
+ const { snapshot, nextState } = await synchronizeSentinels(runtime, cwd, maintenanceState);
118
128
  const target = nodePath.resolve(filePath);
119
129
  const stale = confirmedOutstandingDiagnostics(runtime, cwd).some(
120
130
  (entry) =>
@@ -132,38 +142,51 @@ export async function refreshFileLspMaintenance(options: {
132
142
  runtime.pruneMissingFiles();
133
143
  throwIfCodeRequestInterrupted(control);
134
144
 
135
- return { snapshot, matchedStaleFileCount: stale ? 1 : 0 };
145
+ return {
146
+ snapshot,
147
+ maintenanceState: nextState,
148
+ matchedStaleFileCount: stale ? 1 : 0,
149
+ };
136
150
  }
137
151
 
138
152
  /**
139
- * Sync the widened workspace snapshot, forward sentinel changes, and track
140
- * source files newly created since the last pass.
153
+ * Sync sentinel state and, only for broad refreshes, process source additions.
141
154
  *
142
- * The snapshot is widened to every regular file on every pass so later diffs
143
- * see genuine creations. Only sentinel-file events are forwarded as workspace
144
- * changes (config semantics unchanged); created source files are tracked only
145
- * on the workspace-runtime path and only once the snapshot was primed by an
146
- * earlier pass — the first pass establishes the baseline and cannot tell a
147
- * just-created file from a long-existing one.
155
+ * Sentinel state and source state use separate baselines. A complete source
156
+ * inventory establishes the first baseline without treating existing files as
157
+ * created. Limited inventories leave the previous source state unchanged.
148
158
  */
149
159
  async function synchronizeSentinels(
150
160
  runtime: WorkspaceLspRuntime,
151
- sentinelSnapshot: Map<string, number>,
161
+ cwd: string,
162
+ maintenanceState: LspMaintenanceState,
152
163
  options: LspMaintenanceOptions = {},
153
- ) {
154
- const primed = sentinelSnapshot.size > 0;
155
- const state = runtime.syncWorkspaceSentinelSnapshot(sentinelSnapshot, {
156
- includeSourceFiles: true,
157
- });
158
-
164
+ ): Promise<{
165
+ snapshot: Map<string, number>;
166
+ nextState: LspMaintenanceState;
167
+ sourceTracking?: SourceTrackingReport;
168
+ }> {
169
+ const state = runtime.syncWorkspaceSentinelSnapshot(maintenanceState.sentinelSnapshot);
159
170
  invalidateChangedProjectConfigs(state.changes);
160
171
  if (state.changes.length > 0) runtime.noteWorkspaceChanges(state.changes);
161
172
 
162
- if (options.trackSources && primed) {
163
- await trackCreatedSourceFiles(runtime, state.sourceChanges, options.scope);
173
+ const nextSentinelState = withSentinelSnapshot(maintenanceState, state.snapshot);
174
+ if (!options.trackSources) {
175
+ return { snapshot: state.snapshot, nextState: nextSentinelState };
164
176
  }
165
177
 
166
- return state;
178
+ const sourceResult = await trackCreatedSources({
179
+ runtime,
180
+ cwd,
181
+ state: nextSentinelState,
182
+ scope: options.scope,
183
+ control: options.control,
184
+ });
185
+ return {
186
+ snapshot: state.snapshot,
187
+ nextState: sourceResult.state,
188
+ sourceTracking: sourceResult.report,
189
+ };
167
190
  }
168
191
 
169
192
  /** Invalidate cached tsconfig scope parses only for config files that changed. */
@@ -181,23 +204,6 @@ function invalidateChangedProjectConfigs(changes: readonly FileEvent[]): void {
181
204
  }
182
205
  }
183
206
 
184
- /** Track source files newly created since the last pass, within the scope bound. */
185
- async function trackCreatedSourceFiles(
186
- runtime: WorkspaceLspRuntime,
187
- sourceChanges: readonly FileEvent[],
188
- scope: string | null | undefined,
189
- ): Promise<void> {
190
- for (const change of sourceChanges) {
191
- if (change.type !== FileChangeType.Created) continue;
192
- const filePath = uriToFile(change.uri);
193
- if (scope && !isWithinOrEqual(scope, filePath)) continue;
194
- if (!runtime.isSupportedSourceFile(filePath)) continue;
195
- // Best-effort: a file no client can serve stays untracked and is
196
- // simply absent from evidence until a later explicit request.
197
- await runtime.trackFile(filePath);
198
- }
199
- }
200
-
201
207
  /** Return only diagnostics whose document evidence is confirmed. */
202
208
  function confirmedOutstandingDiagnostics(runtime: WorkspaceLspRuntime, cwd: string) {
203
209
  const outstanding = runtime.getOutstandingDiagnostics(1);