@zokizuan/satori-mcp 6.1.0 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/core/backend-diagnostics.d.ts +17 -0
- package/dist/core/backend-diagnostics.js +44 -0
- package/dist/core/call-graph.d.ts +1 -0
- package/dist/core/call-graph.js +1 -1
- package/dist/core/canonical-symbol-identity.js +33 -5
- package/dist/core/handlers.d.ts +2 -0
- package/dist/core/handlers.js +37 -6
- package/dist/core/manage-indexing-handlers.js +41 -4
- package/dist/core/manage-types.d.ts +1 -1
- package/dist/core/navigation-handlers.js +0 -1
- package/dist/core/python-call-fallback.js +88 -6
- package/dist/core/relationship-backed-call-graph.d.ts +2 -2
- package/dist/core/relationship-backed-call-graph.js +95 -34
- package/dist/core/search-debug-helpers.js +1 -0
- package/dist/core/search-exact-fast-path.js +1 -1
- package/dist/core/search-execution.d.ts +4 -1
- package/dist/core/search-execution.js +21 -7
- package/dist/core/search-frontdoor.js +1 -0
- package/dist/core/search-query-planning.js +10 -11
- package/dist/core/search-query-support.d.ts +1 -1
- package/dist/core/search-query-support.js +2 -2
- package/dist/core/search-response-envelopes.d.ts +2 -2
- package/dist/core/search-response-helpers.d.ts +3 -2
- package/dist/core/search-response-helpers.js +5 -3
- package/dist/core/search-result-finalization.js +6 -3
- package/dist/core/search-types.d.ts +6 -2
- package/dist/core/snapshot.d.ts +6 -0
- package/dist/core/snapshot.js +11 -0
- package/dist/core/sync.d.ts +20 -1
- package/dist/core/sync.js +304 -51
- package/dist/core/tool-response-builders.js +1 -0
- package/dist/index.js +31 -4
- package/dist/server/provider-runtime.d.ts +3 -0
- package/dist/server/provider-runtime.js +23 -3
- package/dist/server/shared-runtime-client.d.ts +11 -0
- package/dist/server/shared-runtime-client.js +360 -0
- package/dist/server/shared-runtime-host.d.ts +24 -0
- package/dist/server/shared-runtime-host.js +322 -0
- package/dist/server/shared-runtime-identity.d.ts +51 -0
- package/dist/server/shared-runtime-identity.js +160 -0
- package/dist/server/shared-runtime-lifecycle.d.ts +33 -0
- package/dist/server/shared-runtime-lifecycle.js +259 -0
- package/dist/server/shared-runtime-transport.d.ts +28 -0
- package/dist/server/shared-runtime-transport.js +158 -0
- package/dist/server/shared-runtime.d.ts +83 -0
- package/dist/server/shared-runtime.js +300 -0
- package/dist/server/start-server.d.ts +9 -32
- package/dist/server/start-server.js +15 -142
- package/dist/tools/search_codebase.js +7 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,12 +7,18 @@ Most users should install Satori through `@zokizuan/satori-cli`. The installer w
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
npm install -g @zokizuan/satori-cli@latest
|
|
11
|
+
satori install --client all
|
|
12
|
+
satori doctor
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
The local Potion runtime currently supports Linux x64, including Windows through WSL2. Connected Voyage and explicit local Ollama configurations are also available. See the [main README](https://github.com/ham-zax/satori#quick-start) for runtime choices.
|
|
15
16
|
|
|
17
|
+
When installed through the CLI, compatible offline Potion + LanceDB clients
|
|
18
|
+
share one private local host, provider/LanceDB state, and one Potion worker. Each
|
|
19
|
+
client remains an independent MCP session. Direct `npx @zokizuan/satori-mcp`
|
|
20
|
+
execution is still isolated and does not join the managed host.
|
|
21
|
+
|
|
16
22
|
Direct package execution is intended for inspection and custom harnesses:
|
|
17
23
|
|
|
18
24
|
```bash
|
|
@@ -64,6 +70,9 @@ In a fresh two-task OpenCode comparison where both arms answered correctly, Sato
|
|
|
64
70
|
- Inbound call-graph evidence is advisory and should be verified before blast-radius edits.
|
|
65
71
|
- Provider, model, dimensions, projection, and vector backend are persisted compatibility identities; changing them requires a reindex.
|
|
66
72
|
- Multiple incompatible live Satori runtimes are blocked from mutating the same publication.
|
|
73
|
+
- Managed offline Potion + LanceDB clients on Linux x64/WSL2 share one private
|
|
74
|
+
local host. Connected providers, Milvus, and explicit Ollama runtimes keep
|
|
75
|
+
the direct per-client lifecycle.
|
|
67
76
|
- Offline neural reranking is not shipped today. The candidate boundary permits a future complete-set local scorer to fail back atomically to exact + BM25 + single-vector ordering.
|
|
68
77
|
|
|
69
78
|
## Development
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EmbeddingProviderErrorCode } from "@zokizuan/satori-core";
|
|
1
2
|
export type VectorBackendDiagnosticCode = "ZILLIZ_CLUSTER_STOPPED" | "VECTOR_BACKEND_AUTH_FAILED" | "VECTOR_BACKEND_UNREACHABLE" | "VECTOR_BACKEND_TIMEOUT" | "VECTOR_BACKEND_CONNECTION_CLOSED";
|
|
2
3
|
export interface VectorBackendDiagnostic {
|
|
3
4
|
code: VectorBackendDiagnosticCode;
|
|
@@ -11,5 +12,21 @@ export interface VectorBackendDiagnostic {
|
|
|
11
12
|
};
|
|
12
13
|
};
|
|
13
14
|
}
|
|
15
|
+
export type SemanticPassFailureDiagnostic = {
|
|
16
|
+
passId: "primary" | "expanded";
|
|
17
|
+
errorName: "Error" | "TypeError" | "RangeError" | "ReferenceError" | "SyntaxError" | "AggregateError" | "EmbeddingProviderError" | "NonErrorThrow";
|
|
18
|
+
code: VectorBackendDiagnosticCode | EmbeddingProviderErrorCode | "UNCLASSIFIED_SEARCH_PASS_FAILURE";
|
|
19
|
+
classifier: "embedding_provider" | "vector_backend" | "unclassified";
|
|
20
|
+
retryable?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare function buildSemanticPassFailureDiagnostic(input: {
|
|
23
|
+
passId: SemanticPassFailureDiagnostic["passId"];
|
|
24
|
+
error: unknown;
|
|
25
|
+
embeddingDiagnostic: {
|
|
26
|
+
code: EmbeddingProviderErrorCode;
|
|
27
|
+
retryable: boolean;
|
|
28
|
+
} | null;
|
|
29
|
+
vectorDiagnostic: VectorBackendDiagnostic | null;
|
|
30
|
+
}): SemanticPassFailureDiagnostic;
|
|
14
31
|
export declare function classifyVectorBackendError(error: unknown): VectorBackendDiagnostic | null;
|
|
15
32
|
//# sourceMappingURL=backend-diagnostics.d.ts.map
|
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
const ALLOWED_SEARCH_PASS_ERROR_NAMES = new Set([
|
|
2
|
+
"Error",
|
|
3
|
+
"TypeError",
|
|
4
|
+
"RangeError",
|
|
5
|
+
"ReferenceError",
|
|
6
|
+
"SyntaxError",
|
|
7
|
+
"AggregateError",
|
|
8
|
+
"EmbeddingProviderError",
|
|
9
|
+
]);
|
|
10
|
+
function classifySearchPassErrorName(error) {
|
|
11
|
+
if (!(error instanceof Error)) {
|
|
12
|
+
return "NonErrorThrow";
|
|
13
|
+
}
|
|
14
|
+
return ALLOWED_SEARCH_PASS_ERROR_NAMES.has(error.name)
|
|
15
|
+
? error.name
|
|
16
|
+
: "Error";
|
|
17
|
+
}
|
|
18
|
+
export function buildSemanticPassFailureDiagnostic(input) {
|
|
19
|
+
const errorName = classifySearchPassErrorName(input.error);
|
|
20
|
+
if (input.embeddingDiagnostic) {
|
|
21
|
+
return {
|
|
22
|
+
passId: input.passId,
|
|
23
|
+
errorName,
|
|
24
|
+
code: input.embeddingDiagnostic.code,
|
|
25
|
+
classifier: "embedding_provider",
|
|
26
|
+
retryable: input.embeddingDiagnostic.retryable,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (input.vectorDiagnostic) {
|
|
30
|
+
return {
|
|
31
|
+
passId: input.passId,
|
|
32
|
+
errorName,
|
|
33
|
+
code: input.vectorDiagnostic.code,
|
|
34
|
+
classifier: "vector_backend",
|
|
35
|
+
retryable: input.vectorDiagnostic.hints.backend.retryable,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
passId: input.passId,
|
|
40
|
+
errorName,
|
|
41
|
+
code: "UNCLASSIFIED_SEARCH_PASS_FAILURE",
|
|
42
|
+
classifier: "unclassified",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
1
45
|
function collectErrorText(value, depth = 0) {
|
|
2
46
|
if (depth > 2 || value == null) {
|
|
3
47
|
return "";
|
|
@@ -84,6 +84,7 @@ export type CallGraphQueryResponse = CallGraphResponseSupported | CallGraphRespo
|
|
|
84
84
|
export interface CallGraphDeltaPolicy {
|
|
85
85
|
shouldRebuild(changedFiles: string[]): boolean;
|
|
86
86
|
}
|
|
87
|
+
export declare const DEFAULT_CALL_GRAPH_TEST_REFERENCE_LIMIT = 50;
|
|
87
88
|
export declare class SupportedSourceDeltaPolicy implements CallGraphDeltaPolicy {
|
|
88
89
|
shouldRebuild(changedFiles: string[]): boolean;
|
|
89
90
|
}
|
package/dist/core/call-graph.js
CHANGED
|
@@ -9,7 +9,7 @@ const QUERY_SUPPORTED_EXTENSIONS = new Set(getSupportedExtensionsForCapability('
|
|
|
9
9
|
const QUERY_SUPPORTED_LANGUAGE_IDS = getSupportedLanguageIdsForCapability('callGraphQuery');
|
|
10
10
|
const DEFAULT_IGNORE_PATTERNS = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/coverage/**', '**/.next/**'];
|
|
11
11
|
const DEFAULT_CALL_GRAPH_NOTE_LIMIT = 200;
|
|
12
|
-
const DEFAULT_CALL_GRAPH_TEST_REFERENCE_LIMIT = 50;
|
|
12
|
+
export const DEFAULT_CALL_GRAPH_TEST_REFERENCE_LIMIT = 50;
|
|
13
13
|
const CALL_KEYWORDS = new Set([
|
|
14
14
|
'if', 'for', 'while', 'switch', 'catch', 'return', 'new', 'typeof', 'function', 'class', 'def', 'await', 'with', 'from', 'import',
|
|
15
15
|
]);
|
|
@@ -8,6 +8,25 @@ export function buildCanonicalSymbolRegistryView(symbols) {
|
|
|
8
8
|
}
|
|
9
9
|
return symbolsByKey;
|
|
10
10
|
}
|
|
11
|
+
function spanContains(parent, child) {
|
|
12
|
+
if (parent.file !== child.file) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (parent.span.startByte !== undefined
|
|
16
|
+
&& parent.span.endByte !== undefined
|
|
17
|
+
&& child.span.startByte !== undefined
|
|
18
|
+
&& child.span.endByte !== undefined) {
|
|
19
|
+
return parent.span.startByte <= child.span.startByte
|
|
20
|
+
&& child.span.endByte <= parent.span.endByte;
|
|
21
|
+
}
|
|
22
|
+
return parent.span.startLine <= child.span.startLine
|
|
23
|
+
&& child.span.endLine <= parent.span.endLine;
|
|
24
|
+
}
|
|
25
|
+
function spanSize(symbol, useBytes) {
|
|
26
|
+
return useBytes
|
|
27
|
+
? Math.max(0, symbol.span.endByte - symbol.span.startByte)
|
|
28
|
+
: Math.max(0, symbol.span.endLine - symbol.span.startLine);
|
|
29
|
+
}
|
|
11
30
|
function resolveParent(input) {
|
|
12
31
|
const { symbol } = input;
|
|
13
32
|
if (!symbol.parentKey) {
|
|
@@ -17,17 +36,26 @@ function resolveParent(input) {
|
|
|
17
36
|
: "missing",
|
|
18
37
|
};
|
|
19
38
|
}
|
|
20
|
-
const
|
|
21
|
-
.filter((candidate) => candidate.symbolInstanceId !== symbol.symbolInstanceId
|
|
22
|
-
|
|
39
|
+
const containingCandidates = (input.registry.get(symbol.parentKey) || [])
|
|
40
|
+
.filter((candidate) => (candidate.symbolInstanceId !== symbol.symbolInstanceId
|
|
41
|
+
&& spanContains(candidate, symbol)));
|
|
42
|
+
if (containingCandidates.length === 0) {
|
|
23
43
|
return { state: "missing" };
|
|
24
44
|
}
|
|
25
|
-
|
|
45
|
+
const byteCandidates = containingCandidates.filter((candidate) => (candidate.span.startByte !== undefined
|
|
46
|
+
&& candidate.span.endByte !== undefined
|
|
47
|
+
&& symbol.span.startByte !== undefined
|
|
48
|
+
&& symbol.span.endByte !== undefined));
|
|
49
|
+
const candidates = byteCandidates.length > 0 ? byteCandidates : containingCandidates;
|
|
50
|
+
const useBytes = byteCandidates.length > 0;
|
|
51
|
+
const innermostSize = Math.min(...candidates.map((candidate) => spanSize(candidate, useBytes)));
|
|
52
|
+
const innermost = candidates.filter((candidate) => spanSize(candidate, useBytes) === innermostSize);
|
|
53
|
+
if (innermost.length !== 1) {
|
|
26
54
|
return { state: "ambiguous" };
|
|
27
55
|
}
|
|
28
56
|
return {
|
|
29
57
|
state: "resolved",
|
|
30
|
-
parentSymbolId:
|
|
58
|
+
parentSymbolId: innermost[0].symbolInstanceId,
|
|
31
59
|
};
|
|
32
60
|
}
|
|
33
61
|
export function projectCanonicalSymbolIdentity(input) {
|
package/dist/core/handlers.d.ts
CHANGED
|
@@ -296,6 +296,7 @@ export declare class ToolHandlers {
|
|
|
296
296
|
searchPassCount: number;
|
|
297
297
|
searchPassSuccessCount: number;
|
|
298
298
|
searchPassFailureCount: number;
|
|
299
|
+
semanticPassFailures?: import("./backend-diagnostics.js").SemanticPassFailureDiagnostic[];
|
|
299
300
|
rerankerAttempted: boolean;
|
|
300
301
|
rerankerUsed: boolean;
|
|
301
302
|
tool?: undefined;
|
|
@@ -336,6 +337,7 @@ export declare class ToolHandlers {
|
|
|
336
337
|
searchPassCount: number;
|
|
337
338
|
searchPassSuccessCount: number;
|
|
338
339
|
searchPassFailureCount: number;
|
|
340
|
+
semanticPassFailures?: import("./backend-diagnostics.js").SemanticPassFailureDiagnostic[];
|
|
339
341
|
rerankerAttempted: boolean;
|
|
340
342
|
rerankerUsed: boolean;
|
|
341
343
|
tool?: undefined;
|
package/dist/core/handlers.js
CHANGED
|
@@ -2298,6 +2298,21 @@ export class ToolHandlers {
|
|
|
2298
2298
|
isError: true,
|
|
2299
2299
|
};
|
|
2300
2300
|
}
|
|
2301
|
+
const parsedOperators = this.searchQuerySupport.parseSearchOperators(input.query);
|
|
2302
|
+
if (parsedOperators.semanticQuery.trim().length === 0) {
|
|
2303
|
+
const payload = this.buildInvalidSearchRequestPayload({
|
|
2304
|
+
path: input.path,
|
|
2305
|
+
query: input.query,
|
|
2306
|
+
scope: input.scope,
|
|
2307
|
+
groupBy: input.groupBy,
|
|
2308
|
+
resultMode: input.resultMode,
|
|
2309
|
+
limit: input.limit,
|
|
2310
|
+
}, 'Operator-only search requires semantic text or a positive must:, path:, or lang: value.');
|
|
2311
|
+
return {
|
|
2312
|
+
content: [{ type: "text", text: this.stringifyToolJson(payload) }],
|
|
2313
|
+
isError: true,
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2301
2316
|
const searchDiagnostics = {
|
|
2302
2317
|
queryLength: input.query.length,
|
|
2303
2318
|
limitRequested: input.limit,
|
|
@@ -2425,9 +2440,16 @@ export class ToolHandlers {
|
|
|
2425
2440
|
thresholdMs: SEARCH_FRESHNESS_THRESHOLD_MS,
|
|
2426
2441
|
});
|
|
2427
2442
|
}
|
|
2428
|
-
return this.syncManager.ensureFreshness(effectiveRoot, exactSourceComparisonRequired ? 0 : SEARCH_FRESHNESS_THRESHOLD_MS,
|
|
2429
|
-
|
|
2430
|
-
|
|
2443
|
+
return this.syncManager.ensureFreshness(effectiveRoot, exactSourceComparisonRequired ? 0 : SEARCH_FRESHNESS_THRESHOLD_MS, {
|
|
2444
|
+
...(preparedRead?.vectorReceipt
|
|
2445
|
+
? { preparedVectorReceipt: preparedRead.vectorReceipt }
|
|
2446
|
+
: {}),
|
|
2447
|
+
...(exactSourceComparisonRequired
|
|
2448
|
+
? {
|
|
2449
|
+
exactSourceComparisonPaths: Array.from(changedFilesState.files).sort(),
|
|
2450
|
+
}
|
|
2451
|
+
: {}),
|
|
2452
|
+
});
|
|
2431
2453
|
}),
|
|
2432
2454
|
noteFreshnessMode: (mode) => {
|
|
2433
2455
|
searchDiagnostics.freshnessMode = mode;
|
|
@@ -2470,7 +2492,8 @@ export class ToolHandlers {
|
|
|
2470
2492
|
}
|
|
2471
2493
|
}
|
|
2472
2494
|
const sourceFreshnessWasEstablished = freshnessDecision.mode === 'synced'
|
|
2473
|
-
|| freshnessDecision.mode === 'reconciled_ignore_change'
|
|
2495
|
+
|| freshnessDecision.mode === 'reconciled_ignore_change'
|
|
2496
|
+
|| freshnessDecision.mode === 'skipped_source_unchanged';
|
|
2474
2497
|
const checkpointWarningAlreadyPresent = frontDoorWarnings.includes(WARNING_CODES.SOURCE_FRESHNESS_CHECKPOINT_UNAVAILABLE);
|
|
2475
2498
|
const partialIndexSearchWarnings = !sourceFreshnessWasEstablished
|
|
2476
2499
|
&& !checkpointWarningAlreadyPresent
|
|
@@ -2487,7 +2510,6 @@ export class ToolHandlers {
|
|
|
2487
2510
|
console.log(`${rootTag} Query metadata: length=${input.query.length}, requestId=${requestId}`);
|
|
2488
2511
|
console.log(`${rootTag} Indexing status: Completed`);
|
|
2489
2512
|
console.log(`${rootTag} 🧠 Using embedding provider: ${encoderEngine.getProvider()} for search`);
|
|
2490
|
-
const parsedOperators = this.searchQuerySupport.parseSearchOperators(input.query);
|
|
2491
2513
|
const semanticQuery = parsedOperators.semanticQuery;
|
|
2492
2514
|
const queryPlan = this.searchQuerySupport.buildSearchQueryPlan(semanticQuery, parsedOperators);
|
|
2493
2515
|
searchDiagnostics.routeKind = queryPlan.route.kind;
|
|
@@ -2537,6 +2559,7 @@ export class ToolHandlers {
|
|
|
2537
2559
|
const initialDirtyFilesNotFreshened = initialObservedChangedFilesState.available
|
|
2538
2560
|
&& initialObservedChangedFilesCount > 0
|
|
2539
2561
|
&& freshnessDecision.mode !== 'synced'
|
|
2562
|
+
&& freshnessDecision.mode !== 'skipped_source_unchanged'
|
|
2540
2563
|
&& freshnessDecision.mode !== 'reconciled_ignore_change';
|
|
2541
2564
|
const initialRankingProvenance = {
|
|
2542
2565
|
semanticPassesUsed: [],
|
|
@@ -2755,6 +2778,14 @@ export class ToolHandlers {
|
|
|
2755
2778
|
resultMode: input.resultMode,
|
|
2756
2779
|
limit: input.limit
|
|
2757
2780
|
}, "Search backend failed: all semantic search passes failed. Retry and verify embedding/vector backends are reachable.", "not_ready", "search_backend_failed");
|
|
2781
|
+
if (debugMode === 'full') {
|
|
2782
|
+
payload.hints = {
|
|
2783
|
+
...(payload.hints || {}),
|
|
2784
|
+
debugSearch: {
|
|
2785
|
+
semanticPassFailures: execution.semanticPassFailures.map((failure) => ({ ...failure })),
|
|
2786
|
+
},
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2758
2789
|
return {
|
|
2759
2790
|
content: [{ type: "text", text: this.stringifyToolJson(payload) }],
|
|
2760
2791
|
isError: true,
|
|
@@ -3025,7 +3056,7 @@ export class ToolHandlers {
|
|
|
3025
3056
|
includeSummary: true,
|
|
3026
3057
|
buildEnvelope: (results, disclosure) => {
|
|
3027
3058
|
const recommendedNextAction = entry.recommendedActions[lookup.nextOffset] ?? null;
|
|
3028
|
-
const noiseMitigationHint = this.searchQuerySupport.buildNoiseMitigationHint(entry.canonicalRoot, results.map((result) => result.target.file), entry.baseEnvelope.scope);
|
|
3059
|
+
const noiseMitigationHint = this.searchQuerySupport.buildNoiseMitigationHint(entry.canonicalRoot, results.map((result) => result.target.file), entry.baseEnvelope.scope, this.searchQuerySupport.parseSearchOperators(entry.baseEnvelope.query));
|
|
3029
3060
|
const generatedArtifactsHint = this.buildGeneratedArtifactsVerificationHint(entry.canonicalRoot, results.map((result) => ({
|
|
3030
3061
|
file: result.target.file,
|
|
3031
3062
|
span: result.target.span,
|
|
@@ -766,6 +766,11 @@ export class ManageIndexingHandlers {
|
|
|
766
766
|
}
|
|
767
767
|
this.host.mutationLeaseCoordinator.publishWhileCurrent(mutationLease, publish);
|
|
768
768
|
},
|
|
769
|
+
publicationAuthority: {
|
|
770
|
+
ownerId: mutationLease.ownerId,
|
|
771
|
+
generation: mutationLease.generation,
|
|
772
|
+
operationId: mutationLease.operationId,
|
|
773
|
+
},
|
|
769
774
|
} : {}),
|
|
770
775
|
});
|
|
771
776
|
if (mutationLease) {
|
|
@@ -781,16 +786,41 @@ export class ManageIndexingHandlers {
|
|
|
781
786
|
...result.proof,
|
|
782
787
|
navigation: {
|
|
783
788
|
status: "unproven",
|
|
784
|
-
basis: "
|
|
789
|
+
basis: "generation_proof_in_progress",
|
|
785
790
|
},
|
|
786
791
|
};
|
|
787
|
-
await this.host.
|
|
792
|
+
const proven = await this.host.context.proveIndexedGeneration(absolutePath);
|
|
793
|
+
if (!proven
|
|
794
|
+
|| proven.collectionName !== result.collectionName
|
|
795
|
+
|| proven.marker.indexedFiles !== result.indexedFiles
|
|
796
|
+
|| proven.exactPayloadCount !== result.totalChunks) {
|
|
797
|
+
throw new Error(`Repair completion for '${absolutePath}' did not match an exact proven generation.`);
|
|
798
|
+
}
|
|
799
|
+
if (result.activatedGeneration) {
|
|
800
|
+
const activated = result.activatedGeneration;
|
|
801
|
+
if (activated.collectionName !== proven.collectionName
|
|
802
|
+
|| activated.markerRunId !== proven.marker.runId
|
|
803
|
+
|| activated.relationshipVersion !== this.host.runtimeFingerprint.relationshipVersion
|
|
804
|
+
|| activated.navigation.generationId !== proven.navigation.generationId
|
|
805
|
+
|| activated.navigation.sealHash !== proven.navigation.navigationSealHash
|
|
806
|
+
|| activated.navigation.symbolRegistryManifestHash
|
|
807
|
+
!== proven.navigation.symbolRegistryManifestHash
|
|
808
|
+
|| activated.navigation.relationshipManifestHash
|
|
809
|
+
!== proven.navigation.relationshipManifestHash) {
|
|
810
|
+
throw new Error(`Relationship-only repair for '${absolutePath}' did not prove its activated navigation generation.`);
|
|
811
|
+
}
|
|
812
|
+
const checkpoint = await this.host.context.inspectSourceFreshnessCheckpoint(absolutePath, proven.collectionName, proven);
|
|
813
|
+
if (checkpoint.status !== "valid"
|
|
814
|
+
|| checkpoint.documentDigest !== activated.sourceCheckpointDocumentDigest) {
|
|
815
|
+
throw new Error(`Relationship-only repair for '${absolutePath}' did not preserve its source checkpoint identity.`);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
788
818
|
assertMutationCurrent?.();
|
|
789
819
|
lastRepairProof = {
|
|
790
820
|
...result.proof,
|
|
791
821
|
navigation: {
|
|
792
822
|
status: "matched",
|
|
793
|
-
basis: "
|
|
823
|
+
basis: "activated_generation_proven",
|
|
794
824
|
},
|
|
795
825
|
};
|
|
796
826
|
try {
|
|
@@ -838,6 +868,13 @@ export class ManageIndexingHandlers {
|
|
|
838
868
|
}
|
|
839
869
|
else {
|
|
840
870
|
const operation = persistOperation("blocked");
|
|
871
|
+
if (result.reason === "repair_proof_limit") {
|
|
872
|
+
return this.host.manageResponse("repair", absolutePath, "blocked", result.message, {
|
|
873
|
+
reason: "repair_proof_limit",
|
|
874
|
+
repairProof: result.proof,
|
|
875
|
+
...(operation ? { operation } : {}),
|
|
876
|
+
});
|
|
877
|
+
}
|
|
841
878
|
const createHint = this.host.buildCreateHint(absolutePath);
|
|
842
879
|
return this.host.manageResponse("repair", absolutePath, "blocked", result.message, {
|
|
843
880
|
reason: result.reason || "needs_create",
|
|
@@ -853,7 +890,7 @@ export class ManageIndexingHandlers {
|
|
|
853
890
|
}
|
|
854
891
|
catch (error) {
|
|
855
892
|
console.error("Error in handleRepairIndex:", error);
|
|
856
|
-
if (lastRepairProof?.navigation.basis === "
|
|
893
|
+
if (lastRepairProof?.navigation.basis === "generation_proof_in_progress") {
|
|
857
894
|
lastRepairProof = {
|
|
858
895
|
...lastRepairProof,
|
|
859
896
|
navigation: {
|
|
@@ -7,7 +7,7 @@ export type ManageIndexAction = (typeof MANAGE_INDEX_ACTIONS)[number];
|
|
|
7
7
|
export type ManageIndexStatus = "ok" | "not_ready" | "not_indexed" | "requires_reindex" | "blocked" | "error";
|
|
8
8
|
export declare const MANAGE_INDEX_STATUS_DETAILS: readonly ["summary", "capabilities", "diagnostics", "full"];
|
|
9
9
|
export type ManageIndexStatusDetail = (typeof MANAGE_INDEX_STATUS_DETAILS)[number];
|
|
10
|
-
export type ManageIndexReason = "indexing" | "not_indexed" | "requires_reindex" | "unnecessary_reindex_ignore_only" | "preflight_unknown" | "backend_timeout" | "remote_delete_pending" | "missing_provider_config" | "vector_backend_unavailable" | "runtime_owner_conflict" | "mutation_in_progress" | "needs_create";
|
|
10
|
+
export type ManageIndexReason = "indexing" | "not_indexed" | "requires_reindex" | "unnecessary_reindex_ignore_only" | "preflight_unknown" | "backend_timeout" | "remote_delete_pending" | "missing_provider_config" | "vector_backend_unavailable" | "runtime_owner_conflict" | "mutation_in_progress" | "needs_create" | "repair_proof_limit";
|
|
11
11
|
export type VectorBackendResponseCode = "ZILLIZ_CLUSTER_STOPPED" | "VECTOR_BACKEND_AUTH_FAILED" | "VECTOR_BACKEND_UNREACHABLE" | "VECTOR_BACKEND_TIMEOUT" | "VECTOR_BACKEND_CONNECTION_CLOSED";
|
|
12
12
|
export type ManageReindexPreflightOutcome = "reindex_required" | "reindex_unnecessary_ignore_only" | "unknown" | "probe_failed";
|
|
13
13
|
export interface ManageIndexToolHint {
|
|
@@ -528,7 +528,6 @@ export class NavigationHandlers {
|
|
|
528
528
|
const exactRegistrySymbols = findExactRegistrySymbols({
|
|
529
529
|
symbols: registryState.symbols,
|
|
530
530
|
symbolIdExact: symbolRef.symbolId,
|
|
531
|
-
symbolLabelExact: symbolRef.symbolLabel,
|
|
532
531
|
});
|
|
533
532
|
if (exactRegistrySymbols.length === 0) {
|
|
534
533
|
const payload = this.host.withProofDebugHint({
|
|
@@ -140,21 +140,103 @@ function findPythonDefinitionIndexNearSpan(lines, symbol) {
|
|
|
140
140
|
const windowEnd = Math.min(lines.length, Math.max(spanEndExclusive, startIndex + 48));
|
|
141
141
|
return findPythonDefinitionIndexByName(lines, symbol.name, windowStart, windowEnd);
|
|
142
142
|
}
|
|
143
|
+
function isPythonDecoratorBlock(lines, startIndex, definitionIndex, indent) {
|
|
144
|
+
let delimiterDepth = 0;
|
|
145
|
+
let quote = null;
|
|
146
|
+
let tripleQuoted = false;
|
|
147
|
+
let escaped = false;
|
|
148
|
+
let expectsDecorator = true;
|
|
149
|
+
for (let index = startIndex; index < definitionIndex; index += 1) {
|
|
150
|
+
const line = lines[index] || "";
|
|
151
|
+
const trimmed = line.trim();
|
|
152
|
+
if (expectsDecorator) {
|
|
153
|
+
if (trimmed.length === 0
|
|
154
|
+
|| !trimmed.startsWith("@")
|
|
155
|
+
|| countPythonIndent(line) !== indent) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
expectsDecorator = false;
|
|
159
|
+
}
|
|
160
|
+
else if (trimmed.length === 0 && delimiterDepth === 0 && !tripleQuoted) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
for (let offset = 0; offset < line.length; offset += 1) {
|
|
164
|
+
const char = line[offset];
|
|
165
|
+
const nextTwo = line.slice(offset, offset + 3);
|
|
166
|
+
if (quote) {
|
|
167
|
+
if (escaped) {
|
|
168
|
+
escaped = false;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (char === "\\") {
|
|
172
|
+
escaped = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (tripleQuoted && nextTwo === quote.repeat(3)) {
|
|
176
|
+
quote = null;
|
|
177
|
+
tripleQuoted = false;
|
|
178
|
+
offset += 2;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (!tripleQuoted && char === quote) {
|
|
182
|
+
quote = null;
|
|
183
|
+
}
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (char === "#") {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
if (nextTwo === "'''" || nextTwo === "\"\"\"") {
|
|
190
|
+
quote = char;
|
|
191
|
+
tripleQuoted = true;
|
|
192
|
+
offset += 2;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (char === "'" || char === "\"") {
|
|
196
|
+
quote = char;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (char === "(" || char === "[" || char === "{") {
|
|
200
|
+
delimiterDepth += 1;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (char === ")" || char === "]" || char === "}") {
|
|
204
|
+
delimiterDepth -= 1;
|
|
205
|
+
if (delimiterDepth < 0) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const explicitContinuation = line.trimEnd().endsWith("\\");
|
|
211
|
+
if (!quote && delimiterDepth === 0 && !explicitContinuation) {
|
|
212
|
+
expectsDecorator = true;
|
|
213
|
+
}
|
|
214
|
+
else if (quote && !tripleQuoted) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
escaped = false;
|
|
218
|
+
}
|
|
219
|
+
return expectsDecorator && delimiterDepth === 0 && quote === null;
|
|
220
|
+
}
|
|
143
221
|
function findPythonDecoratedDefinitionStart(lines, definitionIndex) {
|
|
144
222
|
const indent = countPythonIndent(lines[definitionIndex] || "");
|
|
145
|
-
|
|
223
|
+
const candidates = [];
|
|
146
224
|
for (let index = definitionIndex - 1; index >= 0; index -= 1) {
|
|
147
225
|
const line = lines[index] || "";
|
|
148
226
|
const trimmed = line.trim();
|
|
149
|
-
if (trimmed.length === 0) {
|
|
227
|
+
if (trimmed.length === 0 && candidates.length === 0) {
|
|
150
228
|
break;
|
|
151
229
|
}
|
|
152
|
-
if (
|
|
153
|
-
|
|
230
|
+
if (trimmed.startsWith("@") && countPythonIndent(line) === indent) {
|
|
231
|
+
candidates.push(index);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
for (const candidate of candidates.reverse()) {
|
|
235
|
+
if (isPythonDecoratorBlock(lines, candidate, definitionIndex, indent)) {
|
|
236
|
+
return candidate;
|
|
154
237
|
}
|
|
155
|
-
startIndex = index;
|
|
156
238
|
}
|
|
157
|
-
return
|
|
239
|
+
return definitionIndex;
|
|
158
240
|
}
|
|
159
241
|
function findPythonSourceBackedBlockEnd(lines, definitionIndex, indent) {
|
|
160
242
|
let lastContentLine = definitionIndex + 1;
|
|
@@ -40,8 +40,6 @@ export type RelationshipBackedCallGraphResult = {
|
|
|
40
40
|
};
|
|
41
41
|
hints?: Record<string, unknown>;
|
|
42
42
|
};
|
|
43
|
-
/** True when a repo-relative path looks like a test/fixture site (not production call graph signal). */
|
|
44
|
-
export declare function isTestOrFixtureCallerFile(file: string): boolean;
|
|
45
43
|
/**
|
|
46
44
|
* Prefer a single unique suppressed inbound caller *site* file for recovery search.
|
|
47
45
|
* Prefer production files when both production and test sites exist.
|
|
@@ -58,6 +56,8 @@ export declare class RelationshipBackedCallGraph {
|
|
|
58
56
|
private sortNodes;
|
|
59
57
|
private compareEdges;
|
|
60
58
|
private sortEdges;
|
|
59
|
+
private sortTestReferences;
|
|
60
|
+
private buildTestReferences;
|
|
61
61
|
private sortNotes;
|
|
62
62
|
private mapRelationshipConfidence;
|
|
63
63
|
private createNode;
|