knodin 0.8.3 → 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.
- package/README.md +7 -1
- package/dist/bin/cli.js +489 -78
- package/dist/src/agent-events.js +128 -0
- package/dist/src/agent-hooks.js +156 -0
- package/dist/src/cli-model.js +42 -1
- package/dist/src/engine/candidate-database.js +167 -0
- package/dist/src/engine/embeddings.js +8 -5
- package/dist/src/engine/index.js +930 -262
- package/dist/src/engine/prune.js +8 -0
- package/dist/src/file-metrics.js +153 -0
- package/dist/src/output-telemetry.js +8 -3
- package/dist/src/repair-lease.js +8 -5
- package/dist/src/response-budget.js +2 -1
- package/dist/src/session-telemetry.js +163 -0
- package/dist/src/shared-index/artifact.js +65 -0
- package/dist/src/shared-index/cache.js +267 -0
- package/dist/src/shared-index/compatibility.js +70 -0
- package/dist/src/shared-index/config.js +224 -0
- package/dist/src/shared-index/contract.js +117 -0
- package/dist/src/shared-index/manifest.js +71 -0
- package/dist/src/shared-index/opportunistic.js +37 -0
- package/dist/src/shared-index/overlay.js +98 -0
- package/dist/src/shared-index/provenance.js +149 -0
- package/dist/src/shared-index/publisher.js +198 -0
- package/dist/src/shared-index/restore.js +209 -0
- package/dist/src/shared-index/s3-client.js +124 -0
- package/dist/src/shared-index/schemas.js +86 -0
- package/dist/src/shared-index/selection.js +111 -0
- package/dist/src/tools/knodin-tools.js +70 -3
- package/docs/CLI.md +56 -0
- package/docs/INSTALLATION.md +11 -0
- package/docs/MCP.md +12 -0
- package/docs/SHARED-INDEX-CONTRACT.md +132 -0
- package/docs/TELEMETRY.md +19 -1
- package/docs/releases/0.8.4.md +22 -0
- package/docs/releases/0.8.5.md +25 -0
- package/package.json +12 -3
- package/roadmap/competitive-roadmap.md +20 -0
- package/schemas/shared-index-branch-pointer-v1.schema.json +66 -0
- package/schemas/shared-index-config-v1.schema.json +97 -0
- package/schemas/shared-index-manifest-v1.schema.json +104 -0
- package/schemas/shared-index-provenance-v1.schema.json +100 -0
package/dist/bin/cli.js
CHANGED
|
@@ -16,6 +16,8 @@ import fs from "node:fs";
|
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import readline from "node:readline/promises";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { canUseSessionContextCache, parseClaudeHookPayload, readSessionContextCache, recordClaudeLifecycleEvent, renderSessionContext, SESSION_CONTEXT_BUDGET_LINE, sessionStartHookOutput, writeSessionContextCache, } from "../src/agent-events.js";
|
|
20
|
+
import { inspectClaudeAgentHooks, installClaudeAgentHooks, uninstallClaudeAgentHooks, } from "../src/agent-hooks.js";
|
|
19
21
|
import { detectSupportedAgents, parseInitScope, } from "../src/agent-integration.js";
|
|
20
22
|
import { refreshExternalGraphArtifacts, writeArtifactRefreshRecord, } from "../src/artifact-refresh.js";
|
|
21
23
|
import { checkIndexed, extractPositionals, extractRepoFlag, parseReviewArgs, planIndex, resolveCliRuntimeCommand, resolveRepo, } from "../src/cli-args.js";
|
|
@@ -43,6 +45,7 @@ import { createRepairPlan, createRepairProgressRenderer, parseRepairCliArgs, res
|
|
|
43
45
|
import { runRepositoryInitializationProcess } from "../src/repository-init-process.js";
|
|
44
46
|
import { detectRepositorySignals, discoverRepositories, formatRepositoryHuman, initializeRepositories, inventoryRepository, parseFleetInitArgs, parseRepositoryCommandArgs, repositorySignalInspectionLimit, searchRepositories, withRepositorySignals, } from "../src/repository-management.js";
|
|
45
47
|
import { applyResponseBudget } from "../src/response-budget.js";
|
|
48
|
+
import { appendSessionEvent, clearSessionTelemetry, disableSessionTelemetry, enableSessionTelemetry, readSessionEvents, sessionTelemetryStatus, } from "../src/session-telemetry.js";
|
|
46
49
|
import { configuredRepositoryInitMemoryLimitBytes, enrichSystemRelationships, incorporateSystemQueryEvidence, indexModeForPath, loadSystemConfiguration, queryConfiguredSystem, systemMembershipsForPath, validateSystemHealth, } from "../src/system-config.js";
|
|
47
50
|
import { applyTrustedUpdate, checkTrustedUpdate, claimScheduledUpdateCheck, detectUpdateInstallMethod, explainTrustedUpdate, releaseScheduledUpdateCheck, rollbackTrustedUpdate, trustedUpdateStatus, } from "../src/update-policy.js";
|
|
48
51
|
import { KNODIN_VERSION } from "../src/version.js";
|
|
@@ -362,7 +365,7 @@ async function runRemoteCommand(args) {
|
|
|
362
365
|
* empty result this whole feature exists to avoid.
|
|
363
366
|
*/
|
|
364
367
|
async function indexMirror(source, deferSemantic) {
|
|
365
|
-
const engine = createEngine();
|
|
368
|
+
const engine = createEngine({ watcher: "disabled" });
|
|
366
369
|
try {
|
|
367
370
|
await engine.index(source, undefined, true, { skipEmbeddings: true });
|
|
368
371
|
const structural = await engine.status(source, { audit: "deep" });
|
|
@@ -417,7 +420,8 @@ function formatStatusHuman(result) {
|
|
|
417
420
|
const freshnessLine = `Freshness: ${result.freshness.state}; indexed ${head(result.freshness.indexedHead)}, ` +
|
|
418
421
|
`current ${head(result.freshness.currentHead)} (${result.freshness.commitRelation}${distance}); ` +
|
|
419
422
|
`${result.freshness.workingTree.pendingPaths ?? "unknown"} pending path(s).\n` +
|
|
420
|
-
`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`;
|
|
421
425
|
if (result.status === "healthy")
|
|
422
426
|
return `Graph content is healthy: ${coverage} (knodin ${result.version}; ${result.verification.mode}).\n${freshnessLine}${lifecycleLine}${integrationLine}`;
|
|
423
427
|
if (result.status === "stale")
|
|
@@ -631,7 +635,7 @@ async function main() {
|
|
|
631
635
|
}, 50);
|
|
632
636
|
interval.unref();
|
|
633
637
|
process.send?.({ type: "rss", rssBytes: process.memoryUsage().rss });
|
|
634
|
-
const engine = createEngine();
|
|
638
|
+
const engine = createEngine({ watcher: "disabled" });
|
|
635
639
|
try {
|
|
636
640
|
const systemConfig = loadSystemConfiguration(repository);
|
|
637
641
|
const summary = await initializeRepositories([repository], {
|
|
@@ -684,9 +688,13 @@ async function main() {
|
|
|
684
688
|
};
|
|
685
689
|
const repoFlag = selectorValue("--repo");
|
|
686
690
|
const runtimeCommand = resolveCliRuntimeCommand(process);
|
|
687
|
-
//
|
|
691
|
+
// Internal machine-to-machine commands stay JSON regardless of TTY state.
|
|
688
692
|
// when an older installed hook predates the explicit --json argument.
|
|
689
|
-
const jsonOutput = invocation.options.json === true ||
|
|
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"));
|
|
690
698
|
// `--json` is a shared output flag. Repair owns its richer --json/--jsonl
|
|
691
699
|
// parser; all other commands receive their original arguments minus it.
|
|
692
700
|
const rest = cmd === "repair" ? rawRest : rawRest.filter((argument) => argument !== "--json");
|
|
@@ -753,7 +761,7 @@ async function main() {
|
|
|
753
761
|
if (!plan.json) {
|
|
754
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");
|
|
755
763
|
}
|
|
756
|
-
const engine = createEngine();
|
|
764
|
+
const engine = createEngine({ watcher: "disabled" });
|
|
757
765
|
const systemConfig = loadSystemConfiguration(plan.roots[0] ?? process.cwd());
|
|
758
766
|
const memoryLimitBytes = configuredRepositoryInitMemoryLimitBytes(systemConfig);
|
|
759
767
|
const summary = await initializeRepositories(plan.roots, {
|
|
@@ -790,7 +798,7 @@ async function main() {
|
|
|
790
798
|
depth: plan.depth,
|
|
791
799
|
linkedWorktrees: plan.linkedWorktrees,
|
|
792
800
|
});
|
|
793
|
-
const engine = createEngine();
|
|
801
|
+
const engine = createEngine({ watcher: "disabled" });
|
|
794
802
|
const systemConfig = loadSystemConfiguration(plan.roots[0] ?? process.cwd());
|
|
795
803
|
const repositories = [];
|
|
796
804
|
const discoveryRecords = plan.signals
|
|
@@ -857,7 +865,7 @@ async function main() {
|
|
|
857
865
|
process.exitCode = summary.exitCode;
|
|
858
866
|
return;
|
|
859
867
|
}
|
|
860
|
-
const engine = createEngine();
|
|
868
|
+
const engine = createEngine({ watcher: "disabled" });
|
|
861
869
|
const summary = await initializeRepositories(plan.roots, {
|
|
862
870
|
command: runtimeCommand,
|
|
863
871
|
depth: plan.depth,
|
|
@@ -895,7 +903,7 @@ async function main() {
|
|
|
895
903
|
return;
|
|
896
904
|
}
|
|
897
905
|
if (plan.command === "search") {
|
|
898
|
-
const engine = createEngine();
|
|
906
|
+
const engine = createEngine({ watcher: "disabled" });
|
|
899
907
|
const systemConfig = loadSystemConfiguration(plan.roots[0] ?? process.cwd());
|
|
900
908
|
const output = await searchRepositories(plan.roots, plan.query ?? "", {
|
|
901
909
|
depth: plan.depth,
|
|
@@ -924,7 +932,7 @@ async function main() {
|
|
|
924
932
|
depth: plan.depth,
|
|
925
933
|
linkedWorktrees: plan.linkedWorktrees,
|
|
926
934
|
});
|
|
927
|
-
const engine = createEngine();
|
|
935
|
+
const engine = createEngine({ watcher: "disabled" });
|
|
928
936
|
const systemConfig = loadSystemConfiguration(plan.roots[0] ?? process.cwd());
|
|
929
937
|
const repositories = [];
|
|
930
938
|
for (const repository of discovery.repositories) {
|
|
@@ -1075,12 +1083,43 @@ async function main() {
|
|
|
1075
1083
|
background.unref();
|
|
1076
1084
|
}
|
|
1077
1085
|
}
|
|
1086
|
+
if (cmd === "agent-hooks") {
|
|
1087
|
+
const action = invocation.commandPath[1];
|
|
1088
|
+
const client = selectorValue("--client") ?? "claude";
|
|
1089
|
+
if (client !== "claude")
|
|
1090
|
+
throw new Error("knodin agent-hooks: only --client claude is supported");
|
|
1091
|
+
if (invocation.options.dryRun === true && action !== "install")
|
|
1092
|
+
throw new Error("knodin agent-hooks: --dry-run applies only to install");
|
|
1093
|
+
let hookResult;
|
|
1094
|
+
if (action === "install") {
|
|
1095
|
+
const quote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
1096
|
+
hookResult = installClaudeAgentHooks({
|
|
1097
|
+
command: runtimeCommand.map(quote).join(" "),
|
|
1098
|
+
dryRun: invocation.options.dryRun === true,
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
else if (action === "status")
|
|
1102
|
+
hookResult = inspectClaudeAgentHooks();
|
|
1103
|
+
else if (action === "uninstall")
|
|
1104
|
+
hookResult = uninstallClaudeAgentHooks();
|
|
1105
|
+
else
|
|
1106
|
+
throw new Error("knodin agent-hooks requires install, status, or uninstall");
|
|
1107
|
+
process.stdout.write(jsonOutput
|
|
1108
|
+
? `${JSON.stringify(hookResult)}\n`
|
|
1109
|
+
: formatGenericHuman("agent-hooks", hookResult));
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1078
1112
|
const resolved = resolveRepo(repoFlag, process.cwd());
|
|
1079
1113
|
if (!resolved.ok) {
|
|
1114
|
+
// Global agent hooks also run outside repositories and must fail open.
|
|
1115
|
+
if (cmd === "agent-event")
|
|
1116
|
+
return;
|
|
1080
1117
|
process.stderr.write(`${resolved.error}\n`);
|
|
1081
1118
|
process.exit(1);
|
|
1082
1119
|
}
|
|
1083
1120
|
const repo = resolved.repo;
|
|
1121
|
+
if (cmd === "agent-event" && !fs.existsSync(resolveDbPath(repo)))
|
|
1122
|
+
return;
|
|
1084
1123
|
if (cmd === "doctor") {
|
|
1085
1124
|
const gitProbe = spawnSync(gitExecutable(), ["rev-parse", "--is-inside-work-tree"], {
|
|
1086
1125
|
cwd: repo,
|
|
@@ -1133,7 +1172,7 @@ async function main() {
|
|
|
1133
1172
|
let compressionResult;
|
|
1134
1173
|
let diagnosisUnavailable = false;
|
|
1135
1174
|
if (action === "diagnose") {
|
|
1136
|
-
const diagnosisEngine = createEngine();
|
|
1175
|
+
const diagnosisEngine = createEngine({ watcher: "disabled" });
|
|
1137
1176
|
try {
|
|
1138
1177
|
const health = await inspectGraphQueryHealth(repo, (target) => diagnosisEngine.status(target, { audit: "cached" }));
|
|
1139
1178
|
if (health.available) {
|
|
@@ -1225,12 +1264,19 @@ async function main() {
|
|
|
1225
1264
|
process.stdout.write(formatGenericHuman("compress delete", compressionResult));
|
|
1226
1265
|
return;
|
|
1227
1266
|
}
|
|
1228
|
-
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
|
+
};
|
|
1229
1274
|
let result;
|
|
1230
1275
|
let repairOutput;
|
|
1231
1276
|
let repairWasPlan = false;
|
|
1232
1277
|
let repairExitCode = 0;
|
|
1233
1278
|
let statusWasWatched = false;
|
|
1279
|
+
let agentEventOutput = false;
|
|
1234
1280
|
const graphRead = async (run) => {
|
|
1235
1281
|
const health = await inspectGraphQueryHealth(repo, async (target) => attachLifecycleHealth(target, await engine.status(target, { audit: "cached" })));
|
|
1236
1282
|
if (!health.available) {
|
|
@@ -1246,6 +1292,310 @@ async function main() {
|
|
|
1246
1292
|
return decorateGraphQueryResult(value, verified.state, verified.graph.freshness);
|
|
1247
1293
|
};
|
|
1248
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
|
+
}
|
|
1502
|
+
case "agent-event": {
|
|
1503
|
+
agentEventOutput = true;
|
|
1504
|
+
const event = invocation.positionals[0];
|
|
1505
|
+
if (!event)
|
|
1506
|
+
throw new Error("knodin agent-event requires an event");
|
|
1507
|
+
let payload;
|
|
1508
|
+
try {
|
|
1509
|
+
payload = parseClaudeHookPayload(await readBoundedStdin(65_536));
|
|
1510
|
+
}
|
|
1511
|
+
catch {
|
|
1512
|
+
result = null;
|
|
1513
|
+
break;
|
|
1514
|
+
}
|
|
1515
|
+
if (event !== "session-start") {
|
|
1516
|
+
try {
|
|
1517
|
+
recordClaudeLifecycleEvent(repo, event, payload);
|
|
1518
|
+
}
|
|
1519
|
+
catch {
|
|
1520
|
+
// Optional telemetry must never interrupt the agent.
|
|
1521
|
+
}
|
|
1522
|
+
result = null;
|
|
1523
|
+
break;
|
|
1524
|
+
}
|
|
1525
|
+
const startedAt = performance.now();
|
|
1526
|
+
let status;
|
|
1527
|
+
try {
|
|
1528
|
+
status = await engine.status(repo, { audit: "cached" });
|
|
1529
|
+
}
|
|
1530
|
+
catch {
|
|
1531
|
+
if (payload.session_id)
|
|
1532
|
+
try {
|
|
1533
|
+
appendSessionEvent(repo, {
|
|
1534
|
+
event: "session_start",
|
|
1535
|
+
sessionId: payload.session_id,
|
|
1536
|
+
context: "unavailable",
|
|
1537
|
+
freshness: "unavailable",
|
|
1538
|
+
latencyMs: Math.round((performance.now() - startedAt) * 100) / 100,
|
|
1539
|
+
cacheHit: false,
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
catch {
|
|
1543
|
+
// Best-effort local recording remains fail-open.
|
|
1544
|
+
}
|
|
1545
|
+
result = sessionStartHookOutput(`## knodin session context\n${SESSION_CONTEXT_BUDGET_LINE}\nContext generation was unavailable. Run knodin status before relying on graph evidence.`);
|
|
1546
|
+
break;
|
|
1547
|
+
}
|
|
1548
|
+
const freshness = status.freshness?.state ?? "unknown";
|
|
1549
|
+
const cacheKey = JSON.stringify({
|
|
1550
|
+
schemaVersion: 1,
|
|
1551
|
+
head: status.freshness?.currentHead,
|
|
1552
|
+
fingerprint: status.freshness?.workingTree?.indexedFingerprint,
|
|
1553
|
+
generation: status.indexGeneration,
|
|
1554
|
+
budgets: [600, 8192, 12],
|
|
1555
|
+
});
|
|
1556
|
+
const cacheEligible = canUseSessionContextCache(status.status, freshness);
|
|
1557
|
+
let text = cacheEligible ? readSessionContextCache(repo, cacheKey) : null;
|
|
1558
|
+
let cacheHit = text !== null;
|
|
1559
|
+
let contextState = "delivered";
|
|
1560
|
+
if (!cacheEligible) {
|
|
1561
|
+
contextState = "degraded";
|
|
1562
|
+
text = `## knodin session context\n${SESSION_CONTEXT_BUDGET_LINE}\nGraph evidence is ${freshness}. Run knodin status and follow its repair or reindex guidance before relying on graph evidence.`;
|
|
1563
|
+
}
|
|
1564
|
+
else if (!text) {
|
|
1565
|
+
cacheHit = false;
|
|
1566
|
+
try {
|
|
1567
|
+
const context = await buildKnodinContext(engine, "Orient this coding session", repo, undefined, []);
|
|
1568
|
+
text = renderSessionContext(context, freshness);
|
|
1569
|
+
writeSessionContextCache(repo, cacheKey, text);
|
|
1570
|
+
}
|
|
1571
|
+
catch {
|
|
1572
|
+
contextState = "unavailable";
|
|
1573
|
+
text = `## knodin session context\n${SESSION_CONTEXT_BUDGET_LINE}\nContext generation was unavailable. Run knodin status, then call the connected knodin context operation when needed.`;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
if (payload.session_id)
|
|
1577
|
+
try {
|
|
1578
|
+
appendSessionEvent(repo, {
|
|
1579
|
+
event: "session_start",
|
|
1580
|
+
sessionId: payload.session_id,
|
|
1581
|
+
context: contextState,
|
|
1582
|
+
freshness: freshness === "fresh"
|
|
1583
|
+
? "fresh"
|
|
1584
|
+
: status.status === "repair-needed"
|
|
1585
|
+
? "repair-needed"
|
|
1586
|
+
: freshness === "unknown"
|
|
1587
|
+
? "unavailable"
|
|
1588
|
+
: "stale",
|
|
1589
|
+
latencyMs: Math.round((performance.now() - startedAt) * 100) / 100,
|
|
1590
|
+
cacheHit,
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
catch {
|
|
1594
|
+
// Best-effort local recording remains fail-open.
|
|
1595
|
+
}
|
|
1596
|
+
result = sessionStartHookOutput(text);
|
|
1597
|
+
break;
|
|
1598
|
+
}
|
|
1249
1599
|
case "doctor": {
|
|
1250
1600
|
const client = selectorValue("--client");
|
|
1251
1601
|
if (client !== undefined &&
|
|
@@ -1340,8 +1690,11 @@ async function main() {
|
|
|
1340
1690
|
else {
|
|
1341
1691
|
throw new Error("knodin hook-refresh: invalid lifecycle event");
|
|
1342
1692
|
}
|
|
1343
|
-
const
|
|
1344
|
-
|
|
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 } : {}) };
|
|
1345
1698
|
break;
|
|
1346
1699
|
}
|
|
1347
1700
|
case "init": {
|
|
@@ -1354,6 +1707,11 @@ async function main() {
|
|
|
1354
1707
|
let paths;
|
|
1355
1708
|
try {
|
|
1356
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
|
+
}
|
|
1357
1715
|
paths = await initializeRepository(repo, {
|
|
1358
1716
|
command: runtimeCommand,
|
|
1359
1717
|
index: (target, options) => engine.index(target, undefined, false, options),
|
|
@@ -1785,7 +2143,7 @@ async function main() {
|
|
|
1785
2143
|
? ""
|
|
1786
2144
|
: (rest[1] ?? "");
|
|
1787
2145
|
if (!pattern) {
|
|
1788
|
-
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");
|
|
1789
2147
|
process.exit(1);
|
|
1790
2148
|
}
|
|
1791
2149
|
const directionValue = selectorValue("--direction");
|
|
@@ -1794,6 +2152,16 @@ async function main() {
|
|
|
1794
2152
|
process.exit(1);
|
|
1795
2153
|
}
|
|
1796
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
|
+
}
|
|
1797
2165
|
if (facetsValue?.some((facet) => !["packages", "layers", "boundaries", "hotspots", "entryPoints", "languages"].includes(facet))) {
|
|
1798
2166
|
process.stderr.write("knodin query: --facets contains an unknown architecture facet\n");
|
|
1799
2167
|
process.exit(1);
|
|
@@ -1838,7 +2206,7 @@ async function main() {
|
|
|
1838
2206
|
process.stderr.write("knodin query: --limit must be a positive integer\n");
|
|
1839
2207
|
process.exit(1);
|
|
1840
2208
|
}
|
|
1841
|
-
const queryHealth = pattern.startsWith("lsp_")
|
|
2209
|
+
const queryHealth = pattern.startsWith("lsp_") || pattern === "file_metrics"
|
|
1842
2210
|
? null
|
|
1843
2211
|
: await inspectGraphQueryHealth(repo, async (target) => attachLifecycleHealth(target, await engine.status(target, { audit: "cached" })));
|
|
1844
2212
|
if (queryHealth && !queryHealth.available) {
|
|
@@ -1864,6 +2232,10 @@ async function main() {
|
|
|
1864
2232
|
}
|
|
1865
2233
|
: undefined, {
|
|
1866
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,
|
|
1867
2239
|
minComplexity: selectorValue("--min-complexity")
|
|
1868
2240
|
? Number(selectorValue("--min-complexity"))
|
|
1869
2241
|
: undefined,
|
|
@@ -1888,6 +2260,8 @@ async function main() {
|
|
|
1888
2260
|
: selectorValue("--relations")?.split(",").filter(Boolean),
|
|
1889
2261
|
detailLevel,
|
|
1890
2262
|
});
|
|
2263
|
+
if (pattern === "file_metrics")
|
|
2264
|
+
result = result.fileMetrics;
|
|
1891
2265
|
if (pattern === "impact" || pattern === "dead_code") {
|
|
1892
2266
|
const config = await enrichSystemRelationships(loadSystemConfiguration(repo));
|
|
1893
2267
|
result = incorporateSystemQueryEvidence(config, repo, pattern, target, result);
|
|
@@ -1973,16 +2347,23 @@ async function main() {
|
|
|
1973
2347
|
if (!Number.isInteger(retentionDays) || retentionDays < 1 || retentionDays > 3650)
|
|
1974
2348
|
throw new Error("knodin telemetry: --retention-days must be an integer from 1 to 3650");
|
|
1975
2349
|
const input = selectorValue("--input");
|
|
1976
|
-
if (action === "
|
|
1977
|
-
result =
|
|
2350
|
+
if (action === "enable")
|
|
2351
|
+
result = enableSessionTelemetry(repo);
|
|
2352
|
+
else if (action === "disable")
|
|
2353
|
+
result = disableSessionTelemetry(repo);
|
|
2354
|
+
else if (action === "status")
|
|
2355
|
+
result = {
|
|
2356
|
+
...telemetryStatus(repo, input, retentionDays),
|
|
2357
|
+
session: sessionTelemetryStatus(repo, retentionDays),
|
|
2358
|
+
};
|
|
1978
2359
|
else if (action === "report")
|
|
1979
|
-
result = writeTelemetryReport(repo, readTelemetryRecords(repo, input, retentionDays), selectorValue("--output"));
|
|
2360
|
+
result = writeTelemetryReport(repo, readTelemetryRecords(repo, input, retentionDays), selectorValue("--output"), readSessionEvents(repo, retentionDays));
|
|
1980
2361
|
else if (action === "export")
|
|
1981
|
-
result = exportTelemetry(repo, readTelemetryRecords(repo, input, retentionDays), selectorValue("--output"));
|
|
2362
|
+
result = exportTelemetry(repo, readTelemetryRecords(repo, input, retentionDays), selectorValue("--output"), readSessionEvents(repo, retentionDays));
|
|
1982
2363
|
else if (action === "clear")
|
|
1983
|
-
result = clearTelemetry(repo, input);
|
|
2364
|
+
result = { ...clearTelemetry(repo, input), session: clearSessionTelemetry(repo) };
|
|
1984
2365
|
else
|
|
1985
|
-
throw new Error("knodin telemetry requires status, report, export, or clear");
|
|
2366
|
+
throw new Error("knodin telemetry requires enable, disable, status, report, export, or clear");
|
|
1986
2367
|
break;
|
|
1987
2368
|
}
|
|
1988
2369
|
case "diagnostics": {
|
|
@@ -2074,13 +2455,17 @@ async function main() {
|
|
|
2074
2455
|
process.exit(1);
|
|
2075
2456
|
}
|
|
2076
2457
|
await engine.close();
|
|
2458
|
+
if (agentEventOutput) {
|
|
2459
|
+
if (result !== null && result !== undefined)
|
|
2460
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2077
2463
|
if (statusWasWatched)
|
|
2078
2464
|
return;
|
|
2079
|
-
const
|
|
2080
|
-
|
|
2081
|
-
tokens:
|
|
2082
|
-
items: 100
|
|
2083
|
-
});
|
|
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 });
|
|
2084
2469
|
const finalExitCode = Math.max(Number(process.exitCode ?? 0), repairExitCode);
|
|
2085
2470
|
if (cmd === "init" && !jsonOutput) {
|
|
2086
2471
|
process.stdout.write(formatInitHuman(boundedResult));
|
|
@@ -2128,55 +2513,81 @@ try {
|
|
|
2128
2513
|
}
|
|
2129
2514
|
catch (err) {
|
|
2130
2515
|
const argv = process.argv.slice(2);
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
if (argument
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
"
|
|
2147
|
-
"
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
"
|
|
2154
|
-
"
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2516
|
+
let invokedCommand;
|
|
2517
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
2518
|
+
const argument = argv[index];
|
|
2519
|
+
if (argument === "--repo") {
|
|
2520
|
+
index += 1;
|
|
2521
|
+
continue;
|
|
2522
|
+
}
|
|
2523
|
+
if (argument?.startsWith("--repo=") || argument === "--json")
|
|
2524
|
+
continue;
|
|
2525
|
+
if (!argument?.startsWith("-")) {
|
|
2526
|
+
invokedCommand = argument;
|
|
2527
|
+
break;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
if (invokedCommand === "agent-event") {
|
|
2531
|
+
const eventIndex = argv.indexOf("agent-event");
|
|
2532
|
+
if (argv[eventIndex + 1] === "session-start") {
|
|
2533
|
+
process.stdout.write(`${JSON.stringify(sessionStartHookOutput(`## knodin session context\n${SESSION_CONTEXT_BUDGET_LINE}\nContext generation was unavailable. Run knodin status before relying on graph evidence.`))}\n`);
|
|
2534
|
+
}
|
|
2535
|
+
process.exitCode = 0;
|
|
2536
|
+
}
|
|
2537
|
+
else {
|
|
2538
|
+
const repoIndex = argv.indexOf("--repo");
|
|
2539
|
+
const equalsRepo = argv.find((argument) => argument.startsWith("--repo="));
|
|
2540
|
+
const explicitRepo = repoIndex >= 0 ? argv[repoIndex + 1] : undefined;
|
|
2541
|
+
let candidate = process.cwd();
|
|
2542
|
+
if (explicitRepo)
|
|
2543
|
+
candidate = explicitRepo;
|
|
2544
|
+
else if (equalsRepo)
|
|
2545
|
+
candidate = equalsRepo.slice("--repo=".length);
|
|
2546
|
+
const command = argv.find((argument, index) => {
|
|
2547
|
+
if (argument.startsWith("-"))
|
|
2548
|
+
return false;
|
|
2549
|
+
return !(index > 0 && argv[index - 1] === "--repo") && argument !== candidate;
|
|
2550
|
+
});
|
|
2551
|
+
const knownCommands = new Set([
|
|
2552
|
+
"init",
|
|
2553
|
+
"configure",
|
|
2554
|
+
"agent-hooks",
|
|
2555
|
+
"agent-event",
|
|
2556
|
+
"index",
|
|
2557
|
+
"doctor",
|
|
2558
|
+
"status",
|
|
2559
|
+
"wait",
|
|
2560
|
+
"repair",
|
|
2561
|
+
"serve",
|
|
2562
|
+
"context",
|
|
2563
|
+
"explain",
|
|
2564
|
+
"review",
|
|
2565
|
+
"map",
|
|
2566
|
+
"search",
|
|
2567
|
+
"query",
|
|
2568
|
+
"rename",
|
|
2569
|
+
"wiki",
|
|
2570
|
+
"visualize",
|
|
2571
|
+
"pack",
|
|
2572
|
+
"compress",
|
|
2573
|
+
"prs",
|
|
2574
|
+
"worktrees",
|
|
2575
|
+
"telemetry",
|
|
2576
|
+
"diagnostics",
|
|
2577
|
+
"system",
|
|
2578
|
+
"repos",
|
|
2579
|
+
"remote",
|
|
2580
|
+
"shared",
|
|
2581
|
+
"update",
|
|
2582
|
+
]);
|
|
2583
|
+
const diagnostic = recordDiagnosticFailure(candidate, {
|
|
2584
|
+
surface: "cli",
|
|
2585
|
+
operation: command && knownCommands.has(command) ? command : "unknown",
|
|
2586
|
+
phase: "dispatch",
|
|
2587
|
+
error: err,
|
|
2588
|
+
});
|
|
2589
|
+
const correlation = diagnostic.recorded ? ` [diagnostic ${diagnostic.correlationId}]` : "";
|
|
2590
|
+
console.error(`${err instanceof Error ? err.message : String(err)}${correlation}`);
|
|
2591
|
+
process.exit(1);
|
|
2592
|
+
}
|
|
2182
2593
|
}
|