knodin 0.8.4 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/bin/cli.js +262 -20
  2. package/dist/src/cli-model.js +26 -0
  3. package/dist/src/engine/candidate-database.js +167 -0
  4. package/dist/src/engine/embeddings.js +8 -5
  5. package/dist/src/engine/index.js +930 -262
  6. package/dist/src/engine/prune.js +8 -0
  7. package/dist/src/file-metrics.js +153 -0
  8. package/dist/src/repair-lease.js +8 -5
  9. package/dist/src/response-budget.js +2 -1
  10. package/dist/src/shared-index/artifact.js +65 -0
  11. package/dist/src/shared-index/cache.js +267 -0
  12. package/dist/src/shared-index/compatibility.js +70 -0
  13. package/dist/src/shared-index/config.js +224 -0
  14. package/dist/src/shared-index/contract.js +117 -0
  15. package/dist/src/shared-index/manifest.js +71 -0
  16. package/dist/src/shared-index/opportunistic.js +37 -0
  17. package/dist/src/shared-index/overlay.js +98 -0
  18. package/dist/src/shared-index/provenance.js +149 -0
  19. package/dist/src/shared-index/publisher.js +198 -0
  20. package/dist/src/shared-index/restore.js +209 -0
  21. package/dist/src/shared-index/s3-client.js +124 -0
  22. package/dist/src/shared-index/schemas.js +86 -0
  23. package/dist/src/shared-index/selection.js +111 -0
  24. package/dist/src/tools/knodin-tools.js +70 -3
  25. package/docs/CLI.md +39 -0
  26. package/docs/MCP.md +12 -0
  27. package/docs/SHARED-INDEX-CONTRACT.md +132 -0
  28. package/docs/releases/0.8.5.md +25 -0
  29. package/package.json +11 -3
  30. package/schemas/shared-index-branch-pointer-v1.schema.json +66 -0
  31. package/schemas/shared-index-config-v1.schema.json +97 -0
  32. package/schemas/shared-index-manifest-v1.schema.json +104 -0
  33. package/schemas/shared-index-provenance-v1.schema.json +100 -0
package/dist/bin/cli.js CHANGED
@@ -365,7 +365,7 @@ async function runRemoteCommand(args) {
365
365
  * empty result this whole feature exists to avoid.
366
366
  */
367
367
  async function indexMirror(source, deferSemantic) {
368
- const engine = createEngine();
368
+ const engine = createEngine({ watcher: "disabled" });
369
369
  try {
370
370
  await engine.index(source, undefined, true, { skipEmbeddings: true });
371
371
  const structural = await engine.status(source, { audit: "deep" });
@@ -420,7 +420,8 @@ function formatStatusHuman(result) {
420
420
  const freshnessLine = `Freshness: ${result.freshness.state}; indexed ${head(result.freshness.indexedHead)}, ` +
421
421
  `current ${head(result.freshness.currentHead)} (${result.freshness.commitRelation}${distance}); ` +
422
422
  `${result.freshness.workingTree.pendingPaths ?? "unknown"} pending path(s).\n` +
423
- `Last successful refresh: ${result.freshness.lastSuccessfulRefresh ?? "never"}.\n`;
423
+ `Last successful refresh: ${result.freshness.lastSuccessfulRefresh ?? "never"}.\n` +
424
+ `Freshness mechanism: ${result.freshnessMechanism.strategy}; watcher ${result.freshnessMechanism.watcher}${result.freshnessMechanism.diagnostic ? ` (${result.freshnessMechanism.diagnostic})` : ""}.\n`;
424
425
  if (result.status === "healthy")
425
426
  return `Graph content is healthy: ${coverage} (knodin ${result.version}; ${result.verification.mode}).\n${freshnessLine}${lifecycleLine}${integrationLine}`;
426
427
  if (result.status === "stale")
@@ -634,7 +635,7 @@ async function main() {
634
635
  }, 50);
635
636
  interval.unref();
636
637
  process.send?.({ type: "rss", rssBytes: process.memoryUsage().rss });
637
- const engine = createEngine();
638
+ const engine = createEngine({ watcher: "disabled" });
638
639
  try {
639
640
  const systemConfig = loadSystemConfiguration(repository);
640
641
  const summary = await initializeRepositories([repository], {
@@ -689,7 +690,11 @@ async function main() {
689
690
  const runtimeCommand = resolveCliRuntimeCommand(process);
690
691
  // Internal machine-to-machine commands stay JSON regardless of TTY state.
691
692
  // when an older installed hook predates the explicit --json argument.
692
- const jsonOutput = invocation.options.json === true || cmd === "hook-refresh" || cmd === "agent-event";
693
+ const jsonOutput = invocation.options.json === true ||
694
+ invocation.options.format === "json" ||
695
+ cmd === "hook-refresh" ||
696
+ cmd === "agent-event" ||
697
+ (cmd === "shared" && rawRest.includes("--jsonl"));
693
698
  // `--json` is a shared output flag. Repair owns its richer --json/--jsonl
694
699
  // parser; all other commands receive their original arguments minus it.
695
700
  const rest = cmd === "repair" ? rawRest : rawRest.filter((argument) => argument !== "--json");
@@ -756,7 +761,7 @@ async function main() {
756
761
  if (!plan.json) {
757
762
  process.stderr.write("warning: `knodin fleet init` is deprecated; use `knodin repos init --linked-worktrees=skip|include` (alias retained for two minor releases)\n");
758
763
  }
759
- const engine = createEngine();
764
+ const engine = createEngine({ watcher: "disabled" });
760
765
  const systemConfig = loadSystemConfiguration(plan.roots[0] ?? process.cwd());
761
766
  const memoryLimitBytes = configuredRepositoryInitMemoryLimitBytes(systemConfig);
762
767
  const summary = await initializeRepositories(plan.roots, {
@@ -793,7 +798,7 @@ async function main() {
793
798
  depth: plan.depth,
794
799
  linkedWorktrees: plan.linkedWorktrees,
795
800
  });
796
- const engine = createEngine();
801
+ const engine = createEngine({ watcher: "disabled" });
797
802
  const systemConfig = loadSystemConfiguration(plan.roots[0] ?? process.cwd());
798
803
  const repositories = [];
799
804
  const discoveryRecords = plan.signals
@@ -860,7 +865,7 @@ async function main() {
860
865
  process.exitCode = summary.exitCode;
861
866
  return;
862
867
  }
863
- const engine = createEngine();
868
+ const engine = createEngine({ watcher: "disabled" });
864
869
  const summary = await initializeRepositories(plan.roots, {
865
870
  command: runtimeCommand,
866
871
  depth: plan.depth,
@@ -898,7 +903,7 @@ async function main() {
898
903
  return;
899
904
  }
900
905
  if (plan.command === "search") {
901
- const engine = createEngine();
906
+ const engine = createEngine({ watcher: "disabled" });
902
907
  const systemConfig = loadSystemConfiguration(plan.roots[0] ?? process.cwd());
903
908
  const output = await searchRepositories(plan.roots, plan.query ?? "", {
904
909
  depth: plan.depth,
@@ -927,7 +932,7 @@ async function main() {
927
932
  depth: plan.depth,
928
933
  linkedWorktrees: plan.linkedWorktrees,
929
934
  });
930
- const engine = createEngine();
935
+ const engine = createEngine({ watcher: "disabled" });
931
936
  const systemConfig = loadSystemConfiguration(plan.roots[0] ?? process.cwd());
932
937
  const repositories = [];
933
938
  for (const repository of discovery.repositories) {
@@ -1167,7 +1172,7 @@ async function main() {
1167
1172
  let compressionResult;
1168
1173
  let diagnosisUnavailable = false;
1169
1174
  if (action === "diagnose") {
1170
- const diagnosisEngine = createEngine();
1175
+ const diagnosisEngine = createEngine({ watcher: "disabled" });
1171
1176
  try {
1172
1177
  const health = await inspectGraphQueryHealth(repo, (target) => diagnosisEngine.status(target, { audit: "cached" }));
1173
1178
  if (health.available) {
@@ -1259,7 +1264,13 @@ async function main() {
1259
1264
  process.stdout.write(formatGenericHuman("compress delete", compressionResult));
1260
1265
  return;
1261
1266
  }
1262
- const engine = createEngine();
1267
+ const engine = createEngine({ watcher: "disabled" });
1268
+ const opportunisticSharedRestore = async () => {
1269
+ if (!fs.existsSync(path.join(repo, ".knodin", "shared-index.yaml")))
1270
+ return null;
1271
+ const { attemptOpportunisticSharedRestore } = await import("../src/shared-index/opportunistic.js");
1272
+ return attemptOpportunisticSharedRestore(repo, engine);
1273
+ };
1263
1274
  let result;
1264
1275
  let repairOutput;
1265
1276
  let repairWasPlan = false;
@@ -1281,6 +1292,213 @@ async function main() {
1281
1292
  return decorateGraphQueryResult(value, verified.state, verified.graph.freshness);
1282
1293
  };
1283
1294
  switch (cmd) {
1295
+ case "shared": {
1296
+ const [cacheModule, compatibilityModule, configModule, manifestModule, provenanceModule, publisherModule, restoreModule, s3Module, selectionModule,] = await Promise.all([
1297
+ import("../src/shared-index/cache.js"),
1298
+ import("../src/shared-index/compatibility.js"),
1299
+ import("../src/shared-index/config.js"),
1300
+ import("../src/shared-index/manifest.js"),
1301
+ import("../src/shared-index/provenance.js"),
1302
+ import("../src/shared-index/publisher.js"),
1303
+ import("../src/shared-index/restore.js"),
1304
+ import("../src/shared-index/s3-client.js"),
1305
+ import("../src/shared-index/selection.js"),
1306
+ ]);
1307
+ const { SharedIndexCache } = cacheModule;
1308
+ const { currentSharedIndexCompatibility } = compatibilityModule;
1309
+ const { loadSharedIndexConfig, publicKeyFingerprint, writeSharedIndexConfig } = configModule;
1310
+ const { branchPointerKey } = manifestModule;
1311
+ const { readSharedIndexProvenance, readSharedRestoreAttempt } = provenanceModule;
1312
+ const { createSharedPublisherBundle, finalizeSharedPublisherBundle, guardPublisherPointer, prepareSharedPublisherDatabase, } = publisherModule;
1313
+ const { restoreSharedIndex } = restoreModule;
1314
+ const { createSharedObjectStore, SharedIndexError } = s3Module;
1315
+ const { configuredBranchCandidates } = selectionModule;
1316
+ const action = invocation.commandPath[1];
1317
+ if (action === "configure") {
1318
+ const existing = loadSharedIndexConfig(repo);
1319
+ if (invocation.options.disable === true) {
1320
+ if (existing.state !== "configured")
1321
+ throw new Error("knodin shared configure --disable requires a valid existing configuration");
1322
+ writeSharedIndexConfig(repo, { ...existing.config, enabled: false });
1323
+ result = { state: "configured", config: { ...existing.config, enabled: false } };
1324
+ break;
1325
+ }
1326
+ const required = (name) => {
1327
+ const value = invocation.options[name];
1328
+ if (typeof value !== "string" || !value)
1329
+ throw new Error(`knodin shared configure requires --${name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`);
1330
+ return value;
1331
+ };
1332
+ const publicKeyPath = path.resolve(process.cwd(), required("publicKey"));
1333
+ const publicKeyPem = fs.readFileSync(publicKeyPath, "utf8");
1334
+ const branches = Array.isArray(invocation.options.branch)
1335
+ ? invocation.options.branch.filter((value) => typeof value === "string")
1336
+ : [];
1337
+ if (branches.length === 0)
1338
+ throw new Error("knodin shared configure requires --branch");
1339
+ const config = {
1340
+ schemaVersion: 1,
1341
+ enabled: true,
1342
+ registrationId: required("registrationId"),
1343
+ aws: { region: required("region"), bucket: required("bucket") },
1344
+ repository: {
1345
+ host: required("host"),
1346
+ owner: required("owner"),
1347
+ name: required("name"),
1348
+ },
1349
+ branches,
1350
+ signing: {
1351
+ algorithm: "RSASSA_PSS_SHA_256",
1352
+ keyId: required("keyId"),
1353
+ publicKeySha256: publicKeyFingerprint(publicKeyPem),
1354
+ publicKeyPem,
1355
+ },
1356
+ };
1357
+ const target = writeSharedIndexConfig(repo, config);
1358
+ result = { state: "configured", path: target, config: loadSharedIndexConfig(repo) };
1359
+ break;
1360
+ }
1361
+ if (action === "publisher-metadata") {
1362
+ const configuration = loadSharedIndexConfig(repo);
1363
+ if (configuration.state !== "configured")
1364
+ throw new Error("knodin shared publisher-metadata requires valid reviewed configuration");
1365
+ result = prepareSharedPublisherDatabase(repo);
1366
+ break;
1367
+ }
1368
+ if (action === "publisher-bundle") {
1369
+ const configuration = loadSharedIndexConfig(repo);
1370
+ if (configuration.state !== "configured")
1371
+ throw new Error("knodin shared publisher-bundle requires valid reviewed configuration");
1372
+ const branch = invocation.positionals[0];
1373
+ const output = invocation.options.output;
1374
+ if (!branch || typeof output !== "string")
1375
+ throw new Error("knodin shared publisher-bundle requires <branch> --output <path>");
1376
+ result = await createSharedPublisherBundle({
1377
+ repo,
1378
+ config: configuration.config,
1379
+ branch,
1380
+ outputDirectory: path.resolve(process.cwd(), output),
1381
+ });
1382
+ break;
1383
+ }
1384
+ if (action === "publisher-finalize") {
1385
+ const configuration = loadSharedIndexConfig(repo);
1386
+ if (configuration.state !== "configured")
1387
+ throw new Error("knodin shared publisher-finalize requires valid reviewed configuration");
1388
+ const bundlePath = invocation.positionals[0];
1389
+ if (!bundlePath)
1390
+ throw new Error("knodin shared publisher-finalize requires <bundle>");
1391
+ const bundle = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), bundlePath), "utf8"));
1392
+ const pointer = finalizeSharedPublisherBundle({ config: configuration.config, bundle });
1393
+ result = { bundle, pointer: JSON.parse(pointer.toString("utf8")) };
1394
+ break;
1395
+ }
1396
+ if (action === "publisher-guard") {
1397
+ const configuration = loadSharedIndexConfig(repo);
1398
+ if (configuration.state !== "configured")
1399
+ throw new Error("knodin shared publisher-guard requires valid reviewed configuration");
1400
+ const [pointerPath, branch, commit] = invocation.positionals;
1401
+ if (!pointerPath || !branch || !commit)
1402
+ throw new Error("knodin shared publisher-guard requires <pointer> <branch> <commit>");
1403
+ guardPublisherPointer({
1404
+ repo,
1405
+ config: configuration.config,
1406
+ branch,
1407
+ newCommit: commit,
1408
+ existingPointerBytes: fs.readFileSync(path.resolve(process.cwd(), pointerPath)),
1409
+ });
1410
+ result = { allowed: true, branch, commit };
1411
+ break;
1412
+ }
1413
+ if (action === "status") {
1414
+ const configuration = loadSharedIndexConfig(repo);
1415
+ const graph = await engine.status(repo, { audit: "cached" });
1416
+ let credentialProbe = "not-requested";
1417
+ if (invocation.options.probe === true) {
1418
+ if (configuration.state !== "configured" || !configuration.config.enabled) {
1419
+ credentialProbe = { state: "unavailable", category: "configuration" };
1420
+ }
1421
+ else {
1422
+ const store = createSharedObjectStore(configuration.config);
1423
+ try {
1424
+ const current = spawnSync(gitExecutable(), ["symbolic-ref", "--quiet", "--short", "HEAD"], { cwd: repo, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).stdout.trim();
1425
+ const branch = configuredBranchCandidates(configuration.config, current || undefined)[0];
1426
+ if (!store || !branch)
1427
+ throw new SharedIndexError("configuration", "shared-index probe has no configured local branch candidate");
1428
+ await store.head(branchPointerKey(configuration.config, branch));
1429
+ credentialProbe = { state: "available" };
1430
+ }
1431
+ catch (error) {
1432
+ credentialProbe = {
1433
+ state: "unavailable",
1434
+ category: error instanceof SharedIndexError ? error.category : "unavailable",
1435
+ };
1436
+ }
1437
+ }
1438
+ }
1439
+ result = {
1440
+ configuration,
1441
+ provenance: readSharedIndexProvenance(repo),
1442
+ lastRestoreAttempt: readSharedRestoreAttempt(repo),
1443
+ credentialProbe,
1444
+ freshnessMechanism: graph.freshnessMechanism,
1445
+ };
1446
+ break;
1447
+ }
1448
+ if (action === "pull") {
1449
+ const configuration = loadSharedIndexConfig(repo);
1450
+ if (configuration.state !== "configured")
1451
+ throw new Error("knodin shared pull requires valid .knodin/shared-index.yaml");
1452
+ const store = createSharedObjectStore(configuration.config);
1453
+ if (!store)
1454
+ throw new Error("knodin shared pull: shared-index access is disabled");
1455
+ const progressMode = invocation.options.jsonl === true
1456
+ ? "jsonl"
1457
+ : typeof invocation.options.progress === "string"
1458
+ ? invocation.options.progress
1459
+ : process.stderr.isTTY
1460
+ ? "plain"
1461
+ : "none";
1462
+ if (!["plain", "jsonl", "none"].includes(progressMode))
1463
+ throw new Error("knodin shared pull: --progress must be plain, jsonl, or none");
1464
+ let lastHumanPhase = "";
1465
+ let lastHumanWrite = 0;
1466
+ result = await restoreSharedIndex({
1467
+ repo,
1468
+ config: configuration.config,
1469
+ store,
1470
+ cache: new SharedIndexCache(),
1471
+ engine,
1472
+ clientCompatibility: currentSharedIndexCompatibility(),
1473
+ onProgress: (event) => {
1474
+ if (progressMode === "jsonl")
1475
+ process.stdout.write(`${JSON.stringify({ type: "progress", ...event })}\n`);
1476
+ else if (progressMode === "plain") {
1477
+ const now = Date.now();
1478
+ if (event.phase !== lastHumanPhase ||
1479
+ event.phase === "completed" ||
1480
+ event.phase === "failed" ||
1481
+ now - lastHumanWrite >= 125) {
1482
+ lastHumanPhase = event.phase;
1483
+ lastHumanWrite = now;
1484
+ const count = event.completed !== undefined && event.total !== undefined
1485
+ ? ` ${event.completed.toLocaleString()}/${event.total.toLocaleString()}`
1486
+ : "";
1487
+ const rate = event.ratePerSecond
1488
+ ? ` · ${Math.round(event.ratePerSecond).toLocaleString()}/s`
1489
+ : "";
1490
+ const eta = event.etaSeconds ? ` · ETA ${Math.ceil(event.etaSeconds)}s` : "";
1491
+ process.stderr.write(`[shared:${event.phase}]${count}${rate}${eta} ${event.message}\n`);
1492
+ }
1493
+ }
1494
+ },
1495
+ });
1496
+ if (!result.restored)
1497
+ repairExitCode = 1;
1498
+ break;
1499
+ }
1500
+ throw new Error("knodin shared: unknown action");
1501
+ }
1284
1502
  case "agent-event": {
1285
1503
  agentEventOutput = true;
1286
1504
  const event = invocation.positionals[0];
@@ -1472,8 +1690,11 @@ async function main() {
1472
1690
  else {
1473
1691
  throw new Error("knodin hook-refresh: invalid lifecycle event");
1474
1692
  }
1475
- const indexed = await refreshFromGitEvent(repo, event, (target, files) => engine.index(target, files));
1476
- result = { indexed };
1693
+ const shared = await opportunisticSharedRestore();
1694
+ const indexed = shared?.restored
1695
+ ? []
1696
+ : await refreshFromGitEvent(repo, event, (target, files) => engine.index(target, files));
1697
+ result = { indexed, ...(shared ? { sharedRestore: shared } : {}) };
1477
1698
  break;
1478
1699
  }
1479
1700
  case "init": {
@@ -1486,6 +1707,11 @@ async function main() {
1486
1707
  let paths;
1487
1708
  try {
1488
1709
  try {
1710
+ if (!fs.existsSync(resolveDbPath(repo))) {
1711
+ const shared = await opportunisticSharedRestore();
1712
+ if (shared?.restored)
1713
+ process.stderr.write("[init:shared] Restored and verified shared snapshot\n");
1714
+ }
1489
1715
  paths = await initializeRepository(repo, {
1490
1716
  command: runtimeCommand,
1491
1717
  index: (target, options) => engine.index(target, undefined, false, options),
@@ -1917,7 +2143,7 @@ async function main() {
1917
2143
  ? ""
1918
2144
  : (rest[1] ?? "");
1919
2145
  if (!pattern) {
1920
- process.stderr.write("knodin query requires a <pattern> (lsp_diagnostics|lsp_definitions|lsp_declarations|lsp_implementations|callers_of|callees_of|imports_of|importers_of|import_cycles|file_summary|batch_outline|project_overview|shortest_path|cross_substrate_path|inheritors_of|structural_implementations_of|tests_for|impact|dead_code|large_functions|large_files|rename_preview|flows|flow_of|stats|traverse|feature_path|flow_analysis|resource_reachability|knowledge_gaps|surprising_connections|suggested_questions|architecture_overview|community|triggers_of|publishers_of|listeners_of|handlers_of|endpoints_for|consumers_of|children_of|federated_repos|mcp_tools|api_contract_mismatches)\n");
2146
+ process.stderr.write("knodin query requires a <pattern> (lsp_diagnostics|lsp_definitions|lsp_declarations|lsp_implementations|callers_of|callees_of|imports_of|importers_of|import_cycles|file_summary|file_metrics|batch_outline|project_overview|shortest_path|cross_substrate_path|inheritors_of|structural_implementations_of|tests_for|impact|dead_code|large_functions|large_files|rename_preview|flows|flow_of|stats|traverse|feature_path|flow_analysis|resource_reachability|knowledge_gaps|surprising_connections|suggested_questions|architecture_overview|community|triggers_of|publishers_of|listeners_of|handlers_of|endpoints_for|consumers_of|children_of|federated_repos|mcp_tools|api_contract_mismatches)\n");
1921
2147
  process.exit(1);
1922
2148
  }
1923
2149
  const directionValue = selectorValue("--direction");
@@ -1926,6 +2152,16 @@ async function main() {
1926
2152
  process.exit(1);
1927
2153
  }
1928
2154
  const facetsValue = selectorValue("--facets")?.split(",").filter(Boolean);
2155
+ const testScopeValue = selectorValue("--test-scope");
2156
+ const queryFormat = selectorValue("--format");
2157
+ if (queryFormat && (pattern !== "file_metrics" || queryFormat !== "json")) {
2158
+ process.stderr.write("knodin query: --format is supported only as file_metrics --format json\n");
2159
+ process.exit(1);
2160
+ }
2161
+ if (testScopeValue && !["all", "test", "production"].includes(testScopeValue)) {
2162
+ process.stderr.write("knodin query: --test-scope must be all, test, or production\n");
2163
+ process.exit(1);
2164
+ }
1929
2165
  if (facetsValue?.some((facet) => !["packages", "layers", "boundaries", "hotspots", "entryPoints", "languages"].includes(facet))) {
1930
2166
  process.stderr.write("knodin query: --facets contains an unknown architecture facet\n");
1931
2167
  process.exit(1);
@@ -1970,7 +2206,7 @@ async function main() {
1970
2206
  process.stderr.write("knodin query: --limit must be a positive integer\n");
1971
2207
  process.exit(1);
1972
2208
  }
1973
- const queryHealth = pattern.startsWith("lsp_")
2209
+ const queryHealth = pattern.startsWith("lsp_") || pattern === "file_metrics"
1974
2210
  ? null
1975
2211
  : await inspectGraphQueryHealth(repo, async (target) => attachLifecycleHealth(target, await engine.status(target, { audit: "cached" })));
1976
2212
  if (queryHealth && !queryHealth.available) {
@@ -1996,6 +2232,10 @@ async function main() {
1996
2232
  }
1997
2233
  : undefined, {
1998
2234
  minLines: selectorValue("--min-lines") ? Number(selectorValue("--min-lines")) : undefined,
2235
+ base: pattern === "file_metrics" ? selectorValue("--base") : undefined,
2236
+ testScope: pattern === "file_metrics"
2237
+ ? testScopeValue
2238
+ : undefined,
1999
2239
  minComplexity: selectorValue("--min-complexity")
2000
2240
  ? Number(selectorValue("--min-complexity"))
2001
2241
  : undefined,
@@ -2020,6 +2260,8 @@ async function main() {
2020
2260
  : selectorValue("--relations")?.split(",").filter(Boolean),
2021
2261
  detailLevel,
2022
2262
  });
2263
+ if (pattern === "file_metrics")
2264
+ result = result.fileMetrics;
2023
2265
  if (pattern === "impact" || pattern === "dead_code") {
2024
2266
  const config = await enrichSystemRelationships(loadSystemConfiguration(repo));
2025
2267
  result = incorporateSystemQueryEvidence(config, repo, pattern, target, result);
@@ -2220,11 +2462,10 @@ async function main() {
2220
2462
  }
2221
2463
  if (statusWasWatched)
2222
2464
  return;
2223
- const boundedResult = applyResponseBudget(result, cmd, responseBudget, {
2224
- bytes: 65_536,
2225
- tokens: 16_384,
2226
- items: 100,
2227
- });
2465
+ const fileMetricsOutput = cmd === "query" && rest[0] === "file_metrics";
2466
+ const boundedResult = applyResponseBudget(result, cmd, responseBudget, fileMetricsOutput
2467
+ ? { bytes: 16_777_216, tokens: 4_194_304, items: 100_000 }
2468
+ : { bytes: 65_536, tokens: 16_384, items: 100 });
2228
2469
  const finalExitCode = Math.max(Number(process.exitCode ?? 0), repairExitCode);
2229
2470
  if (cmd === "init" && !jsonOutput) {
2230
2471
  process.stdout.write(formatInitHuman(boundedResult));
@@ -2336,6 +2577,7 @@ catch (err) {
2336
2577
  "system",
2337
2578
  "repos",
2338
2579
  "remote",
2580
+ "shared",
2339
2581
  "update",
2340
2582
  ]);
2341
2583
  const diagnostic = recordDiagnosticFailure(candidate, {
@@ -64,6 +64,28 @@ function addRemoteCommands(program, capture) {
64
64
  leaf(remote, "remove <identity>", "delete a mirror's clone, graph, and registry entry", capture);
65
65
  leaf(remote, "refresh <identity>", "fetch the mirror again and re-index it", capture);
66
66
  }
67
+ function addSharedIndexCommands(program, capture) {
68
+ const shared = program.command("shared").description("manage verified shared-index snapshots");
69
+ leaf(shared, "status", "inspect shared-index configuration and provenance without network", capture).option("--probe", "explicitly probe S3 authentication and pointer access");
70
+ leaf(shared, "pull", "discover, verify, reconcile, audit, and promote a shared snapshot", capture)
71
+ .option("--jsonl", "stream lossless phase events as JSONL")
72
+ .option("--progress <mode>", "plain, jsonl, or none");
73
+ leaf(shared, "configure", "write reviewed repository trust configuration", capture)
74
+ .option("--disable", "disable automatic shared-index access without deleting graph or cache")
75
+ .option("--registration-id <id>", "publisher registration identifier")
76
+ .option("--region <region>", "AWS region")
77
+ .option("--bucket <bucket>", "S3 bucket")
78
+ .option("--host <host>", "canonical Git host")
79
+ .option("--owner <owner>", "repository owner")
80
+ .option("--name <name>", "repository name")
81
+ .addOption(option("--branch <pattern>", "exact branch or terminal slash-star pattern", "collect"))
82
+ .option("--key-id <id>", "reviewed signing key identifier")
83
+ .option("--public-key <path>", "PEM public-key file (credentials are never accepted)");
84
+ leaf(shared, "publisher-metadata", "stamp and emit the exact client-derived publisher compatibility metadata", capture);
85
+ leaf(shared, "publisher-bundle <branch>", "stream a graph and canonical manifest bundle", capture).requiredOption("--output <path>", "new or empty private output directory");
86
+ leaf(shared, "publisher-finalize <bundle>", "verify a detached signature and emit the canonical branch pointer", capture);
87
+ leaf(shared, "publisher-guard <pointer> <branch> <commit>", "refuse a backward or diverged branch-pointer update", capture);
88
+ }
67
89
  function addRepositoryCommands(program, capture) {
68
90
  const repos = program.command("repos").description("manage a portfolio of repositories");
69
91
  for (const action of ["discover", "init", "status", "doctor"]) {
@@ -142,6 +164,9 @@ function addGraphCommands(program, capture) {
142
164
  .addOption(option("--min-complexity <count>", "minimum complexity", "integer"))
143
165
  .option("--kinds <values>", "comma-separated symbol kinds")
144
166
  .option("--path <prefix>", "repo-relative path prefix")
167
+ .option("--base <ref>", "compare file_metrics with the merge-base of this Git ref")
168
+ .option("--test-scope <scope>", "file_metrics scope: all, test, or production")
169
+ .option("--format <format>", "file_metrics machine output format; currently json")
145
170
  .option("--variable <name>", "flow-analysis variable")
146
171
  .option("--facets <values>", "comma-separated architecture facets")
147
172
  .addOption(option("--top <count>", "maximum ranked results", "integer"))
@@ -262,6 +287,7 @@ function createCliProgram(capture = () => { }) {
262
287
  leaf(program, "refresh-artifacts [event]", "refresh external graph artifacts", capture);
263
288
  addRepositoryCommands(program, capture);
264
289
  addRemoteCommands(program, capture);
290
+ addSharedIndexCommands(program, capture);
265
291
  addGraphCommands(program, capture);
266
292
  addArtifactCommands(program, capture);
267
293
  const system = program.command("system").description("inspect declared multi-repository systems");
@@ -0,0 +1,167 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { resolveDbPath, resolveStateDir } from "./state-paths.js";
5
+ const PROMOTION_SCHEMA_VERSION = 1;
6
+ const CANDIDATES_DIRECTORY = "candidates";
7
+ const PROMOTION_MARKER = "promotion.json";
8
+ function inside(parent, child) {
9
+ const relative = path.relative(path.resolve(parent), path.resolve(child));
10
+ return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
11
+ }
12
+ function assertPromotionRecord(repo, value) {
13
+ if (!value || typeof value !== "object")
14
+ throw new Error("invalid promotion recovery record");
15
+ const record = value;
16
+ const stateDir = resolveStateDir(repo);
17
+ const activePath = resolveDbPath(repo);
18
+ if (record.schemaVersion !== PROMOTION_SCHEMA_VERSION ||
19
+ !record.candidatePath ||
20
+ !record.backupPath ||
21
+ record.activePath !== activePath ||
22
+ !inside(stateDir, record.candidatePath) ||
23
+ !inside(stateDir, record.backupPath) ||
24
+ !record.phase ||
25
+ !["prepared", "backed-up", "promoted"].includes(record.phase)) {
26
+ throw new Error("invalid promotion recovery record");
27
+ }
28
+ return record;
29
+ }
30
+ function fsyncFile(target) {
31
+ const descriptor = fs.openSync(target, "r");
32
+ try {
33
+ fs.fsyncSync(descriptor);
34
+ }
35
+ finally {
36
+ fs.closeSync(descriptor);
37
+ }
38
+ }
39
+ function fsyncDirectory(target) {
40
+ try {
41
+ const descriptor = fs.openSync(target, "r");
42
+ try {
43
+ fs.fsyncSync(descriptor);
44
+ }
45
+ finally {
46
+ fs.closeSync(descriptor);
47
+ }
48
+ }
49
+ catch (error) {
50
+ // Some platforms do not support directory fsync. File fsync and same-filesystem
51
+ // rename remain the strongest available ordering guarantee there.
52
+ if (process.platform !== "win32")
53
+ throw error;
54
+ }
55
+ }
56
+ function atomicRecord(target, record) {
57
+ const temporary = `${target}.${process.pid}.${crypto.randomUUID()}.tmp`;
58
+ fs.writeFileSync(temporary, `${JSON.stringify(record)}\n`, { mode: 0o600 });
59
+ fsyncFile(temporary);
60
+ fs.renameSync(temporary, target);
61
+ fsyncDirectory(path.dirname(target));
62
+ }
63
+ export function candidateRoot(repo) {
64
+ return path.join(resolveStateDir(repo), CANDIDATES_DIRECTORY);
65
+ }
66
+ export function promotionMarkerPath(repo) {
67
+ return path.join(resolveStateDir(repo), PROMOTION_MARKER);
68
+ }
69
+ export function allocateCandidate(repo) {
70
+ const resolvedRepo = path.resolve(repo);
71
+ const root = candidateRoot(resolvedRepo);
72
+ fs.mkdirSync(root, { recursive: true, mode: 0o700 });
73
+ const directory = fs.mkdtempSync(path.join(root, "candidate-"));
74
+ fs.chmodSync(directory, 0o700);
75
+ return {
76
+ schemaVersion: 1,
77
+ id: path.basename(directory),
78
+ repo: resolvedRepo,
79
+ databasePath: path.join(directory, "db.sqlite"),
80
+ createdAt: new Date().toISOString(),
81
+ };
82
+ }
83
+ export function assertCandidate(repo, candidate) {
84
+ const resolvedRepo = path.resolve(repo);
85
+ if (candidate.schemaVersion !== 1 ||
86
+ candidate.repo !== resolvedRepo ||
87
+ !inside(candidateRoot(resolvedRepo), candidate.databasePath) ||
88
+ path.basename(candidate.databasePath) !== "db.sqlite") {
89
+ throw new Error("candidate does not belong to this repository");
90
+ }
91
+ }
92
+ /** Restore the previous active graph after a process stopped between promotion renames. */
93
+ export function recoverInterruptedPromotion(repo) {
94
+ const marker = promotionMarkerPath(repo);
95
+ if (!fs.existsSync(marker))
96
+ return "none";
97
+ const record = assertPromotionRecord(repo, JSON.parse(fs.readFileSync(marker, "utf8")));
98
+ const activeExists = fs.existsSync(record.activePath);
99
+ const backupExists = fs.existsSync(record.backupPath);
100
+ let outcome;
101
+ if (!activeExists && backupExists) {
102
+ fs.renameSync(record.backupPath, record.activePath);
103
+ fsyncDirectory(path.dirname(record.activePath));
104
+ outcome = "recovered";
105
+ }
106
+ else if (activeExists) {
107
+ outcome = record.phase === "promoted" ? "completed" : "recovered";
108
+ }
109
+ else {
110
+ throw new Error("interrupted promotion has neither active nor backup database");
111
+ }
112
+ fs.rmSync(marker, { force: true });
113
+ fsyncDirectory(path.dirname(marker));
114
+ return outcome;
115
+ }
116
+ /** Same-filesystem, marker-backed promotion. Caller must close and audit both databases first. */
117
+ export function promoteCandidateFile(repo, candidate) {
118
+ assertCandidate(repo, candidate);
119
+ const activePath = resolveDbPath(repo);
120
+ const stateDir = resolveStateDir(repo);
121
+ if (!fs.existsSync(candidate.databasePath))
122
+ throw new Error("candidate database is missing");
123
+ for (const suffix of ["-wal", "-shm"])
124
+ if (fs.existsSync(`${candidate.databasePath}${suffix}`))
125
+ throw new Error("candidate database has uncheckpointed sidecar files");
126
+ fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 });
127
+ fsyncFile(candidate.databasePath);
128
+ const backupPath = path.join(stateDir, `db.sqlite.backup.${Date.now()}.${crypto.randomUUID().slice(0, 8)}`);
129
+ const marker = promotionMarkerPath(repo);
130
+ let record = {
131
+ schemaVersion: 1,
132
+ phase: "prepared",
133
+ candidatePath: candidate.databasePath,
134
+ activePath,
135
+ backupPath,
136
+ };
137
+ atomicRecord(marker, record);
138
+ try {
139
+ if (fs.existsSync(activePath))
140
+ fs.renameSync(activePath, backupPath);
141
+ record = { ...record, phase: "backed-up" };
142
+ atomicRecord(marker, record);
143
+ fs.renameSync(candidate.databasePath, activePath);
144
+ record = { ...record, phase: "promoted" };
145
+ atomicRecord(marker, record);
146
+ fsyncFile(activePath);
147
+ fsyncDirectory(stateDir);
148
+ fs.rmSync(marker, { force: true });
149
+ fsyncDirectory(stateDir);
150
+ return fs.existsSync(backupPath) ? backupPath : null;
151
+ }
152
+ catch (error) {
153
+ if (fs.existsSync(activePath) && fs.existsSync(backupPath)) {
154
+ fs.mkdirSync(path.dirname(candidate.databasePath), { recursive: true, mode: 0o700 });
155
+ fs.renameSync(activePath, candidate.databasePath);
156
+ }
157
+ if (!fs.existsSync(activePath) && fs.existsSync(backupPath))
158
+ fs.renameSync(backupPath, activePath);
159
+ fs.rmSync(marker, { force: true });
160
+ fsyncDirectory(stateDir);
161
+ throw error;
162
+ }
163
+ }
164
+ export function discardCandidateFiles(repo, candidate) {
165
+ assertCandidate(repo, candidate);
166
+ fs.rmSync(path.dirname(candidate.databasePath), { recursive: true, force: true });
167
+ }