knodin 0.8.7 → 0.9.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/dist/bin/cli.js +307 -85
- package/dist/fixtures/update-verification/config/application.xml +4 -0
- package/dist/fixtures/update-verification/dbt/manifest.json +17 -0
- package/dist/fixtures/update-verification/dbt/models/accounts.sql +1 -0
- package/dist/fixtures/update-verification/dbt/models/contacts.sql +1 -0
- package/dist/fixtures/update-verification/fixture.json +147 -0
- package/dist/fixtures/update-verification/force-app/main/default/classes/UpdateFixture.cls +14 -0
- package/dist/fixtures/update-verification/force-app/main/default/flows/Update_Verification.flow-meta.xml +9 -0
- package/dist/fixtures/update-verification/package.json +5 -0
- package/dist/fixtures/update-verification/prisma/schema.prisma +10 -0
- package/dist/fixtures/update-verification/sql/functions.sql +16 -0
- package/dist/fixtures/update-verification/src/CsharpFixture.cs +6 -0
- package/dist/fixtures/update-verification/src/JavaFixture.java +9 -0
- package/dist/fixtures/update-verification/src/javascript.js +7 -0
- package/dist/fixtures/update-verification/src/python_fixture.py +6 -0
- package/dist/fixtures/update-verification/src/typescript.ts +7 -0
- package/dist/fixtures/update-verification/terraform/main.tf +7 -0
- package/dist/fixtures/update-verification/terraform/modules/verified-child/main.tf +3 -0
- package/dist/src/agent-integration.js +26 -3
- package/dist/src/backup-retention.js +811 -0
- package/dist/src/cli-model.js +35 -8
- package/dist/src/docs-sections.js +1 -0
- package/dist/src/doctor.js +142 -12
- package/dist/src/engine/embeddings.js +8 -1
- package/dist/src/engine/index.js +355 -292
- package/dist/src/init.js +222 -179
- package/dist/src/manager-update.js +455 -0
- package/dist/src/release-preflight.js +106 -95
- package/dist/src/repair-lease.js +77 -8
- package/dist/src/response-budget.js +5 -1
- package/dist/src/server.js +18 -5
- package/dist/src/shared-index/compatibility.js +4 -2
- package/dist/src/tools/knodin-tools.js +69 -10
- package/dist/src/update-ceremony.js +20 -24
- package/dist/src/update-coordination.js +158 -0
- package/dist/src/update-executor.js +336 -0
- package/dist/src/update-verifier.js +358 -0
- package/docs/BACKUP-RETENTION.md +54 -0
- package/docs/CLI.md +6 -0
- package/docs/DOCTOR-AND-UPDATES.md +44 -12
- package/docs/SIGNED-UPDATES.md +29 -22
- package/docs/releases/0.9.0.md +43 -0
- package/package.json +7 -1
- package/roadmap/competitive-roadmap.md +8 -0
package/dist/src/engine/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import { runReadOnlyLspQuery } from "../lsp-readonly.js";
|
|
|
25
25
|
import { acquireRepairLease } from "../repair-lease.js";
|
|
26
26
|
import { analyzeResourceReachability, resourceFingerprint, } from "../resource-reachability.js";
|
|
27
27
|
import { contentFingerprint, writeStructuralSnapshot, } from "../structural-snapshot.js";
|
|
28
|
+
import { acquireLifecycleCoordination } from "../update-coordination.js";
|
|
28
29
|
import { KNODIN_VERSION } from "../version.js";
|
|
29
30
|
import * as ann from "./ann-hnsw.js";
|
|
30
31
|
import { allocateCandidate, assertCandidate, discardCandidateFiles, promoteCandidateFile, recoverInterruptedPromotion, } from "./candidate-database.js";
|
|
@@ -783,7 +784,7 @@ function freshnessMechanismFor(repoPath, policy) {
|
|
|
783
784
|
}
|
|
784
785
|
// Bumped whenever any repo is (re)indexed, so cached community-detection results
|
|
785
786
|
// below can be invalidated cheaply instead of recomputed on every call.
|
|
786
|
-
export const KNODIN_SCHEMA_VERSION =
|
|
787
|
+
export const KNODIN_SCHEMA_VERSION = 23;
|
|
787
788
|
const LAST_REBUILD_SCHEMA_VERSION = 22;
|
|
788
789
|
let indexGeneration = 0;
|
|
789
790
|
const resourceReachabilityCache = new Map();
|
|
@@ -969,7 +970,10 @@ function writePersistedStatusAudit(repoPath, databasePath, databaseFingerprint,
|
|
|
969
970
|
};
|
|
970
971
|
try {
|
|
971
972
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
972
|
-
fs.writeFileSync(temporary, `${JSON.stringify(snapshot)}\n`, {
|
|
973
|
+
fs.writeFileSync(temporary, `${JSON.stringify(snapshot)}\n`, {
|
|
974
|
+
mode: 0o600,
|
|
975
|
+
flag: "wx",
|
|
976
|
+
});
|
|
973
977
|
fs.renameSync(temporary, target);
|
|
974
978
|
}
|
|
975
979
|
finally {
|
|
@@ -3229,7 +3233,10 @@ function getPackageMap(repoPath) {
|
|
|
3229
3233
|
}
|
|
3230
3234
|
}
|
|
3231
3235
|
if (cacheable)
|
|
3232
|
-
packageMapCache.set(resolvedRepoPath, {
|
|
3236
|
+
packageMapCache.set(resolvedRepoPath, {
|
|
3237
|
+
generation: indexGeneration,
|
|
3238
|
+
value: map,
|
|
3239
|
+
});
|
|
3233
3240
|
return map;
|
|
3234
3241
|
}
|
|
3235
3242
|
/** Best source entry file for a package: prefer source-y fields, else src/index, else index. */
|
|
@@ -3749,7 +3756,13 @@ function extractApexPlatformDependencies(content, repoPath) {
|
|
|
3749
3756
|
async function indexSalesforceMetadataFile(content, relativePath, repoPath, db, metadata) {
|
|
3750
3757
|
const dependencies = [];
|
|
3751
3758
|
const add = (to, kind, sourceEvidence, methodReference) => {
|
|
3752
|
-
dependencies.push({
|
|
3759
|
+
dependencies.push({
|
|
3760
|
+
to,
|
|
3761
|
+
kind,
|
|
3762
|
+
confidence: 1,
|
|
3763
|
+
sourceEvidence,
|
|
3764
|
+
methodReference,
|
|
3765
|
+
});
|
|
3753
3766
|
};
|
|
3754
3767
|
const addField = (object, value, kind, evidence) => {
|
|
3755
3768
|
const qualified = value.includes(".") ? value : `${object}.${value}`;
|
|
@@ -5579,10 +5592,6 @@ async function indexPrismaFile(content, tree, relativePath, _repoPath, db) {
|
|
|
5579
5592
|
const hasRelation = colDecl.text.includes("@relation");
|
|
5580
5593
|
if (hasRelation) {
|
|
5581
5594
|
dependencies.push({
|
|
5582
|
-
from: modelName,
|
|
5583
|
-
to: targetModel,
|
|
5584
|
-
kind: "orm-relation",
|
|
5585
|
-
}, {
|
|
5586
5595
|
from: relativePath,
|
|
5587
5596
|
to: targetModel,
|
|
5588
5597
|
kind: "orm-relation",
|
|
@@ -5605,7 +5614,6 @@ async function indexPrismaFile(content, tree, relativePath, _repoPath, db) {
|
|
|
5605
5614
|
const modelBlockRegex = /model\s+(\w+)\s*\{([^}]+)\}/g;
|
|
5606
5615
|
let mBlock = modelBlockRegex.exec(content);
|
|
5607
5616
|
while (mBlock !== null) {
|
|
5608
|
-
const srcModel = mBlock[1];
|
|
5609
5617
|
const blockContent = mBlock[2];
|
|
5610
5618
|
// `(?<!\w)` pins the leading field name to a word start; without it the scan
|
|
5611
5619
|
// restarts inside every identifier and re-walks it, which is quadratic.
|
|
@@ -5613,11 +5621,6 @@ async function indexPrismaFile(content, tree, relativePath, _repoPath, db) {
|
|
|
5613
5621
|
let rel = relationRegex.exec(blockContent);
|
|
5614
5622
|
while (rel !== null) {
|
|
5615
5623
|
const targetModel = rel[1];
|
|
5616
|
-
dependencies.push({
|
|
5617
|
-
from: srcModel,
|
|
5618
|
-
to: targetModel,
|
|
5619
|
-
kind: "orm-relation",
|
|
5620
|
-
});
|
|
5621
5624
|
dependencies.push({
|
|
5622
5625
|
from: relativePath,
|
|
5623
5626
|
to: targetModel,
|
|
@@ -6252,7 +6255,10 @@ function reconcileTypeScriptDi(db, repoPath) {
|
|
|
6252
6255
|
.replace(/^type\s+/, "")
|
|
6253
6256
|
.split(/\s+as\s+/);
|
|
6254
6257
|
if (parts[0])
|
|
6255
|
-
imports.set(parts[1] ?? parts[0], {
|
|
6258
|
+
imports.set(parts[1] ?? parts[0], {
|
|
6259
|
+
original: parts[0],
|
|
6260
|
+
module: match[2],
|
|
6261
|
+
});
|
|
6256
6262
|
}
|
|
6257
6263
|
}
|
|
6258
6264
|
for (const match of masked.matchAll(/\bimport\s+([A-Za-z_$][\w$]*)\s+from\s*["']([^"']+)["']/g))
|
|
@@ -6307,11 +6313,15 @@ function reconcileTypeScriptDi(db, repoPath) {
|
|
|
6307
6313
|
return { reason: `Token import ${token} does not resolve uniquely.` };
|
|
6308
6314
|
const definitionFile = followReExports(repoPath, moduleFile, imported.original, 0);
|
|
6309
6315
|
if (!exactSymbol(definitionFile, imported.original))
|
|
6310
|
-
return {
|
|
6316
|
+
return {
|
|
6317
|
+
reason: `Token ${token} does not have one indexed declaration.`,
|
|
6318
|
+
};
|
|
6311
6319
|
return { value: `symbol:${definitionFile}#${imported.original}` };
|
|
6312
6320
|
}
|
|
6313
6321
|
if (!exactSymbol(file, token))
|
|
6314
|
-
return {
|
|
6322
|
+
return {
|
|
6323
|
+
reason: `Token ${token} does not have one indexed declaration.`,
|
|
6324
|
+
};
|
|
6315
6325
|
return { value: `symbol:${file}#${token}` };
|
|
6316
6326
|
};
|
|
6317
6327
|
const implementation = (name) => {
|
|
@@ -6471,7 +6481,10 @@ function reconcileTypeScriptDi(db, repoPath) {
|
|
|
6471
6481
|
? bindingCandidates.filter((binding) => binding.framework === resolution.framework && binding.token === resolution.token)
|
|
6472
6482
|
: (bindingsByToken.get(`${resolution.framework}\0${resolution.registry}\0${resolution.token}`) ?? []);
|
|
6473
6483
|
if (bindings.length === 1)
|
|
6474
|
-
candidates.push({
|
|
6484
|
+
candidates.push({
|
|
6485
|
+
...resolution,
|
|
6486
|
+
implementation: bindings[0].implementation,
|
|
6487
|
+
});
|
|
6475
6488
|
else
|
|
6476
6489
|
candidates.push({
|
|
6477
6490
|
...resolution,
|
|
@@ -6844,7 +6857,9 @@ function mirrorSnapshotFor(repoPath) {
|
|
|
6844
6857
|
const record = lookupMirror(repoPath);
|
|
6845
6858
|
if (!record)
|
|
6846
6859
|
return {};
|
|
6847
|
-
return {
|
|
6860
|
+
return {
|
|
6861
|
+
mirror: { url: record.url, sha: record.sha, fetchedAt: record.fetchedAt },
|
|
6862
|
+
};
|
|
6848
6863
|
}
|
|
6849
6864
|
/**
|
|
6850
6865
|
* Compares indexed symbols against embedded ones. A repository with no symbols
|
|
@@ -8025,6 +8040,8 @@ async function getOrInitDb(repoPath, options = {}) {
|
|
|
8025
8040
|
// newly indexable JSON files. v22 admits markdown as a link-only
|
|
8026
8041
|
// participant, so previously skipped documentation must be visited once
|
|
8027
8042
|
// to populate its doc_link / doc_link_broken edges.
|
|
8043
|
+
// v23 changes the embedding runtime from FP32 to q8. Source topology is
|
|
8044
|
+
// unchanged, so migrate in place and regenerate only semantic vectors.
|
|
8028
8045
|
// Highest version whose upgrade needs the stored data REBUILT. Versions
|
|
8029
8046
|
// above it migrate in place, so an upgrade costs a DELETE rather than a
|
|
8030
8047
|
// full re-index + re-embed (~40 min of CPU on an 18k-symbol corpus).
|
|
@@ -8034,6 +8051,7 @@ async function getOrInitDb(repoPath, options = {}) {
|
|
|
8034
8051
|
// v13 -> v14 purges embeddings orphaned while the FK cascade was inert.
|
|
8035
8052
|
// Runs after the CREATE TABLEs below, since the table must exist.
|
|
8036
8053
|
const needsOrphanPurge = storedVersion < KNODIN_SCHEMA_VERSION;
|
|
8054
|
+
const needsEmbeddingRepresentationRefresh = storedVersion >= 22 && storedVersion < 23;
|
|
8037
8055
|
if (storedVersion < LAST_REBUILD_SCHEMA_VERSION) {
|
|
8038
8056
|
db.run("DROP TRIGGER IF EXISTS after_symbol_insert;");
|
|
8039
8057
|
db.run("DROP TRIGGER IF EXISTS after_symbol_delete;");
|
|
@@ -8252,6 +8270,8 @@ async function getOrInitDb(repoPath, options = {}) {
|
|
|
8252
8270
|
if (needsOrphanPurge) {
|
|
8253
8271
|
purgeOrphanEmbeddings(db);
|
|
8254
8272
|
}
|
|
8273
|
+
if (needsEmbeddingRepresentationRefresh)
|
|
8274
|
+
db.run("DELETE FROM symbol_embeddings;");
|
|
8255
8275
|
finishDbOpenMigration();
|
|
8256
8276
|
// Check if we need to do a full index (e.g. if the database is brand new/empty)
|
|
8257
8277
|
let needsIndex = true;
|
|
@@ -10673,7 +10693,9 @@ function withStaleness(engine, claimRepository, openDb, openPolicy) {
|
|
|
10673
10693
|
},
|
|
10674
10694
|
};
|
|
10675
10695
|
}
|
|
10676
|
-
const DEFAULT_ENGINE_OPEN_POLICY = Object.freeze({
|
|
10696
|
+
const DEFAULT_ENGINE_OPEN_POLICY = Object.freeze({
|
|
10697
|
+
watcher: "enabled",
|
|
10698
|
+
});
|
|
10677
10699
|
/** Default SQLite + Tree-Sitter engine. */
|
|
10678
10700
|
export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
|
|
10679
10701
|
const owner = Symbol("knodin-engine");
|
|
@@ -11049,14 +11071,20 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
|
|
|
11049
11071
|
candidateAudits.set(candidate.id, databaseSha256);
|
|
11050
11072
|
else
|
|
11051
11073
|
candidateAudits.delete(candidate.id);
|
|
11052
|
-
return {
|
|
11074
|
+
return {
|
|
11075
|
+
healthy: issues.length === 0,
|
|
11076
|
+
issues: [...new Set(issues)],
|
|
11077
|
+
databaseSha256,
|
|
11078
|
+
};
|
|
11053
11079
|
},
|
|
11054
11080
|
async promoteCandidate(candidate) {
|
|
11055
11081
|
assertCandidate(candidate.repo, candidate);
|
|
11056
11082
|
const auditedDigest = candidateAudits.get(candidate.id);
|
|
11057
11083
|
if (!auditedDigest)
|
|
11058
11084
|
throw new Error("candidate must pass deep audit before promotion");
|
|
11059
|
-
const confirmation = await engine.auditCandidate(candidate, {
|
|
11085
|
+
const confirmation = await engine.auditCandidate(candidate, {
|
|
11086
|
+
mode: "deep",
|
|
11087
|
+
});
|
|
11060
11088
|
if (!confirmation.healthy || confirmation.databaseSha256 !== auditedDigest)
|
|
11061
11089
|
throw new Error("candidate or repository changed after deep audit");
|
|
11062
11090
|
closeCandidateDatabase(candidate);
|
|
@@ -11245,7 +11273,10 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
|
|
|
11245
11273
|
callees: allCallees.slice(0, cap),
|
|
11246
11274
|
calleeCount: allCallees.length,
|
|
11247
11275
|
...(omissions.length > 0
|
|
11248
|
-
? {
|
|
11276
|
+
? {
|
|
11277
|
+
omissions: omissions.slice(0, cap),
|
|
11278
|
+
omissionCount: omissions.length,
|
|
11279
|
+
}
|
|
11249
11280
|
: {}),
|
|
11250
11281
|
blastRadius: allBlastFiles.slice(0, cap),
|
|
11251
11282
|
blastRadiusCount: allBlastFiles.length,
|
|
@@ -12209,8 +12240,12 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
|
|
|
12209
12240
|
const count = (sql) => db.query(sql).get()?.count ?? 0;
|
|
12210
12241
|
const orphaned = {
|
|
12211
12242
|
embeddings: count(`SELECT COUNT(*) count FROM symbol_embeddings WHERE ${ORPHANED_EMBEDDING_PREDICATE}`),
|
|
12212
|
-
references: count(
|
|
12213
|
-
|
|
12243
|
+
references: count(`SELECT COUNT(*) count FROM "references" r
|
|
12244
|
+
WHERE NOT EXISTS (SELECT 1 FROM index_state i WHERE i.filePath=r.callerFile)
|
|
12245
|
+
AND NOT (r.callerFile LIKE '.knodin/dbt/%' AND EXISTS (SELECT 1 FROM symbols s WHERE s.filePath=r.callerFile))`),
|
|
12246
|
+
dependencies: count(`SELECT COUNT(*) count FROM dependencies d
|
|
12247
|
+
WHERE NOT EXISTS (SELECT 1 FROM index_state i WHERE i.filePath=d.fromFile)
|
|
12248
|
+
AND NOT (d.fromFile LIKE '.knodin/dbt/%' AND EXISTS (SELECT 1 FROM symbols s WHERE s.filePath=d.fromFile))`),
|
|
12214
12249
|
};
|
|
12215
12250
|
const records = staleFiles.map((file) => `indexed file no longer exists: ${file}`);
|
|
12216
12251
|
for (const file of ineligibleFiles)
|
|
@@ -12255,7 +12290,11 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
|
|
|
12255
12290
|
lastIndexedHead: getMeta(db, "lastIndexedHead") ?? "",
|
|
12256
12291
|
freshness,
|
|
12257
12292
|
indexGeneration,
|
|
12258
|
-
verification: {
|
|
12293
|
+
verification: {
|
|
12294
|
+
mode: "deep-audit",
|
|
12295
|
+
auditVerifiedAt: verifiedAt,
|
|
12296
|
+
verifiedAt,
|
|
12297
|
+
},
|
|
12259
12298
|
freshnessMechanism: freshnessMechanismFor(resolved, openPolicy),
|
|
12260
12299
|
repairSteps: needsRepair
|
|
12261
12300
|
? [
|
|
@@ -12736,292 +12775,306 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
|
|
|
12736
12775
|
},
|
|
12737
12776
|
async index(repoPath, files, clean = false, options) {
|
|
12738
12777
|
const resolved = path.resolve(repoPath);
|
|
12739
|
-
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
|
|
12743
|
-
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
|
|
12747
|
-
|
|
12778
|
+
const indexCoordination = acquireLifecycleCoordination({
|
|
12779
|
+
command: "index",
|
|
12780
|
+
repository: resolved,
|
|
12781
|
+
verificationToken: process.env.KNODIN_UPDATE_VERIFICATION_TOKEN,
|
|
12782
|
+
});
|
|
12783
|
+
try {
|
|
12784
|
+
if (clean &&
|
|
12785
|
+
(!files || files.length === 0) &&
|
|
12786
|
+
!openPolicy.databasePath &&
|
|
12787
|
+
!process.env.VITEST &&
|
|
12788
|
+
process.env.NODE_ENV !== "test") {
|
|
12789
|
+
const progress = createIndexProgressReporter(options?.onProgress);
|
|
12790
|
+
progress("starting", 0, "Creating isolated clean-index candidate");
|
|
12791
|
+
const candidate = await engine.createCandidate(resolved);
|
|
12792
|
+
let promoted = false;
|
|
12793
|
+
try {
|
|
12794
|
+
const reconciliation = await engine.reconcileCandidate(candidate, resolved, undefined, {
|
|
12795
|
+
onProgress: progress,
|
|
12796
|
+
skipEmbeddings: options?.skipEmbeddings,
|
|
12797
|
+
});
|
|
12798
|
+
progress("verifying", 0, "Deep-auditing clean-index candidate", {
|
|
12799
|
+
phaseTotal: 1,
|
|
12800
|
+
});
|
|
12801
|
+
const audit = await engine.auditCandidate(candidate, {
|
|
12802
|
+
mode: "deep",
|
|
12803
|
+
});
|
|
12804
|
+
if (!audit.healthy)
|
|
12805
|
+
throw new Error(`clean-index candidate audit failed: ${audit.issues.join(", ")}`);
|
|
12806
|
+
await engine.promoteCandidate(candidate);
|
|
12807
|
+
promoted = true;
|
|
12808
|
+
const activeDb = await openDb(resolved, {
|
|
12809
|
+
skipContentReconciliation: true,
|
|
12810
|
+
skipFreshnessGuard: true,
|
|
12811
|
+
watcher: openPolicy.watcher,
|
|
12812
|
+
});
|
|
12813
|
+
if (!process.env.VITEST && process.env.NODE_ENV !== "test")
|
|
12814
|
+
publishStructuralSnapshot(resolved, activeDb);
|
|
12815
|
+
const health = await engine.status(resolved, {
|
|
12816
|
+
audit: "deep",
|
|
12817
|
+
ignoreActiveOperation: true,
|
|
12818
|
+
});
|
|
12819
|
+
const issueCount = health.missing.files.length + health.missing.records.length;
|
|
12820
|
+
progress("verifying", 1, health.status === "healthy"
|
|
12821
|
+
? "Clean-index candidate promoted and verified"
|
|
12822
|
+
: `Promoted graph has ${issueCount.toLocaleString()} issue(s)`, { phaseTotal: 1 });
|
|
12823
|
+
progress("completed", reconciliation.reconciled.length, "Clean index completed", {
|
|
12824
|
+
phaseTotal: reconciliation.reconciled.length,
|
|
12825
|
+
});
|
|
12826
|
+
return {
|
|
12827
|
+
indexed: reconciliation.reconciled,
|
|
12828
|
+
unchanged: [],
|
|
12829
|
+
verification: {
|
|
12830
|
+
status: health.status,
|
|
12831
|
+
issueCount,
|
|
12832
|
+
missing: health.missing,
|
|
12833
|
+
verifiedAt: health.verification.verifiedAt,
|
|
12834
|
+
},
|
|
12835
|
+
};
|
|
12836
|
+
}
|
|
12837
|
+
catch (error) {
|
|
12838
|
+
if (!promoted && candidates.has(candidate.id))
|
|
12839
|
+
await engine.discardCandidate(candidate);
|
|
12840
|
+
if (openPolicy.watcher === "enabled" && fs.existsSync(resolveDbPath(resolved)))
|
|
12841
|
+
await openDb(resolved, {
|
|
12842
|
+
skipContentReconciliation: true,
|
|
12843
|
+
skipFreshnessGuard: true,
|
|
12844
|
+
});
|
|
12845
|
+
throw error;
|
|
12846
|
+
}
|
|
12847
|
+
}
|
|
12848
|
+
const watcherStateBefore = watcherStates.get(resolved)?.watcher;
|
|
12849
|
+
if (!openPolicy.databasePath)
|
|
12850
|
+
await suspendFileWatcher(resolved);
|
|
12748
12851
|
try {
|
|
12749
|
-
|
|
12852
|
+
invalidateResolutionCaches(repoPath);
|
|
12853
|
+
const indexed = [];
|
|
12854
|
+
const unchanged = [];
|
|
12855
|
+
let scipReport;
|
|
12856
|
+
let sarifReport;
|
|
12857
|
+
const progress = createIndexProgressReporter(options?.onProgress);
|
|
12858
|
+
progress("starting", 0, "Opening local graph database");
|
|
12859
|
+
const db = await openDb(repoPath, {
|
|
12750
12860
|
onProgress: progress,
|
|
12751
|
-
|
|
12752
|
-
|
|
12753
|
-
|
|
12754
|
-
|
|
12755
|
-
if (!audit.healthy)
|
|
12756
|
-
throw new Error(`clean-index candidate audit failed: ${audit.issues.join(", ")}`);
|
|
12757
|
-
await engine.promoteCandidate(candidate);
|
|
12758
|
-
promoted = true;
|
|
12759
|
-
const activeDb = await openDb(resolved, {
|
|
12861
|
+
watcher: "disabled",
|
|
12862
|
+
// The explicit lifecycle operation owns all content work. Otherwise
|
|
12863
|
+
// getOrInitDb's freshness guard can perform a full, invisible
|
|
12864
|
+
// reconciliation before a targeted index even starts.
|
|
12760
12865
|
skipContentReconciliation: true,
|
|
12761
12866
|
skipFreshnessGuard: true,
|
|
12762
|
-
watcher: openPolicy.watcher,
|
|
12763
12867
|
});
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
ignoreActiveOperation: true,
|
|
12769
|
-
});
|
|
12770
|
-
const issueCount = health.missing.files.length + health.missing.records.length;
|
|
12771
|
-
progress("verifying", 1, health.status === "healthy"
|
|
12772
|
-
? "Clean-index candidate promoted and verified"
|
|
12773
|
-
: `Promoted graph has ${issueCount.toLocaleString()} issue(s)`, { phaseTotal: 1 });
|
|
12774
|
-
progress("completed", reconciliation.reconciled.length, "Clean index completed", {
|
|
12775
|
-
phaseTotal: reconciliation.reconciled.length,
|
|
12776
|
-
});
|
|
12777
|
-
return {
|
|
12778
|
-
indexed: reconciliation.reconciled,
|
|
12779
|
-
unchanged: [],
|
|
12780
|
-
verification: {
|
|
12781
|
-
status: health.status,
|
|
12782
|
-
issueCount,
|
|
12783
|
-
missing: health.missing,
|
|
12784
|
-
verifiedAt: health.verification.verifiedAt,
|
|
12785
|
-
},
|
|
12786
|
-
};
|
|
12787
|
-
}
|
|
12788
|
-
catch (error) {
|
|
12789
|
-
if (!promoted && candidates.has(candidate.id))
|
|
12790
|
-
await engine.discardCandidate(candidate);
|
|
12791
|
-
if (openPolicy.watcher === "enabled" && fs.existsSync(resolveDbPath(resolved)))
|
|
12792
|
-
await openDb(resolved, {
|
|
12793
|
-
skipContentReconciliation: true,
|
|
12794
|
-
skipFreshnessGuard: true,
|
|
12868
|
+
let completionMessage = "Local graph is current";
|
|
12869
|
+
if (files && files.length > 0) {
|
|
12870
|
+
progress("indexing-files", 0, `Checking ${files.length.toLocaleString()} requested files`, {
|
|
12871
|
+
phaseTotal: files.length,
|
|
12795
12872
|
});
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
|
|
12802
|
-
try {
|
|
12803
|
-
invalidateResolutionCaches(repoPath);
|
|
12804
|
-
const indexed = [];
|
|
12805
|
-
const unchanged = [];
|
|
12806
|
-
let scipReport;
|
|
12807
|
-
let sarifReport;
|
|
12808
|
-
const progress = createIndexProgressReporter(options?.onProgress);
|
|
12809
|
-
progress("starting", 0, "Opening local graph database");
|
|
12810
|
-
const db = await openDb(repoPath, {
|
|
12811
|
-
onProgress: progress,
|
|
12812
|
-
watcher: "disabled",
|
|
12813
|
-
// The explicit lifecycle operation owns all content work. Otherwise
|
|
12814
|
-
// getOrInitDb's freshness guard can perform a full, invisible
|
|
12815
|
-
// reconciliation before a targeted index even starts.
|
|
12816
|
-
skipContentReconciliation: true,
|
|
12817
|
-
skipFreshnessGuard: true,
|
|
12818
|
-
});
|
|
12819
|
-
let completionMessage = "Local graph is current";
|
|
12820
|
-
if (files && files.length > 0) {
|
|
12821
|
-
progress("indexing-files", 0, `Checking ${files.length.toLocaleString()} requested files`, {
|
|
12822
|
-
phaseTotal: files.length,
|
|
12823
|
-
});
|
|
12824
|
-
for (const [fileIndex, file] of files.entries()) {
|
|
12825
|
-
const relativePath = path.isAbsolute(file) ? path.relative(repoPath, file) : file;
|
|
12826
|
-
const absPath = path.resolve(repoPath, relativePath);
|
|
12827
|
-
const needsUpdate = clean || fileDriftedFromIndexState(db, repoPath, relativePath);
|
|
12828
|
-
if (!needsUpdate) {
|
|
12829
|
-
unchanged.push(relativePath);
|
|
12830
|
-
}
|
|
12831
|
-
else if (fs.existsSync(absPath)) {
|
|
12832
|
-
await indexFile(absPath, relativePath, repoPath, db);
|
|
12833
|
-
recordIndexState(db, repoPath, relativePath);
|
|
12834
|
-
}
|
|
12835
|
-
else {
|
|
12836
|
-
// File was deleted
|
|
12837
|
-
db.run("BEGIN TRANSACTION;");
|
|
12838
|
-
try {
|
|
12839
|
-
deleteSymbolsForFile(db, relativePath);
|
|
12840
|
-
db.run('DELETE FROM "references" WHERE callerFile = ?', [relativePath]);
|
|
12841
|
-
db.run("DELETE FROM dependencies WHERE fromFile = ?", [relativePath]);
|
|
12842
|
-
db.run("DELETE FROM mcp_tools WHERE filePath = ?", [relativePath]);
|
|
12843
|
-
db.run("COMMIT;");
|
|
12873
|
+
for (const [fileIndex, file] of files.entries()) {
|
|
12874
|
+
const relativePath = path.isAbsolute(file) ? path.relative(repoPath, file) : file;
|
|
12875
|
+
const absPath = path.resolve(repoPath, relativePath);
|
|
12876
|
+
const needsUpdate = clean || fileDriftedFromIndexState(db, repoPath, relativePath);
|
|
12877
|
+
if (!needsUpdate) {
|
|
12878
|
+
unchanged.push(relativePath);
|
|
12844
12879
|
}
|
|
12845
|
-
|
|
12846
|
-
db
|
|
12847
|
-
|
|
12880
|
+
else if (fs.existsSync(absPath)) {
|
|
12881
|
+
await indexFile(absPath, relativePath, repoPath, db);
|
|
12882
|
+
recordIndexState(db, repoPath, relativePath);
|
|
12848
12883
|
}
|
|
12849
|
-
|
|
12884
|
+
else {
|
|
12885
|
+
// File was deleted
|
|
12886
|
+
db.run("BEGIN TRANSACTION;");
|
|
12887
|
+
try {
|
|
12888
|
+
deleteSymbolsForFile(db, relativePath);
|
|
12889
|
+
db.run('DELETE FROM "references" WHERE callerFile = ?', [relativePath]);
|
|
12890
|
+
db.run("DELETE FROM dependencies WHERE fromFile = ?", [relativePath]);
|
|
12891
|
+
db.run("DELETE FROM mcp_tools WHERE filePath = ?", [relativePath]);
|
|
12892
|
+
db.run("COMMIT;");
|
|
12893
|
+
}
|
|
12894
|
+
catch (e) {
|
|
12895
|
+
db.run("ROLLBACK;");
|
|
12896
|
+
console.error(`Error deleting file from index: ${relativePath}`, e);
|
|
12897
|
+
}
|
|
12898
|
+
removeIndexState(db, relativePath);
|
|
12899
|
+
}
|
|
12900
|
+
if (needsUpdate)
|
|
12901
|
+
indexed.push(relativePath);
|
|
12902
|
+
progress("indexing-files", fileIndex + 1, `Checking ${files.length.toLocaleString()} requested files`, { phaseTotal: files.length });
|
|
12850
12903
|
}
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
persistSymbolIdentities(db, repoPath, indexed);
|
|
12862
|
-
reconcileTypeScriptDi(db, repoPath);
|
|
12863
|
-
await indexEmbeddings(db, repoPath, progress);
|
|
12864
|
-
indexGeneration++;
|
|
12865
|
-
}
|
|
12866
|
-
const alreadyCurrent = unchanged.length === 1
|
|
12867
|
-
? "1 requested file already current"
|
|
12868
|
-
: `${unchanged.length.toLocaleString()} requested files already current`;
|
|
12869
|
-
completionMessage =
|
|
12870
|
-
indexed.length === 0
|
|
12871
|
-
? alreadyCurrent
|
|
12872
|
-
: `Indexed ${indexed.length.toLocaleString()} requested file(s); ${alreadyCurrent}`;
|
|
12873
|
-
}
|
|
12874
|
-
else {
|
|
12875
|
-
// Full re-index or incremental sync
|
|
12876
|
-
let hasSymbols = false;
|
|
12877
|
-
try {
|
|
12878
|
-
const countResult = db
|
|
12879
|
-
.query("SELECT COUNT(*) as count FROM symbols")
|
|
12880
|
-
.all();
|
|
12881
|
-
if (countResult && countResult.length > 0 && countResult[0].count > 0) {
|
|
12882
|
-
hasSymbols = true;
|
|
12904
|
+
const refreshedFlows = await refreshFlowsForApexFiles(db, repoPath, indexed);
|
|
12905
|
+
for (const flow of refreshedFlows)
|
|
12906
|
+
if (!indexed.includes(flow))
|
|
12907
|
+
indexed.push(flow);
|
|
12908
|
+
if (indexed.length > 0) {
|
|
12909
|
+
progress("finalizing", 0, "Refreshing identities and semantic embeddings");
|
|
12910
|
+
persistSymbolIdentities(db, repoPath, indexed);
|
|
12911
|
+
reconcileTypeScriptDi(db, repoPath);
|
|
12912
|
+
await indexEmbeddings(db, repoPath, progress);
|
|
12913
|
+
indexGeneration++;
|
|
12883
12914
|
}
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12915
|
+
const alreadyCurrent = unchanged.length === 1
|
|
12916
|
+
? "1 requested file already current"
|
|
12917
|
+
: `${unchanged.length.toLocaleString()} requested files already current`;
|
|
12918
|
+
completionMessage =
|
|
12919
|
+
indexed.length === 0
|
|
12920
|
+
? alreadyCurrent
|
|
12921
|
+
: `Indexed ${indexed.length.toLocaleString()} requested file(s); ${alreadyCurrent}`;
|
|
12889
12922
|
}
|
|
12890
12923
|
else {
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
languages: facts.languages,
|
|
12918
|
-
indexers: facts.indexers,
|
|
12919
|
-
elapsedMs: facts.elapsedMs,
|
|
12920
|
-
bounds: {
|
|
12921
|
-
maxBytes: bounds.maxBytes,
|
|
12922
|
-
maxFiles: bounds.maxFiles,
|
|
12923
|
-
maxFacts: bounds.maxFacts,
|
|
12924
|
-
timeoutMs: bounds.timeoutMs,
|
|
12925
|
-
},
|
|
12926
|
-
};
|
|
12927
|
-
for (const file of affectedFiles)
|
|
12928
|
-
if (!indexed.includes(file))
|
|
12924
|
+
// Full re-index or incremental sync
|
|
12925
|
+
let hasSymbols = false;
|
|
12926
|
+
try {
|
|
12927
|
+
const countResult = db
|
|
12928
|
+
.query("SELECT COUNT(*) as count FROM symbols")
|
|
12929
|
+
.all();
|
|
12930
|
+
if (countResult && countResult.length > 0 && countResult[0].count > 0) {
|
|
12931
|
+
hasSymbols = true;
|
|
12932
|
+
}
|
|
12933
|
+
}
|
|
12934
|
+
catch (_) { }
|
|
12935
|
+
if (!clean && hasSymbols) {
|
|
12936
|
+
progress("collecting-files", 0, "Checking existing local graph for changes");
|
|
12937
|
+
await reconcileIndex(repoPath, db, progress);
|
|
12938
|
+
}
|
|
12939
|
+
else {
|
|
12940
|
+
await indexRepo(repoPath, db, progress, options?.skipEmbeddings === true);
|
|
12941
|
+
}
|
|
12942
|
+
// Reuse the same candidate set as indexing/reconciliation. This keeps
|
|
12943
|
+
// LWC HTML/CSS bundle members visible in the response without admitting
|
|
12944
|
+
// arbitrary web assets to the source index.
|
|
12945
|
+
// Appended in a loop, not `push(...files)`: spreading an array as
|
|
12946
|
+
// arguments overflows the call stack once a repository is large
|
|
12947
|
+
// enough. Observed on a 40k-file checkout, which is well within the
|
|
12948
|
+
// range knodin is meant to index.
|
|
12949
|
+
for (const file of collectRepoFiles(repoPath))
|
|
12929
12950
|
indexed.push(file);
|
|
12930
|
-
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
12942
|
-
|
|
12943
|
-
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
|
|
12963
|
-
|
|
12964
|
-
|
|
12965
|
-
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
12951
|
+
completionMessage = `${indexed.length.toLocaleString()} repository file(s) are current`;
|
|
12952
|
+
}
|
|
12953
|
+
if (options?.scip) {
|
|
12954
|
+
const facts = readScipIndex(repoPath, options.scip);
|
|
12955
|
+
const affectedFiles = persistScipFacts(db, repoPath, facts);
|
|
12956
|
+
progress("finalizing", 0, "Refreshing SCIP identities and semantic embeddings");
|
|
12957
|
+
await indexEmbeddings(db, repoPath, progress);
|
|
12958
|
+
indexGeneration++;
|
|
12959
|
+
const bounds = { ...SCIP_DEFAULT_LIMITS, ...options.scip };
|
|
12960
|
+
scipReport = {
|
|
12961
|
+
provenance: "scip",
|
|
12962
|
+
inputPath: facts.inputPath,
|
|
12963
|
+
bytes: facts.bytes,
|
|
12964
|
+
files: facts.files,
|
|
12965
|
+
facts: facts.facts,
|
|
12966
|
+
languages: facts.languages,
|
|
12967
|
+
indexers: facts.indexers,
|
|
12968
|
+
elapsedMs: facts.elapsedMs,
|
|
12969
|
+
bounds: {
|
|
12970
|
+
maxBytes: bounds.maxBytes,
|
|
12971
|
+
maxFiles: bounds.maxFiles,
|
|
12972
|
+
maxFacts: bounds.maxFacts,
|
|
12973
|
+
timeoutMs: bounds.timeoutMs,
|
|
12974
|
+
},
|
|
12975
|
+
};
|
|
12976
|
+
for (const file of affectedFiles)
|
|
12977
|
+
if (!indexed.includes(file))
|
|
12978
|
+
indexed.push(file);
|
|
12979
|
+
}
|
|
12980
|
+
if (options?.sarif) {
|
|
12981
|
+
const facts = readSarifLog(repoPath, options.sarif);
|
|
12982
|
+
persistSarifFindings(db, facts);
|
|
12983
|
+
// Findings are not graph facts, so no re-embedding and no generation
|
|
12984
|
+
// bump: the graph is unchanged and existing answers stay valid.
|
|
12985
|
+
const bounds = { ...SARIF_DEFAULT_LIMITS, ...options.sarif };
|
|
12986
|
+
sarifReport = {
|
|
12987
|
+
provenance: "sarif",
|
|
12988
|
+
inputPath: facts.inputPath,
|
|
12989
|
+
bytes: facts.bytes,
|
|
12990
|
+
tools: facts.tools,
|
|
12991
|
+
rules: facts.rules,
|
|
12992
|
+
files: facts.files,
|
|
12993
|
+
findings: facts.findings.length,
|
|
12994
|
+
skippedUnresolved: facts.skippedUnresolved,
|
|
12995
|
+
elapsedMs: facts.elapsedMs,
|
|
12996
|
+
bounds: {
|
|
12997
|
+
maxBytes: bounds.maxBytes,
|
|
12998
|
+
maxFindings: bounds.maxFindings,
|
|
12999
|
+
timeoutMs: bounds.timeoutMs,
|
|
13000
|
+
},
|
|
13001
|
+
};
|
|
13002
|
+
}
|
|
13003
|
+
progress("verifying", 0, "Verifying local graph health", {
|
|
13004
|
+
phaseTotal: 1,
|
|
13005
|
+
});
|
|
13006
|
+
let health = await engine.status(repoPath, {
|
|
12972
13007
|
audit: "deep",
|
|
12973
13008
|
ignoreActiveOperation: true,
|
|
12974
13009
|
});
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
|
|
12980
|
-
|
|
12981
|
-
// Explicit indexing counts as a successful reconciliation only after
|
|
12982
|
-
// the audit proves that no requested or unrequested drift remains.
|
|
12983
|
-
if (files && files.length > 0) {
|
|
13010
|
+
const contentVerified = health.coverage.sourceFiles === health.coverage.indexedFiles &&
|
|
13011
|
+
health.missing.files.length === 0 &&
|
|
13012
|
+
health.missing.records.length === 0 &&
|
|
13013
|
+
Object.values(health.orphaned).every((count) => count === 0);
|
|
13014
|
+
if (contentVerified &&
|
|
13015
|
+
(health.freshness.state === "stale-head" || health.freshness.state === "unknown")) {
|
|
12984
13016
|
setMeta(db, "lastIndexedHead", gitHead(repoPath) ?? "");
|
|
12985
13017
|
setMeta(db, "lastSuccessfulReconciliation", new Date().toISOString());
|
|
12986
13018
|
recordFreshnessBaseline(repoPath, db);
|
|
13019
|
+
statusCache.delete(databaseCacheKey(repoPath, openPolicy.databasePath));
|
|
13020
|
+
health = await engine.status(repoPath, {
|
|
13021
|
+
audit: "deep",
|
|
13022
|
+
ignoreActiveOperation: true,
|
|
13023
|
+
});
|
|
12987
13024
|
}
|
|
12988
|
-
|
|
12989
|
-
//
|
|
12990
|
-
|
|
12991
|
-
|
|
12992
|
-
|
|
13025
|
+
// The postcondition belongs to this index result. Do not turn the next
|
|
13026
|
+
// explicit `status` call into a cache hit against an internal audit.
|
|
13027
|
+
statusCache.delete(databaseCacheKey(repoPath, openPolicy.databasePath));
|
|
13028
|
+
const issueCount = health.missing.files.length + health.missing.records.length;
|
|
13029
|
+
if (health.status === "healthy") {
|
|
13030
|
+
// Explicit indexing counts as a successful reconciliation only after
|
|
13031
|
+
// the audit proves that no requested or unrequested drift remains.
|
|
13032
|
+
if (files && files.length > 0) {
|
|
13033
|
+
setMeta(db, "lastIndexedHead", gitHead(repoPath) ?? "");
|
|
13034
|
+
setMeta(db, "lastSuccessfulReconciliation", new Date().toISOString());
|
|
13035
|
+
recordFreshnessBaseline(repoPath, db);
|
|
13036
|
+
}
|
|
13037
|
+
noteFreshness(repoPath, "fresh");
|
|
13038
|
+
// Test engines use an in-memory graph and must not create untracked
|
|
13039
|
+
// repository artifacts that pollute Git-diff/review fixtures.
|
|
13040
|
+
if (!process.env.VITEST && process.env.NODE_ENV !== "test")
|
|
13041
|
+
publishStructuralSnapshot(repoPath, db);
|
|
13042
|
+
}
|
|
13043
|
+
else {
|
|
13044
|
+
freshnessProbes.delete(path.resolve(repoPath));
|
|
13045
|
+
}
|
|
13046
|
+
progress("verifying", 1, health.status === "healthy"
|
|
13047
|
+
? "Local graph health verified"
|
|
13048
|
+
: `Local graph still has ${issueCount.toLocaleString()} issue(s)`, { phaseTotal: 1 });
|
|
13049
|
+
const completed = files?.length ?? indexed.length;
|
|
13050
|
+
progress("completed", completed, health.status === "healthy"
|
|
13051
|
+
? completionMessage
|
|
13052
|
+
: `${completionMessage}; verification found ${issueCount.toLocaleString()} issue(s)`, { phaseTotal: completed });
|
|
13053
|
+
return {
|
|
13054
|
+
indexed,
|
|
13055
|
+
unchanged,
|
|
13056
|
+
scip: scipReport,
|
|
13057
|
+
sarif: sarifReport,
|
|
13058
|
+
verification: {
|
|
13059
|
+
status: health.status,
|
|
13060
|
+
issueCount,
|
|
13061
|
+
missing: health.missing,
|
|
13062
|
+
verifiedAt: health.verification.verifiedAt,
|
|
13063
|
+
},
|
|
13064
|
+
};
|
|
13065
|
+
}
|
|
13066
|
+
finally {
|
|
13067
|
+
const activeDb = dbInstances.get(resolved);
|
|
13068
|
+
if (!openPolicy.databasePath &&
|
|
13069
|
+
activeDb &&
|
|
13070
|
+
openPolicy.watcher === "enabled" &&
|
|
13071
|
+
watcherStateBefore !== "scale-limited" &&
|
|
13072
|
+
watcherStateBefore !== "resource-limited")
|
|
13073
|
+
startFileWatcher(resolved, activeDb, openPolicy.watcherFileLimit);
|
|
12993
13074
|
}
|
|
12994
|
-
else {
|
|
12995
|
-
freshnessProbes.delete(path.resolve(repoPath));
|
|
12996
|
-
}
|
|
12997
|
-
progress("verifying", 1, health.status === "healthy"
|
|
12998
|
-
? "Local graph health verified"
|
|
12999
|
-
: `Local graph still has ${issueCount.toLocaleString()} issue(s)`, { phaseTotal: 1 });
|
|
13000
|
-
const completed = files?.length ?? indexed.length;
|
|
13001
|
-
progress("completed", completed, health.status === "healthy"
|
|
13002
|
-
? completionMessage
|
|
13003
|
-
: `${completionMessage}; verification found ${issueCount.toLocaleString()} issue(s)`, { phaseTotal: completed });
|
|
13004
|
-
return {
|
|
13005
|
-
indexed,
|
|
13006
|
-
unchanged,
|
|
13007
|
-
scip: scipReport,
|
|
13008
|
-
sarif: sarifReport,
|
|
13009
|
-
verification: {
|
|
13010
|
-
status: health.status,
|
|
13011
|
-
issueCount,
|
|
13012
|
-
missing: health.missing,
|
|
13013
|
-
verifiedAt: health.verification.verifiedAt,
|
|
13014
|
-
},
|
|
13015
|
-
};
|
|
13016
13075
|
}
|
|
13017
13076
|
finally {
|
|
13018
|
-
|
|
13019
|
-
if (!openPolicy.databasePath &&
|
|
13020
|
-
activeDb &&
|
|
13021
|
-
openPolicy.watcher === "enabled" &&
|
|
13022
|
-
watcherStateBefore !== "scale-limited" &&
|
|
13023
|
-
watcherStateBefore !== "resource-limited")
|
|
13024
|
-
startFileWatcher(resolved, activeDb, openPolicy.watcherFileLimit);
|
|
13077
|
+
indexCoordination.release();
|
|
13025
13078
|
}
|
|
13026
13079
|
},
|
|
13027
13080
|
async search(query, repoPath, limit, options = {}) {
|
|
@@ -15070,7 +15123,11 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
|
|
|
15070
15123
|
});
|
|
15071
15124
|
diskFingerprint = resourceFingerprint(inputs);
|
|
15072
15125
|
if (!diskDrifted)
|
|
15073
|
-
setBoundedCache(resourceCorpusCache, resolvedRepoPath, {
|
|
15126
|
+
setBoundedCache(resourceCorpusCache, resolvedRepoPath, {
|
|
15127
|
+
generation: indexGeneration,
|
|
15128
|
+
inputs,
|
|
15129
|
+
fingerprint: diskFingerprint,
|
|
15130
|
+
}, RESOURCE_CORPUS_CACHE_LIMIT);
|
|
15074
15131
|
}
|
|
15075
15132
|
// Disk state is part of the cache identity. A generation-only key can
|
|
15076
15133
|
// return a previously fresh result during the freshness-probe TTL after
|
|
@@ -15080,7 +15137,10 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
|
|
|
15080
15137
|
const cached = resourceReachabilityCache.get(cacheKey);
|
|
15081
15138
|
if (cached?.generation === indexGeneration) {
|
|
15082
15139
|
const result = structuredClone(cached.result);
|
|
15083
|
-
return finish([], {
|
|
15140
|
+
return finish([], {
|
|
15141
|
+
count: result.paths.length,
|
|
15142
|
+
resourceReachability: result,
|
|
15143
|
+
});
|
|
15084
15144
|
}
|
|
15085
15145
|
const result = analyzeResourceReachability(inputs, {
|
|
15086
15146
|
maxItems: cap,
|
|
@@ -15090,7 +15150,10 @@ export function createEngine(openPolicy = DEFAULT_ENGINE_OPEN_POLICY) {
|
|
|
15090
15150
|
expectedFingerprint: diskDrifted ? `indexed:${diskFingerprint}` : diskFingerprint,
|
|
15091
15151
|
});
|
|
15092
15152
|
setBoundedCache(resourceReachabilityCache, cacheKey, { generation: indexGeneration, result: structuredClone(result) }, RESOURCE_REACHABILITY_CACHE_LIMIT);
|
|
15093
|
-
return finish([], {
|
|
15153
|
+
return finish([], {
|
|
15154
|
+
count: result.paths.length,
|
|
15155
|
+
resourceReachability: result,
|
|
15156
|
+
});
|
|
15094
15157
|
}
|
|
15095
15158
|
case "flow_analysis": {
|
|
15096
15159
|
// C8 deliberately does not persist a PDG. This is a bounded, local
|