@zokizuan/satori-mcp 4.11.16 → 4.11.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/config.d.ts +18 -0
- package/dist/core/call-graph.d.ts +2 -2
- package/dist/core/call-graph.js +5 -3
- package/dist/core/current-source-symbols.d.ts +15 -0
- package/dist/core/current-source-symbols.js +237 -0
- package/dist/core/handlers.d.ts +16 -2
- package/dist/core/handlers.js +196 -47
- package/dist/core/manage-indexing-handlers.d.ts +13 -10
- package/dist/core/manage-indexing-handlers.js +425 -69
- package/dist/core/manage-maintenance-handlers.d.ts +6 -4
- package/dist/core/manage-maintenance-handlers.js +181 -27
- package/dist/core/manage-types.d.ts +13 -2
- package/dist/core/mutation-lease.d.ts +71 -0
- package/dist/core/mutation-lease.js +274 -0
- package/dist/core/navigation-handlers.js +1 -1
- package/dist/core/registry-file-outline.d.ts +1 -1
- package/dist/core/registry-file-outline.js +99 -39
- package/dist/core/relationship-backed-call-graph.d.ts +4 -3
- package/dist/core/relationship-backed-call-graph.js +27 -8
- package/dist/core/runtime-owner.d.ts +3 -0
- package/dist/core/runtime-owner.js +1 -1
- package/dist/core/search-exact-fast-path.d.ts +1 -0
- package/dist/core/search-exact-fast-path.js +21 -1
- package/dist/core/search-execution.js +30 -1
- package/dist/core/search-query-support.d.ts +5 -0
- package/dist/core/search-query-support.js +148 -1
- package/dist/core/search-response-helpers.js +9 -0
- package/dist/core/snapshot.d.ts +34 -3
- package/dist/core/snapshot.js +397 -21
- package/dist/core/sync.d.ts +20 -3
- package/dist/core/sync.js +302 -52
- package/dist/core/tool-response-builders.d.ts +9 -1
- package/dist/core/tool-response-builders.js +16 -1
- package/dist/core/tracked-root-readiness.d.ts +0 -1
- package/dist/core/tracked-root-readiness.js +0 -1
- package/dist/core/vector-backend-maintenance.d.ts +14 -3
- package/dist/core/vector-backend-maintenance.js +149 -27
- package/dist/core/warnings.d.ts +1 -0
- package/dist/core/warnings.js +1 -0
- package/dist/index.js +14 -5
- package/dist/server/bootstrap-stdio.d.ts +1 -1
- package/dist/server/provider-runtime.d.ts +3 -0
- package/dist/server/provider-runtime.js +15 -6
- package/dist/server/start-server.d.ts +4 -2
- package/dist/server/start-server.js +21 -37
- package/dist/tools/list_codebases.js +3 -1
- package/dist/tools/manage_index.js +6 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ For Codex, add `--install-guidance-hook` only when you want an installer-managed
|
|
|
45
45
|
Advanced direct execution is available through the package bin:
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
npx -y @zokizuan/satori-mcp@4.11.
|
|
48
|
+
npx -y @zokizuan/satori-mcp@4.11.17 --help
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
Use direct package execution for inspection, smoke tests, or unsupported harnesses. For supported clients, prefer `satori-cli install` so startup does not depend on package-manager resolution.
|
|
@@ -73,10 +73,12 @@ Important defaults:
|
|
|
73
73
|
- `read_file` only reads under tracked searchable roots (`indexed` / `sync_completed`); it is not a general host filesystem reader. Relative paths and root escapes are denied.
|
|
74
74
|
- Prefer `recommendedNextAction` when present; if `callGraphHint.supported=false`, follow `navigationFallback` args rather than inventing spans.
|
|
75
75
|
- `requires_reindex` means reindex first, then retry the original call (do not substitute `sync`).
|
|
76
|
-
- `manage_index action="clear"` is destructive and should be explicit. `repair`
|
|
76
|
+
- `manage_index action="clear"` is destructive and should be explicit. `repair` reports structured proof across collection, snapshot, marker, fingerprint, payload, stale remote chunks, and navigation. Only no related collection routes to create; existing incompatible, incomplete, stale, malformed, or unprovable generations route to reindex. Backend failures preserve partial proof when available and should be diagnosed before retrying repair. Successful repair may write a fresh completion marker and rebuild navigation, but never re-embeds or rewrites source chunks.
|
|
77
77
|
- After changing `EMBEDDING_PROVIDER`, `EMBEDDING_MODEL`, embedding dimension, `HYBRID_MODE`, vector backend settings, or the Satori runtime version, restart **all** Satori MCP clients before running `manage_index create`, `reindex`, `sync`, `clear`, or `repair`.
|
|
78
78
|
- Satori records live runtime owners in `~/.satori/runtime/owners.json` and blocks those index mutations with `status="blocked"` / `reason="runtime_owner_conflict"` if another live Satori MCP runtime has a different fingerprint, package version, or config identity.
|
|
79
79
|
- On `runtime_owner_conflict`, the manage envelope lists conflicting **pids**, **versions**, and conflict reasons, plus a concrete `hints.nextStep`. MCP tools never kill other processes. Stop the listed host clients (or only orphaned Satori MCP node PIDs), leave a single package version/config running, then retry. `manage_index status` and `list_codebases` also show a compact **Runtime owners** line (live pids/versions); multi-version means mutations may block. `satori-cli doctor` reports multi-version live owners.
|
|
80
|
+
- A separate canonical-root mutation lease permits multiple readers but only one create/reindex/sync/repair/clear writer across processes. Contention returns `reason="mutation_in_progress"`; `manage_index status` exposes `hints.activeMutation`. Leases do not have a time-based expiry, and a live owner is never evicted by age alone.
|
|
81
|
+
- `zillizDropCollection` is fail-closed: Satori must prove the selected collection's owning codebase root and lease both affected roots before deletion. Unknown ownership or an active writer leaves the collection and local snapshot unchanged.
|
|
80
82
|
- Grouped `search_codebase` results with supported call graph attach **`inboundRecovery`**: a ready-to-run `must:` search to verify callers before blast-radius edits (`call_graph` inbound stays advisory/low).
|
|
81
83
|
|
|
82
84
|
## Navigation Sidecars
|
|
@@ -136,7 +138,7 @@ The full generated tool reference below is kept in the npm README for MCP client
|
|
|
136
138
|
|
|
137
139
|
### `manage_index`
|
|
138
140
|
|
|
139
|
-
Manage index lifecycle operations (create/reindex/sync/status/clear/repair) for a codebase path. repair rebuilds local readiness only when existing vector payload and trusted runtime fingerprint proof match
|
|
141
|
+
Manage index lifecycle operations (create/reindex/sync/status/clear/repair) for a codebase path. repair rebuilds local readiness only when existing vector payload and trusted runtime fingerprint proof match. Repair responses may include optional `repairProof` evidence for collection, snapshot, marker, fingerprint, payload, staleRemoteChunks, and navigation. No related collection routes to create; an existing incompatible, incomplete, stale, malformed, or unprovable generation routes to reindex; backend failures preserve partial proof when collection began and should be diagnosed before retrying repair. Successful repair may write a fresh completion marker and rebuild navigation, but it does not re-embed or rewrite source chunks. Ignore-rule edits in repo-root .satoriignore/.gitignore reconcile automatically in the normal sync path. Use action="sync" for immediate convergence and action="reindex" for full rebuild recovery (preflight may block unnecessary ignore-only reindex churn unless allowUnnecessaryReindex=true). Successful sync responses include `syncStats` with `added`, `removed`, and `modified` counts; consume those fields instead of parsing `humanText`. create/reindex return the kickoff response immediately and do not poll to terminal state; use action="status" to observe progress. Status may include `languageCapabilities`, which combines declared claims with compatible symbol-registry and relationship-sidecar evidence for indexed languages. Mutation responses may include a durable `operation` receipt with `id`, canonical root, generation, accepted time, current phase, last durable transition, runtime fingerprint, and writer identity. Status returns the latest persisted receipt after restart. In a status envelope, top-level `action` remains `status` while `operation.action` names the observed mutation. Terminal phases are `completed`, `failed`, and `blocked`; `operation` is absent when no durable operation exists or contention was rejected before lease acquisition.
|
|
140
142
|
|
|
141
143
|
| Parameter | Type | Required | Default | Description |
|
|
142
144
|
|---|---|---|---|---|
|
package/dist/config.d.ts
CHANGED
|
@@ -12,6 +12,23 @@ export interface IndexFingerprint {
|
|
|
12
12
|
vectorStoreProvider: VectorStoreProvider;
|
|
13
13
|
schemaVersion: 'dense_v3' | 'hybrid_v3';
|
|
14
14
|
}
|
|
15
|
+
export type IndexOperationAction = 'create' | 'reindex' | 'sync' | 'repair' | 'clear';
|
|
16
|
+
export type IndexOperationPhase = 'accepted' | 'preflight' | 'scanning' | 'writing' | 'proving' | 'publishing' | 'completed' | 'failed' | 'blocked';
|
|
17
|
+
export interface IndexOperationReceipt {
|
|
18
|
+
id: string;
|
|
19
|
+
action: IndexOperationAction;
|
|
20
|
+
canonicalRoot: string;
|
|
21
|
+
generation: number;
|
|
22
|
+
acceptedAt: string;
|
|
23
|
+
phase: IndexOperationPhase;
|
|
24
|
+
lastDurableTransitionAt: string;
|
|
25
|
+
runtimeFingerprint: IndexFingerprint;
|
|
26
|
+
writer: {
|
|
27
|
+
ownerId: string;
|
|
28
|
+
pid: number;
|
|
29
|
+
satoriVersion: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
15
32
|
export interface ContextMcpConfig {
|
|
16
33
|
name: string;
|
|
17
34
|
version: string;
|
|
@@ -103,6 +120,7 @@ export interface CodebaseSnapshotV3 {
|
|
|
103
120
|
formatVersion: 'v3';
|
|
104
121
|
codebases: Record<string, CodebaseInfo>;
|
|
105
122
|
clearTombstones?: Record<string, CodebaseClearTombstone>;
|
|
123
|
+
latestOperations?: Record<string, IndexOperationReceipt>;
|
|
106
124
|
lastUpdated: string;
|
|
107
125
|
}
|
|
108
126
|
export type CodebaseSnapshot = CodebaseSnapshotV1 | CodebaseSnapshotV2 | CodebaseSnapshotV3;
|
|
@@ -107,8 +107,8 @@ export declare class CallGraphSidecarManager {
|
|
|
107
107
|
});
|
|
108
108
|
shouldRebuildForDelta(changedFiles: string[]): boolean;
|
|
109
109
|
loadSidecar(codebasePath: string): CallGraphSidecar | null;
|
|
110
|
-
rebuildForCodebase(codebasePath: string, ignorePatterns?: string[]): Promise<CallGraphSidecarInfo>;
|
|
111
|
-
rebuildIfSupportedDelta(codebasePath: string, changedFiles: string[], ignorePatterns?: string[]): Promise<CallGraphSidecarInfo | null>;
|
|
110
|
+
rebuildForCodebase(codebasePath: string, ignorePatterns?: string[], assertMutationCurrent?: () => void): Promise<CallGraphSidecarInfo>;
|
|
111
|
+
rebuildIfSupportedDelta(codebasePath: string, changedFiles: string[], ignorePatterns?: string[], assertMutationCurrent?: () => void): Promise<CallGraphSidecarInfo | null>;
|
|
112
112
|
queryGraph(codebasePath: string, symbolRef: CallGraphSymbolRef, options: {
|
|
113
113
|
direction: CallGraphDirection;
|
|
114
114
|
depth: number;
|
package/dist/core/call-graph.js
CHANGED
|
@@ -68,7 +68,7 @@ export class CallGraphSidecarManager {
|
|
|
68
68
|
return null;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
async rebuildForCodebase(codebasePath, ignorePatterns = []) {
|
|
71
|
+
async rebuildForCodebase(codebasePath, ignorePatterns = [], assertMutationCurrent) {
|
|
72
72
|
const absoluteRoot = path.resolve(codebasePath);
|
|
73
73
|
const files = await this.collectSourceFiles(absoluteRoot, ignorePatterns);
|
|
74
74
|
const graph = await this.buildGraph(absoluteRoot, files);
|
|
@@ -83,7 +83,9 @@ export class CallGraphSidecarManager {
|
|
|
83
83
|
};
|
|
84
84
|
const sidecarPath = this.getSidecarPath(absoluteRoot);
|
|
85
85
|
const sidecarDir = path.dirname(sidecarPath);
|
|
86
|
+
assertMutationCurrent?.();
|
|
86
87
|
await fs.promises.mkdir(sidecarDir, { recursive: true });
|
|
88
|
+
assertMutationCurrent?.();
|
|
87
89
|
await fs.promises.writeFile(sidecarPath, JSON.stringify(sidecar, null, 2), 'utf8');
|
|
88
90
|
return {
|
|
89
91
|
version: 'v3',
|
|
@@ -95,11 +97,11 @@ export class CallGraphSidecarManager {
|
|
|
95
97
|
fingerprint: this.runtimeFingerprint,
|
|
96
98
|
};
|
|
97
99
|
}
|
|
98
|
-
async rebuildIfSupportedDelta(codebasePath, changedFiles, ignorePatterns = []) {
|
|
100
|
+
async rebuildIfSupportedDelta(codebasePath, changedFiles, ignorePatterns = [], assertMutationCurrent) {
|
|
99
101
|
if (!this.shouldRebuildForDelta(changedFiles)) {
|
|
100
102
|
return null;
|
|
101
103
|
}
|
|
102
|
-
return this.rebuildForCodebase(codebasePath, ignorePatterns);
|
|
104
|
+
return this.rebuildForCodebase(codebasePath, ignorePatterns, assertMutationCurrent);
|
|
103
105
|
}
|
|
104
106
|
queryGraph(codebasePath, symbolRef, options) {
|
|
105
107
|
const languageSupport = this.isSupportedQueryLanguage(symbolRef.file);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type SymbolRecord } from "@zokizuan/satori-core";
|
|
2
|
+
import type { PythonSourceBackedSpanRepair } from "./python-call-fallback.js";
|
|
3
|
+
export type CurrentSourceSymbolValidation = PythonSourceBackedSpanRepair & {
|
|
4
|
+
match: "matched" | "missing" | "ambiguous" | "unavailable" | "not_applicable";
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Rebuild exact-navigation identities from the current file using the same
|
|
8
|
+
* splitter and registry builder as indexing. Persisted IDs remain handles;
|
|
9
|
+
* only source-proven spans are substituted.
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateCurrentSourceSymbolSpans(input: {
|
|
12
|
+
codebaseRoot: string;
|
|
13
|
+
symbols: SymbolRecord[];
|
|
14
|
+
}): Promise<CurrentSourceSymbolValidation[]>;
|
|
15
|
+
//# sourceMappingURL=current-source-symbols.d.ts.map
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { AstCodeSplitter, buildSymbolRecordsForFile, normalizeLanguageId, openRegularFileInsideRoot, } from "@zokizuan/satori-core";
|
|
5
|
+
const CURRENT_SOURCE_LANGUAGES = new Set(["typescript", "javascript", "python"]);
|
|
6
|
+
const CURRENT_SOURCE_MAX_BYTES = 256 * 1024;
|
|
7
|
+
function isInsideRoot(candidate, root) {
|
|
8
|
+
const relative = path.relative(root, candidate);
|
|
9
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
10
|
+
}
|
|
11
|
+
async function readCurrentSource(codebaseRoot, relativeFile) {
|
|
12
|
+
let handle;
|
|
13
|
+
try {
|
|
14
|
+
const canonicalRoot = await fs.realpath(codebaseRoot);
|
|
15
|
+
const logicalFile = path.resolve(canonicalRoot, relativeFile);
|
|
16
|
+
if (!isInsideRoot(logicalFile, canonicalRoot)) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
handle = await openRegularFileInsideRoot(logicalFile, canonicalRoot);
|
|
20
|
+
const stat = await handle.stat();
|
|
21
|
+
if (stat.size > CURRENT_SOURCE_MAX_BYTES) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const buffer = Buffer.allocUnsafe(CURRENT_SOURCE_MAX_BYTES + 1);
|
|
25
|
+
let offset = 0;
|
|
26
|
+
while (offset < buffer.length) {
|
|
27
|
+
const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, offset);
|
|
28
|
+
if (bytesRead === 0)
|
|
29
|
+
break;
|
|
30
|
+
offset += bytesRead;
|
|
31
|
+
}
|
|
32
|
+
if (offset > CURRENT_SOURCE_MAX_BYTES) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
return buffer.subarray(0, offset).toString("utf8");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await handle?.close().catch(() => undefined);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function unchangedValidation(symbol, match) {
|
|
45
|
+
return {
|
|
46
|
+
symbol,
|
|
47
|
+
attempted: match !== "not_applicable",
|
|
48
|
+
validated: match === "matched",
|
|
49
|
+
repaired: false,
|
|
50
|
+
startBeforeDefinition: false,
|
|
51
|
+
endTruncated: false,
|
|
52
|
+
match,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function currentDeclarationKey(symbol) {
|
|
56
|
+
const span = symbol.span;
|
|
57
|
+
if (span.endByte !== undefined) {
|
|
58
|
+
// Export/decorator wrapper nodes and their declaration node share the
|
|
59
|
+
// same end boundary. Distinct same-line declarations do not.
|
|
60
|
+
return `${symbol.symbolKey}\0endByte:${span.endByte}`;
|
|
61
|
+
}
|
|
62
|
+
return [
|
|
63
|
+
symbol.symbolKey,
|
|
64
|
+
span.endLine,
|
|
65
|
+
span.endColumn ?? "",
|
|
66
|
+
].join("\0");
|
|
67
|
+
}
|
|
68
|
+
function minOptional(left, right) {
|
|
69
|
+
if (left === undefined)
|
|
70
|
+
return right;
|
|
71
|
+
if (right === undefined)
|
|
72
|
+
return left;
|
|
73
|
+
return Math.min(left, right);
|
|
74
|
+
}
|
|
75
|
+
function maxOptional(left, right) {
|
|
76
|
+
if (left === undefined)
|
|
77
|
+
return right;
|
|
78
|
+
if (right === undefined)
|
|
79
|
+
return left;
|
|
80
|
+
return Math.max(left, right);
|
|
81
|
+
}
|
|
82
|
+
function compareOptionalPosition(left, right) {
|
|
83
|
+
if (left === right)
|
|
84
|
+
return 0;
|
|
85
|
+
if (left === undefined)
|
|
86
|
+
return 1;
|
|
87
|
+
if (right === undefined)
|
|
88
|
+
return -1;
|
|
89
|
+
return left - right;
|
|
90
|
+
}
|
|
91
|
+
function compareSourcePosition(left, right) {
|
|
92
|
+
return left.span.startLine - right.span.startLine
|
|
93
|
+
|| compareOptionalPosition(left.span.startByte, right.span.startByte)
|
|
94
|
+
|| compareOptionalPosition(left.span.startColumn, right.span.startColumn)
|
|
95
|
+
|| left.span.endLine - right.span.endLine
|
|
96
|
+
|| compareOptionalPosition(left.span.endByte, right.span.endByte)
|
|
97
|
+
|| compareOptionalPosition(left.span.endColumn, right.span.endColumn);
|
|
98
|
+
}
|
|
99
|
+
function hasDistinctSourcePositions(symbols) {
|
|
100
|
+
return symbols.every((symbol, index) => index === 0 || compareSourcePosition(symbols[index - 1], symbol) !== 0);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Rebuild exact-navigation identities from the current file using the same
|
|
104
|
+
* splitter and registry builder as indexing. Persisted IDs remain handles;
|
|
105
|
+
* only source-proven spans are substituted.
|
|
106
|
+
*/
|
|
107
|
+
export async function validateCurrentSourceSymbolSpans(input) {
|
|
108
|
+
if (input.symbols.length === 0) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const language = normalizeLanguageId(input.symbols[0].language);
|
|
112
|
+
if (!CURRENT_SOURCE_LANGUAGES.has(language)) {
|
|
113
|
+
return input.symbols.map((symbol) => unchangedValidation(symbol, "not_applicable"));
|
|
114
|
+
}
|
|
115
|
+
const relativeFile = input.symbols[0].file;
|
|
116
|
+
if (input.symbols.some((symbol) => symbol.file !== relativeFile || normalizeLanguageId(symbol.language) !== language)) {
|
|
117
|
+
return input.symbols.map((symbol) => unchangedValidation(symbol, "unavailable"));
|
|
118
|
+
}
|
|
119
|
+
const source = await readCurrentSource(input.codebaseRoot, relativeFile);
|
|
120
|
+
if (source === undefined) {
|
|
121
|
+
return input.symbols.map((symbol) => unchangedValidation(symbol, "unavailable"));
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const splitter = new AstCodeSplitter(Number.MAX_SAFE_INTEGER);
|
|
125
|
+
splitter.setChunkOverlap(0);
|
|
126
|
+
const split = await splitter.splitWithEvidence(source, language, relativeFile);
|
|
127
|
+
if (!split.structuralParseCompleted) {
|
|
128
|
+
return input.symbols.map((symbol) => unchangedValidation(symbol, "unavailable"));
|
|
129
|
+
}
|
|
130
|
+
const chunks = split.chunks;
|
|
131
|
+
const fileHash = crypto.createHash("sha256").update(source, "utf8").digest("hex");
|
|
132
|
+
const extractorVersion = input.symbols[0].extractorVersion;
|
|
133
|
+
const currentByIdentityAndSpan = new Map();
|
|
134
|
+
const currentFileOwner = buildSymbolRecordsForFile({
|
|
135
|
+
relativePath: relativeFile,
|
|
136
|
+
language,
|
|
137
|
+
content: source,
|
|
138
|
+
fileHash,
|
|
139
|
+
extractorVersion,
|
|
140
|
+
chunks: [],
|
|
141
|
+
})[0];
|
|
142
|
+
if (currentFileOwner) {
|
|
143
|
+
currentByIdentityAndSpan.set(currentDeclarationKey(currentFileOwner), currentFileOwner);
|
|
144
|
+
}
|
|
145
|
+
for (const chunk of chunks) {
|
|
146
|
+
const chunkSymbols = buildSymbolRecordsForFile({
|
|
147
|
+
relativePath: relativeFile,
|
|
148
|
+
language,
|
|
149
|
+
content: source,
|
|
150
|
+
fileHash,
|
|
151
|
+
extractorVersion,
|
|
152
|
+
chunks: [chunk],
|
|
153
|
+
});
|
|
154
|
+
for (const current of chunkSymbols.filter((symbol) => symbol.kind !== "file")) {
|
|
155
|
+
const declarationKey = currentDeclarationKey(current);
|
|
156
|
+
const existing = currentByIdentityAndSpan.get(declarationKey);
|
|
157
|
+
if (!existing) {
|
|
158
|
+
currentByIdentityAndSpan.set(declarationKey, current);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
currentByIdentityAndSpan.set(declarationKey, {
|
|
162
|
+
...existing,
|
|
163
|
+
span: {
|
|
164
|
+
...existing.span,
|
|
165
|
+
startLine: Math.min(existing.span.startLine, current.span.startLine),
|
|
166
|
+
endLine: Math.max(existing.span.endLine, current.span.endLine),
|
|
167
|
+
startByte: minOptional(existing.span.startByte, current.span.startByte),
|
|
168
|
+
endByte: maxOptional(existing.span.endByte, current.span.endByte),
|
|
169
|
+
startColumn: minOptional(existing.span.startColumn, current.span.startColumn),
|
|
170
|
+
endColumn: maxOptional(existing.span.endColumn, current.span.endColumn),
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const currentSymbols = [...currentByIdentityAndSpan.values()];
|
|
176
|
+
const currentByKey = new Map();
|
|
177
|
+
for (const current of currentSymbols) {
|
|
178
|
+
const matches = currentByKey.get(current.symbolKey) || [];
|
|
179
|
+
matches.push(current);
|
|
180
|
+
currentByKey.set(current.symbolKey, matches);
|
|
181
|
+
}
|
|
182
|
+
const persistedByKey = new Map();
|
|
183
|
+
for (const symbol of input.symbols) {
|
|
184
|
+
const matches = persistedByKey.get(symbol.symbolKey) || [];
|
|
185
|
+
matches.push(symbol);
|
|
186
|
+
persistedByKey.set(symbol.symbolKey, matches);
|
|
187
|
+
}
|
|
188
|
+
const assignedCurrentByPersistedId = new Map();
|
|
189
|
+
for (const [symbolKey, persisted] of persistedByKey) {
|
|
190
|
+
const current = currentByKey.get(symbolKey) || [];
|
|
191
|
+
if (persisted.length !== current.length) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (persisted.length > 1 && persisted.some((symbol) => symbol.fileHash !== fileHash)) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const sortedPersisted = [...persisted].sort(compareSourcePosition);
|
|
198
|
+
const sortedCurrent = [...current].sort(compareSourcePosition);
|
|
199
|
+
if (persisted.length > 1
|
|
200
|
+
&& (!hasDistinctSourcePositions(sortedPersisted) || !hasDistinctSourcePositions(sortedCurrent))) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
for (let index = 0; index < sortedPersisted.length; index += 1) {
|
|
204
|
+
assignedCurrentByPersistedId.set(sortedPersisted[index].symbolInstanceId, sortedCurrent[index]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return input.symbols.map((symbol) => {
|
|
208
|
+
const matches = currentByKey.get(symbol.symbolKey) || [];
|
|
209
|
+
if (matches.length === 0) {
|
|
210
|
+
return unchangedValidation(symbol, "missing");
|
|
211
|
+
}
|
|
212
|
+
const current = assignedCurrentByPersistedId.get(symbol.symbolInstanceId);
|
|
213
|
+
if (!current) {
|
|
214
|
+
return unchangedValidation(symbol, "ambiguous");
|
|
215
|
+
}
|
|
216
|
+
const repaired = current.span.startLine !== symbol.span.startLine
|
|
217
|
+
|| current.span.endLine !== symbol.span.endLine
|
|
218
|
+
|| current.span.startByte !== symbol.span.startByte
|
|
219
|
+
|| current.span.endByte !== symbol.span.endByte
|
|
220
|
+
|| current.span.startColumn !== symbol.span.startColumn
|
|
221
|
+
|| current.span.endColumn !== symbol.span.endColumn;
|
|
222
|
+
return {
|
|
223
|
+
symbol: repaired ? { ...symbol, span: current.span } : symbol,
|
|
224
|
+
attempted: true,
|
|
225
|
+
validated: true,
|
|
226
|
+
repaired,
|
|
227
|
+
startBeforeDefinition: symbol.span.startLine < current.span.startLine,
|
|
228
|
+
endTruncated: symbol.span.endLine < current.span.endLine,
|
|
229
|
+
match: "matched",
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return input.symbols.map((symbol) => unchangedValidation(symbol, "unavailable"));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=current-source-symbols.js.map
|
package/dist/core/handlers.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { CallGraphSidecarManager } from "./call-graph.js";
|
|
|
8
8
|
import { type ReindexPreflightResult } from "./working-tree-state.js";
|
|
9
9
|
import { type SearchDiagnostics } from "./search-execution.js";
|
|
10
10
|
import { type RuntimeOwnerMutationGate } from "./runtime-owner.js";
|
|
11
|
+
import { MutationLeaseCoordinator } from "./mutation-lease.js";
|
|
11
12
|
type IndexCodebaseArgs = {
|
|
12
13
|
path: string;
|
|
13
14
|
force?: boolean;
|
|
@@ -26,6 +27,7 @@ type ReindexCodebaseArgs = {
|
|
|
26
27
|
type ToolArgs = Record<string, unknown>;
|
|
27
28
|
export declare class ToolHandlers {
|
|
28
29
|
private readonly runtimeOwnerGate;
|
|
30
|
+
private readonly mutationLeaseCoordinator;
|
|
29
31
|
private context;
|
|
30
32
|
private snapshotManager;
|
|
31
33
|
private syncManager;
|
|
@@ -48,7 +50,7 @@ export declare class ToolHandlers {
|
|
|
48
50
|
private readonly vectorBackendMaintenance;
|
|
49
51
|
private readonly relationshipBackedCallGraph;
|
|
50
52
|
private readonly toolResponseBuilders;
|
|
51
|
-
constructor(context: Context, snapshotManager: SnapshotManager, syncManager: SyncManager, runtimeFingerprint: IndexFingerprint, capabilities: CapabilityResolver, now?: () => number, callGraphManager?: CallGraphSidecarManager, reranker?: VoyageAIReranker | null, gitignoreForceReloadEveryN?: number, navigationStore?: NavigationStore, runtimeOwnerGate?: RuntimeOwnerMutationGate | null);
|
|
53
|
+
constructor(context: Context, snapshotManager: SnapshotManager, syncManager: SyncManager, runtimeFingerprint: IndexFingerprint, capabilities: CapabilityResolver, now?: () => number, callGraphManager?: CallGraphSidecarManager, reranker?: VoyageAIReranker | null, gitignoreForceReloadEveryN?: number, navigationStore?: NavigationStore, runtimeOwnerGate?: RuntimeOwnerMutationGate | null, mutationLeaseCoordinator?: MutationLeaseCoordinator | null);
|
|
52
54
|
private setIndexingStats;
|
|
53
55
|
private clearIndexingStats;
|
|
54
56
|
private createSearchPhaseTimings;
|
|
@@ -94,7 +96,20 @@ export declare class ToolHandlers {
|
|
|
94
96
|
private buildRuntimeOwnerConflictResponse;
|
|
95
97
|
private buildIndexingMetadata;
|
|
96
98
|
private isIndexingStateStale;
|
|
99
|
+
/**
|
|
100
|
+
* Recover abandoned `indexing` lifecycle rows.
|
|
101
|
+
*
|
|
102
|
+
* Wall-clock grace applies only before exclusive ownership is proven. When
|
|
103
|
+
* `existingLease` is held (or startup forces exclusive acquisition), recovery
|
|
104
|
+
* runs immediately because a live compliant writer cannot share the root.
|
|
105
|
+
*/
|
|
97
106
|
private recoverStaleIndexingStateIfNeeded;
|
|
107
|
+
/**
|
|
108
|
+
* Startup entry for interrupted-index recovery. Acquires a mutation lease per
|
|
109
|
+
* root, skips live writers, and reuses the fenced recovery path (no unfenced
|
|
110
|
+
* snapshot lifecycle publication).
|
|
111
|
+
*/
|
|
112
|
+
recoverInterruptedIndexingAtStartup(): Promise<void>;
|
|
98
113
|
private buildManageActionBlockedMessage;
|
|
99
114
|
private getManageRetryAfterMs;
|
|
100
115
|
private buildStaleLocalHint;
|
|
@@ -110,7 +125,6 @@ export declare class ToolHandlers {
|
|
|
110
125
|
private isPathWithinCodebase;
|
|
111
126
|
private getTrackedRootEntryForPath;
|
|
112
127
|
private probeLocalSearchCollectionState;
|
|
113
|
-
private markCodebaseSearchStateMissing;
|
|
114
128
|
private summarizeFingerprint;
|
|
115
129
|
private fingerprintsEqual;
|
|
116
130
|
private isRuntimeFingerprintMismatch;
|