knodin 0.8.7 → 0.10.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.
Files changed (57) hide show
  1. package/dist/bin/cli.js +341 -91
  2. package/dist/fixtures/update-verification/config/application.xml +4 -0
  3. package/dist/fixtures/update-verification/dbt/manifest.json +17 -0
  4. package/dist/fixtures/update-verification/dbt/models/accounts.sql +1 -0
  5. package/dist/fixtures/update-verification/dbt/models/contacts.sql +1 -0
  6. package/dist/fixtures/update-verification/fixture.json +147 -0
  7. package/dist/fixtures/update-verification/force-app/main/default/classes/UpdateFixture.cls +14 -0
  8. package/dist/fixtures/update-verification/force-app/main/default/flows/Update_Verification.flow-meta.xml +9 -0
  9. package/dist/fixtures/update-verification/package.json +5 -0
  10. package/dist/fixtures/update-verification/prisma/schema.prisma +10 -0
  11. package/dist/fixtures/update-verification/sql/functions.sql +16 -0
  12. package/dist/fixtures/update-verification/src/CsharpFixture.cs +6 -0
  13. package/dist/fixtures/update-verification/src/JavaFixture.java +9 -0
  14. package/dist/fixtures/update-verification/src/javascript.js +7 -0
  15. package/dist/fixtures/update-verification/src/python_fixture.py +6 -0
  16. package/dist/fixtures/update-verification/src/typescript.ts +7 -0
  17. package/dist/fixtures/update-verification/terraform/main.tf +7 -0
  18. package/dist/fixtures/update-verification/terraform/modules/verified-child/main.tf +3 -0
  19. package/dist/src/agent-integration.js +26 -3
  20. package/dist/src/backup-retention.js +811 -0
  21. package/dist/src/cli-model.js +49 -8
  22. package/dist/src/docs-sections.js +1 -0
  23. package/dist/src/doctor.js +142 -12
  24. package/dist/src/engine/embeddings.js +8 -1
  25. package/dist/src/engine/index.js +1272 -645
  26. package/dist/src/engine/parse-pool-resources.js +96 -0
  27. package/dist/src/engine/parse-pool.js +352 -0
  28. package/dist/src/engine/parse-protocol.js +1 -0
  29. package/dist/src/engine/parse-worker.js +51 -0
  30. package/dist/src/engine/reflink-copy.js +23 -0
  31. package/dist/src/engine/seal-command.js +116 -0
  32. package/dist/src/engine/seal.js +270 -0
  33. package/dist/src/engine/sealed-open.js +116 -0
  34. package/dist/src/engine/sealed-query.js +49 -0
  35. package/dist/src/init.js +251 -183
  36. package/dist/src/manager-update.js +455 -0
  37. package/dist/src/release-preflight.js +106 -95
  38. package/dist/src/repair-lease.js +77 -8
  39. package/dist/src/response-budget.js +5 -1
  40. package/dist/src/server.js +18 -5
  41. package/dist/src/shared-index/compatibility.js +4 -2
  42. package/dist/src/shared-index/selection.js +1 -1
  43. package/dist/src/tools/knodin-tools.js +85 -13
  44. package/dist/src/update-ceremony.js +20 -24
  45. package/dist/src/update-coordination.js +158 -0
  46. package/dist/src/update-executor.js +355 -0
  47. package/dist/src/update-verifier.js +358 -0
  48. package/dist/src/worktree-seed.js +172 -0
  49. package/docs/BACKUP-RETENTION.md +54 -0
  50. package/docs/CLI.md +6 -0
  51. package/docs/DOCTOR-AND-UPDATES.md +44 -12
  52. package/docs/SHARED-INDEX-CONTRACT.md +9 -0
  53. package/docs/SIGNED-UPDATES.md +29 -22
  54. package/docs/releases/0.10.0.md +127 -0
  55. package/docs/releases/0.9.0.md +43 -0
  56. package/package.json +8 -1
  57. package/roadmap/competitive-roadmap.md +8 -0
package/dist/bin/cli.js CHANGED
@@ -14,12 +14,12 @@
14
14
  import { spawn, spawnSync } from "node:child_process";
15
15
  import fs from "node:fs";
16
16
  import path from "node:path";
17
- import readline from "node:readline/promises";
18
17
  import { fileURLToPath } from "node:url";
19
18
  import { canUseSessionContextCache, parseClaudeHookPayload, readSessionContextCache, recordClaudeLifecycleEvent, renderSessionContext, SESSION_CONTEXT_BUDGET_LINE, sessionStartHookOutput, writeSessionContextCache, } from "../src/agent-events.js";
20
19
  import { inspectClaudeAgentHooks, installClaudeAgentHooks, uninstallClaudeAgentHooks, } from "../src/agent-hooks.js";
21
20
  import { detectSupportedAgents, parseInitScope, } from "../src/agent-integration.js";
22
21
  import { refreshExternalGraphArtifacts, writeArtifactRefreshRecord, } from "../src/artifact-refresh.js";
22
+ import { formatBackupHuman, installBackupRetention, listBackups, maybeRunOpportunisticRetention, pruneBackups, removeBackupRetention, retentionDoctor, retentionStatus, runInstalledRetention, } from "../src/backup-retention.js";
23
23
  import { checkIndexed, extractPositionals, extractRepoFlag, parseReviewArgs, planIndex, resolveCliRuntimeCommand, resolveRepo, } from "../src/cli-args.js";
24
24
  import { helpCommandPath, parseCliInvocation, renderCliHelp } from "../src/cli-model.js";
25
25
  import { buildKnodinContext } from "../src/context.js";
@@ -27,20 +27,24 @@ import { exportContext, grepPackedArtifact, readPackedArtifact } from "../src/co
27
27
  import { clearDiagnostics, collectDiagnostics, diagnosticsStatus, disableDiagnostics, enableDiagnostics, inspectDiagnosticsBundle, persistDiagnosticsPreview, recordDiagnosticFailure, } from "../src/diagnostics.js";
28
28
  import { getDocSection, listDocTopics } from "../src/docs-sections.js";
29
29
  import { diagnoseInstallation } from "../src/doctor.js";
30
- import { createEngine, REPO_WIDE_QUERY_PATTERNS, } from "../src/engine/index.js";
30
+ import { createEngine, KNODIN_SCHEMA_VERSION, REPO_WIDE_QUERY_PATTERNS, } from "../src/engine/index.js";
31
+ import { runSeal } from "../src/engine/seal-command.js";
32
+ import { runSealedQuery } from "../src/engine/sealed-query.js";
31
33
  import { resolveDbPath } from "../src/engine/state-paths.js";
32
34
  import { diagnoseFailure, } from "../src/failure-diagnosis.js";
33
35
  import { gitExecutable } from "../src/git-executable.js";
34
36
  import { decorateGraphQueryResult, inspectGraphQueryHealth } from "../src/graph-query-health.js";
35
37
  import { createIndexActivityReporter } from "../src/index-activity.js";
36
- import { detectTrackedTeamIntegration, InitializationHealthError, initializeRepository, inspectRepositoryIntegrationStatus, readRepositoryIntegrationConfig, refreshFromGitEvent, } from "../src/init.js";
38
+ import { InitializationHealthError, initializeRepository, inspectRepositoryIntegrationStatus, readRepositoryIntegrationConfig, refreshFromGitEvent, } from "../src/init.js";
37
39
  import { createInitProgressRenderer } from "../src/init-progress.js";
38
40
  import { attachLifecycleHealth, attachRepairLifecycle } from "../src/lifecycle-health.js";
41
+ import { acknowledgeUpdateFailure, queryOwnerAvailability, readManagerUpdateState, readUpdateJournal, resolveManagerOwnership, setManualPin, unpinUpdate, updateAttention, writeManagerUpdateState, } from "../src/manager-update.js";
39
42
  import { addMirror, listMirrors, refreshMirror, removeMirror } from "../src/mirror.js";
40
43
  import { compressOutput, compressOutputFile, deleteOutputArtifact, readOutputArtifact, } from "../src/output-compression.js";
41
44
  import { clearTelemetry, exportTelemetry, readTelemetryRecords, telemetryStatus, writeTelemetryReport, } from "../src/output-telemetry.js";
42
45
  import { auditPullRequests } from "../src/pr-triage.js";
43
46
  import { deliverProgressiveEvidence, } from "../src/progressive-evidence.js";
47
+ import { acquireRepairLease } from "../src/repair-lease.js";
44
48
  import { createRepairPlan, createRepairProgressRenderer, parseRepairCliArgs, resolveRepairProgressMode, serializeRepairJsonlRecord, } from "../src/repair-progress.js";
45
49
  import { runRepositoryInitializationProcess } from "../src/repository-init-process.js";
46
50
  import { detectRepositorySignals, discoverRepositories, formatRepositoryHuman, initializeRepositories, inventoryRepository, parseFleetInitArgs, parseRepositoryCommandArgs, repositorySignalInspectionLimit, searchRepositories, withRepositorySignals, } from "../src/repository-management.js";
@@ -49,11 +53,14 @@ import { appendSessionEvent, clearSessionTelemetry, disableSessionTelemetry, ena
49
53
  import { inspectKnodinSkills, installKnodinSkills, KNODIN_SKILLS, removeKnodinSkills, } from "../src/skill-management.js";
50
54
  import { configuredRepositoryInitMemoryLimitBytes, enrichSystemRelationships, incorporateSystemQueryEvidence, indexModeForPath, loadSystemConfiguration, queryConfiguredSystem, systemMembershipsForPath, validateSystemHealth, } from "../src/system-config.js";
51
55
  import { applySystemPlan, planSystemImport, planSystemLink, planSystemRelate, planSystemUnlink, planSystemUnrelate, } from "../src/system-management.js";
56
+ import { coordinationStatus } from "../src/update-coordination.js";
57
+ import { certifyAutomaticUpdates, executeManagerUpdate, recoverInterruptedUpdate, } from "../src/update-executor.js";
52
58
  import { applyTrustedUpdate, checkTrustedUpdate, claimScheduledUpdateCheck, detectUpdateInstallMethod, explainTrustedUpdate, releaseScheduledUpdateCheck, rollbackTrustedUpdate, trustedUpdateStatus, } from "../src/update-policy.js";
53
59
  import { KNODIN_VERSION } from "../src/version.js";
54
60
  import { writeVisualization, } from "../src/visualization.js";
55
61
  import { waitForFresh } from "../src/wait-for-fresh.js";
56
62
  import { inspectWorktrees, reconcileWorktrees, removeManagedWorktree, } from "../src/worktree-lifecycle.js";
63
+ import { indexOrSeed } from "../src/worktree-seed.js";
57
64
  function explicitScope(args) {
58
65
  const index = args.indexOf("--scope");
59
66
  if (index >= 0) {
@@ -65,44 +72,6 @@ function explicitScope(args) {
65
72
  const equals = args.find((argument) => argument.startsWith("--scope="));
66
73
  return equals ? parseInitScope(equals.slice("--scope=".length)) : null;
67
74
  }
68
- async function chooseInitScope(args, repo) {
69
- const selected = explicitScope(args);
70
- if (selected)
71
- return selected;
72
- if (detectTrackedTeamIntegration(repo)) {
73
- process.stderr.write("[init:scope] Detected tracked knodin team integration; preserving team scope\n");
74
- return "team";
75
- }
76
- if (!process.stdin.isTTY || !process.stderr.isTTY)
77
- return "personal";
78
- const prompt = readline.createInterface({
79
- input: process.stdin,
80
- output: process.stderr,
81
- });
82
- try {
83
- const answer = await prompt.question([
84
- "How should knodin integrate with coding agents?",
85
- " 1. Personal (recommended) — all detected agents; Git stays clean",
86
- " 2. Team — create commit-ready shared configuration",
87
- " 3. CLI-only — agents will not discover or invoke knodin automatically",
88
- "Select [1]: ",
89
- ].join("\n"));
90
- if (!answer.trim() || answer.trim() === "1")
91
- return "personal";
92
- if (answer.trim() === "2")
93
- return "team";
94
- if (answer.trim() === "3") {
95
- const confirmation = await prompt.question("CLI-only requires manual knodin commands. Continue? [y/N] ");
96
- if (!/^y(?:es)?$/i.test(confirmation.trim()))
97
- throw new Error("knodin init: CLI-only selection cancelled");
98
- return "cli-only";
99
- }
100
- return parseInitScope(answer.trim());
101
- }
102
- finally {
103
- prompt.close();
104
- }
105
- }
106
75
  function integrationAgents(repo) {
107
76
  const previous = readRepositoryIntegrationConfig(repo)?.agents ?? [];
108
77
  const repositoryDetected = inspectRepositoryIntegrationStatus(repo)?.agents ?? [];
@@ -151,7 +120,7 @@ function formatConfigureHuman(result) {
151
120
  const externalOutcomeLines = result.paths.externalConfigurationOutcomes
152
121
  .map(({ system, state }) => `\nExternal configuration outcome: ${system} ${state} (external mutation not locally observable)`)
153
122
  .join("");
154
- return `${result.message}\nAgent integration: ${result.paths.scope} — ${configured}${failures}${filesystemMutationLines}${externalOutcomeLines}\nGraph initialization: unchanged\nLifecycle refresh: ${refresh}\nNext: ${result.nextAction}\n`;
123
+ return `${result.message}\nAgent integration: ${result.paths.scope} — ${configured}${failures}${filesystemMutationLines}${externalOutcomeLines}\nGraph initialization: refreshed and verified\nLifecycle refresh: ${refresh}\nNext: ${result.nextAction}\n`;
155
124
  }
156
125
  function formatRepairHuman(result) {
157
126
  const coverage = result.after.coverage;
@@ -652,7 +621,7 @@ async function main() {
652
621
  depth: 0,
653
622
  include: [repository],
654
623
  command: resolveCliRuntimeCommand(process),
655
- index: (target) => engine.index(target),
624
+ index: indexOrSeed(engine, KNODIN_SCHEMA_VERSION),
656
625
  status: (target) => engine.status(target),
657
626
  agents: detectSupportedAgents(),
658
627
  indexMode: (target) => indexModeForPath(loadSystemConfiguration(target), target),
@@ -705,6 +674,26 @@ async function main() {
705
674
  // `--json` is a shared output flag. Repair owns its richer --json/--jsonl
706
675
  // parser; all other commands receive their original arguments minus it.
707
676
  const rest = cmd === "repair" ? rawRest : rawRest.filter((argument) => argument !== "--json");
677
+ let recoveryJournal = readUpdateJournal();
678
+ const recoverySafeStates = new Set(["planned", "candidate-verified", "rollback-verified"]);
679
+ if (recoveryJournal && !recoverySafeStates.has(recoveryJournal.state) && cmd !== "doctor") {
680
+ const recovery = await recoverInterruptedUpdate({ runtimeCommand, env: process.env });
681
+ recoveryJournal = readUpdateJournal();
682
+ if (recovery?.status === "terminal-unhealthy" && cmd !== "update") {
683
+ throw new Error(`knodin update recovery is terminal-unhealthy: ${recovery.reason ?? "candidate and rollback verification failed"}`);
684
+ }
685
+ }
686
+ if (recoveryJournal &&
687
+ !recoverySafeStates.has(recoveryJournal.state) &&
688
+ cmd !== "update" &&
689
+ cmd !== "doctor") {
690
+ throw new Error(`knodin is in unverified update state ${recoveryJournal.state}; run \`knodin update status\` for recovery guidance`);
691
+ }
692
+ if (!jsonOutput && cmd !== "update") {
693
+ const attention = updateAttention(readManagerUpdateState());
694
+ if (attention)
695
+ process.stderr.write(`${attention}\n`);
696
+ }
708
697
  const selector = {
709
698
  identity: selectorValue("--identity"),
710
699
  file: selectorValue("--file"),
@@ -776,7 +765,7 @@ async function main() {
776
765
  depth: plan.depth,
777
766
  dryRun: plan.dryRun,
778
767
  worktrees: plan.worktrees,
779
- index: (target) => engine.index(target),
768
+ index: indexOrSeed(engine, KNODIN_SCHEMA_VERSION),
780
769
  status: (target) => engine.status(target),
781
770
  agents: detectSupportedAgents(),
782
771
  indexMode: (target) => indexModeForPath(systemConfig, target),
@@ -826,7 +815,11 @@ async function main() {
826
815
  repositories,
827
816
  };
828
817
  const boundedOutput = plan.signals
829
- ? applyResponseBudget(output, "repositories:discover", { bytes: plan.byteBudget, tokens: plan.tokenBudget, items: plan.itemBudget }, { bytes: 65_536, tokens: 16_384, items: 100 })
818
+ ? applyResponseBudget(output, "repositories:discover", {
819
+ bytes: plan.byteBudget,
820
+ tokens: plan.tokenBudget,
821
+ items: plan.itemBudget,
822
+ }, { bytes: 65_536, tokens: 16_384, items: 100 })
830
823
  : output;
831
824
  process.stdout.write(`${JSON.stringify(boundedOutput, null, plan.json ? 0 : 2)}\n`);
832
825
  process.exitCode = discovery.issues.length > 0 ? 1 : 0;
@@ -880,7 +873,7 @@ async function main() {
880
873
  manifestPath: plan.manifestPath,
881
874
  include: plan.include,
882
875
  exclude: plan.exclude,
883
- index: (target) => engine.index(target),
876
+ index: indexOrSeed(engine, KNODIN_SCHEMA_VERSION),
884
877
  status: (target) => engine.status(target),
885
878
  agents: detectSupportedAgents(),
886
879
  indexMode: (target) => indexModeForPath(systemConfig, target),
@@ -903,7 +896,12 @@ async function main() {
903
896
  }));
904
897
  }
905
898
  await engine.close();
906
- const output = { schemaVersion: 1, command: "init", ...summary, repositories };
899
+ const output = {
900
+ schemaVersion: 1,
901
+ command: "init",
902
+ ...summary,
903
+ repositories,
904
+ };
907
905
  process.stdout.write(plan.json ? `${JSON.stringify(output)}\n` : formatRepositoryHuman(summary));
908
906
  process.exitCode = summary.exitCode;
909
907
  return;
@@ -1010,13 +1008,15 @@ async function main() {
1010
1008
  if (cmd === "update") {
1011
1009
  if (repoFlag !== undefined)
1012
1010
  throw new Error("knodin update does not accept --repo");
1013
- const [action, ...unsupported] = rest;
1014
- if (!action || !["status", "check", "explain", "apply", "rollback"].includes(action)) {
1015
- throw new Error("knodin update requires status, check, explain, apply, or rollback");
1016
- }
1017
- if (unsupported.length > 0)
1018
- throw new Error(`knodin update ${action}: unknown argument ${unsupported[0]}`);
1011
+ const [action, ...actionArguments] = rest;
1012
+ if (!action)
1013
+ throw new Error("knodin update requires an action");
1019
1014
  const method = detectUpdateInstallMethod(runtimeCommand);
1015
+ const owner = resolveManagerOwnership({
1016
+ runtimeCommand,
1017
+ executablePath: runtimeCommand.at(-1),
1018
+ env: process.env,
1019
+ });
1020
1020
  const runManager = async (argv) => {
1021
1021
  const [executable, ...arguments_] = argv;
1022
1022
  if (!executable)
@@ -1052,8 +1052,16 @@ async function main() {
1052
1052
  healthCheck,
1053
1053
  };
1054
1054
  let output;
1055
- if (action === "status")
1056
- output = trustedUpdateStatus(options);
1055
+ if (action === "status") {
1056
+ output = {
1057
+ ...trustedUpdateStatus(options),
1058
+ owner,
1059
+ managerState: readManagerUpdateState(),
1060
+ journal: readUpdateJournal(),
1061
+ coordination: coordinationStatus(),
1062
+ attention: updateAttention(readManagerUpdateState()),
1063
+ };
1064
+ }
1057
1065
  else if (action === "check") {
1058
1066
  try {
1059
1067
  output = await checkTrustedUpdate(options);
@@ -1067,11 +1075,178 @@ async function main() {
1067
1075
  output = explainTrustedUpdate(options);
1068
1076
  else if (action === "apply")
1069
1077
  output = await applyTrustedUpdate(options);
1070
- else
1078
+ else if (action === "rollback")
1071
1079
  output = await rollbackTrustedUpdate(options);
1080
+ else if (action === "available") {
1081
+ output = await queryOwnerAvailability({ installedVersion: KNODIN_VERSION, owner });
1082
+ const availability = output;
1083
+ const state = readManagerUpdateState();
1084
+ if (availability.latestVersion &&
1085
+ state.pin?.kind === "automatic-rollback" &&
1086
+ state.pin.acknowledgedFailedVersion === state.pin.failedVersion &&
1087
+ availability.latestVersion !== state.pin.failedVersion) {
1088
+ writeManagerUpdateState({
1089
+ ...state,
1090
+ lastAvailableVersion: availability.latestVersion,
1091
+ pin: { ...state.pin, newerAvailableVersion: availability.latestVersion },
1092
+ });
1093
+ }
1094
+ }
1095
+ else if (action === "upgrade") {
1096
+ const apply = actionArguments.includes("--apply");
1097
+ const explicitVersion = actionArguments.find((argument) => argument !== "--apply");
1098
+ let targetVersion = explicitVersion;
1099
+ if (!targetVersion) {
1100
+ const availability = await queryOwnerAvailability({
1101
+ installedVersion: KNODIN_VERSION,
1102
+ owner,
1103
+ });
1104
+ if (!availability.latestVersion)
1105
+ output = availability;
1106
+ else
1107
+ targetVersion = availability.latestVersion;
1108
+ }
1109
+ if (targetVersion)
1110
+ output = await executeManagerUpdate({
1111
+ currentVersion: KNODIN_VERSION,
1112
+ targetVersion,
1113
+ runtimeCommand,
1114
+ env: process.env,
1115
+ apply,
1116
+ });
1117
+ }
1118
+ else if (action === "pin") {
1119
+ const apply = actionArguments.includes("--apply");
1120
+ const version = actionArguments.find((argument) => argument !== "--apply");
1121
+ if (!version)
1122
+ throw new Error("knodin update pin requires a version");
1123
+ output = apply
1124
+ ? await executeManagerUpdate({
1125
+ currentVersion: KNODIN_VERSION,
1126
+ targetVersion: version,
1127
+ runtimeCommand,
1128
+ env: process.env,
1129
+ apply: true,
1130
+ pinKind: "manual",
1131
+ })
1132
+ : setManualPin(version);
1133
+ }
1134
+ else if (action === "unpin")
1135
+ output = unpinUpdate();
1136
+ else if (action === "acknowledge")
1137
+ output = acknowledgeUpdateFailure();
1138
+ else if (action === "auto") {
1139
+ const subaction = actionArguments[0];
1140
+ if (!subaction || !["status", "enable", "disable", "run"].includes(subaction))
1141
+ throw new Error("knodin update auto requires status, enable, disable, or run");
1142
+ const state = readManagerUpdateState();
1143
+ if (subaction === "status")
1144
+ output = state;
1145
+ else if (subaction === "disable") {
1146
+ output = { ...state, autoEnabled: false };
1147
+ writeManagerUpdateState(output);
1148
+ }
1149
+ else if (subaction === "enable") {
1150
+ if (state.platformAutoEligible) {
1151
+ output = { ...state, autoEnabled: true };
1152
+ writeManagerUpdateState(output);
1153
+ }
1154
+ else
1155
+ output = await certifyAutomaticUpdates({
1156
+ currentVersion: KNODIN_VERSION,
1157
+ runtimeCommand,
1158
+ env: process.env,
1159
+ });
1160
+ }
1161
+ else if (!state.autoEnabled || !state.platformAutoEligible) {
1162
+ output = { ...state, status: "disabled" };
1163
+ }
1164
+ else {
1165
+ const availability = await queryOwnerAvailability({
1166
+ installedVersion: KNODIN_VERSION,
1167
+ owner,
1168
+ });
1169
+ output =
1170
+ availability.status === "available" &&
1171
+ availability.autoEligible &&
1172
+ availability.latestVersion
1173
+ ? await executeManagerUpdate({
1174
+ currentVersion: KNODIN_VERSION,
1175
+ targetVersion: availability.latestVersion,
1176
+ runtimeCommand,
1177
+ env: process.env,
1178
+ apply: true,
1179
+ })
1180
+ : availability;
1181
+ }
1182
+ }
1183
+ else
1184
+ throw new Error(`knodin update: unknown action ${action}`);
1072
1185
  process.stdout.write(jsonOutput ? `${JSON.stringify(output)}\n` : formatGenericHuman("update", output));
1073
1186
  return;
1074
1187
  }
1188
+ if (cmd === "backups") {
1189
+ const action = invocation.commandPath[1];
1190
+ const retentionAction = invocation.commandPath[2];
1191
+ const roots = invocation.positionals;
1192
+ const defaultRoots = () => {
1193
+ if (roots.length > 0)
1194
+ return roots;
1195
+ const target = resolveRepo(repoFlag, process.cwd());
1196
+ if (!target.ok)
1197
+ throw new Error(target.error);
1198
+ return [target.repo];
1199
+ };
1200
+ const depth = invocation.options.depth;
1201
+ const retentionDays = invocation.options.retentionDays;
1202
+ const keepNewest = invocation.options.keepNewest;
1203
+ let output;
1204
+ if (action === "list")
1205
+ output = listBackups(defaultRoots(), { depth });
1206
+ else if (action === "prune") {
1207
+ output = pruneBackups(defaultRoots(), {
1208
+ depth,
1209
+ retentionDays,
1210
+ keepNewest,
1211
+ apply: invocation.options.apply === true,
1212
+ });
1213
+ }
1214
+ else if (action === "retention" && retentionAction === "install") {
1215
+ output = installBackupRetention(roots, {
1216
+ depth,
1217
+ retentionDays,
1218
+ keepNewest,
1219
+ schedule: invocation.options.schedule,
1220
+ dryRun: invocation.options.dryRun === true,
1221
+ launcher: runtimeCommand,
1222
+ });
1223
+ }
1224
+ else if (action === "retention" && retentionAction === "status") {
1225
+ output = retentionStatus();
1226
+ }
1227
+ else if (action === "retention" && retentionAction === "doctor") {
1228
+ output = retentionDoctor();
1229
+ }
1230
+ else if (action === "retention" && retentionAction === "remove") {
1231
+ output = removeBackupRetention();
1232
+ }
1233
+ else if (action === "retention" && retentionAction === "run") {
1234
+ output = runInstalledRetention();
1235
+ }
1236
+ else {
1237
+ throw new Error("knodin backups requires list, prune, or retention install|status|doctor|remove");
1238
+ }
1239
+ if (invocation.options.jsonl === true) {
1240
+ const records = output.backups ??
1241
+ output.candidates ?? [output];
1242
+ for (const record of records)
1243
+ process.stdout.write(`${JSON.stringify(record)}\n`);
1244
+ }
1245
+ else {
1246
+ process.stdout.write(jsonOutput ? `${JSON.stringify(output)}\n` : formatBackupHuman("backups", output));
1247
+ }
1248
+ return;
1249
+ }
1075
1250
  if (process.stdout.isTTY &&
1076
1251
  claimScheduledUpdateCheck({
1077
1252
  currentVersion: KNODIN_VERSION,
@@ -1123,6 +1298,14 @@ async function main() {
1123
1298
  process.exit(1);
1124
1299
  }
1125
1300
  const repo = resolved.repo;
1301
+ if (cmd !== "doctor") {
1302
+ try {
1303
+ maybeRunOpportunisticRetention(repo);
1304
+ }
1305
+ catch {
1306
+ // Best-effort cleanup must never make an ordinary command fail.
1307
+ }
1308
+ }
1126
1309
  if (cmd === "agent-event" && !fs.existsSync(resolveDbPath(repo)))
1127
1310
  return;
1128
1311
  if (cmd === "doctor") {
@@ -1246,7 +1429,10 @@ async function main() {
1246
1429
  const input = positionals[0] === "create" ? (positionals[1] ?? "-") : (positionals[0] ?? "-");
1247
1430
  compressionResult =
1248
1431
  input === "-"
1249
- ? compressOutput(repo, { ...request, text: await readBoundedStdin(maxInputBytes) })
1432
+ ? compressOutput(repo, {
1433
+ ...request,
1434
+ text: await readBoundedStdin(maxInputBytes),
1435
+ })
1250
1436
  : compressOutputFile(repo, input, request);
1251
1437
  }
1252
1438
  if (action === "diagnose" &&
@@ -1325,7 +1511,10 @@ async function main() {
1325
1511
  if (existing.state !== "configured")
1326
1512
  throw new Error("knodin shared configure --disable requires a valid existing configuration");
1327
1513
  writeSharedIndexConfig(repo, { ...existing.config, enabled: false });
1328
- result = { state: "configured", config: { ...existing.config, enabled: false } };
1514
+ result = {
1515
+ state: "configured",
1516
+ config: { ...existing.config, enabled: false },
1517
+ };
1329
1518
  break;
1330
1519
  }
1331
1520
  const required = (name) => {
@@ -1360,7 +1549,11 @@ async function main() {
1360
1549
  },
1361
1550
  };
1362
1551
  const target = writeSharedIndexConfig(repo, config);
1363
- result = { state: "configured", path: target, config: loadSharedIndexConfig(repo) };
1552
+ result = {
1553
+ state: "configured",
1554
+ path: target,
1555
+ config: loadSharedIndexConfig(repo),
1556
+ };
1364
1557
  break;
1365
1558
  }
1366
1559
  if (action === "publisher-metadata") {
@@ -1394,7 +1587,10 @@ async function main() {
1394
1587
  if (!bundlePath)
1395
1588
  throw new Error("knodin shared publisher-finalize requires <bundle>");
1396
1589
  const bundle = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), bundlePath), "utf8"));
1397
- const pointer = finalizeSharedPublisherBundle({ config: configuration.config, bundle });
1590
+ const pointer = finalizeSharedPublisherBundle({
1591
+ config: configuration.config,
1592
+ bundle,
1593
+ });
1398
1594
  result = { bundle, pointer: JSON.parse(pointer.toString("utf8")) };
1399
1595
  break;
1400
1596
  }
@@ -1421,12 +1617,19 @@ async function main() {
1421
1617
  let credentialProbe = "not-requested";
1422
1618
  if (invocation.options.probe === true) {
1423
1619
  if (configuration.state !== "configured" || !configuration.config.enabled) {
1424
- credentialProbe = { state: "unavailable", category: "configuration" };
1620
+ credentialProbe = {
1621
+ state: "unavailable",
1622
+ category: "configuration",
1623
+ };
1425
1624
  }
1426
1625
  else {
1427
1626
  const store = createSharedObjectStore(configuration.config);
1428
1627
  try {
1429
- const current = spawnSync(gitExecutable(), ["symbolic-ref", "--quiet", "--short", "HEAD"], { cwd: repo, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).stdout.trim();
1628
+ const current = spawnSync(gitExecutable(), ["symbolic-ref", "--quiet", "--short", "HEAD"], {
1629
+ cwd: repo,
1630
+ encoding: "utf8",
1631
+ stdio: ["ignore", "pipe", "ignore"],
1632
+ }).stdout.trim();
1430
1633
  const branch = configuredBranchCandidates(configuration.config, current || undefined)[0];
1431
1634
  if (!store || !branch)
1432
1635
  throw new SharedIndexError("configuration", "shared-index probe has no configured local branch candidate");
@@ -1667,7 +1870,7 @@ async function main() {
1667
1870
  continue;
1668
1871
  await initializeRepository(checkout, {
1669
1872
  command: runtimeCommand,
1670
- index: (target, indexOptions) => engine.index(target, undefined, false, indexOptions),
1873
+ index: indexOrSeed(engine, KNODIN_SCHEMA_VERSION),
1671
1874
  scope: "personal",
1672
1875
  agents: [],
1673
1876
  });
@@ -1703,7 +1906,11 @@ async function main() {
1703
1906
  const [systemId, ...repositories] = invocation.positionals;
1704
1907
  if (!systemId)
1705
1908
  throw new Error("knodin system unlink requires <system-id>");
1706
- const plan = planSystemUnlink({ systemId, repositories, repoPath: repo });
1909
+ const plan = planSystemUnlink({
1910
+ systemId,
1911
+ repositories,
1912
+ repoPath: repo,
1913
+ });
1707
1914
  result = invocation.options.dryRun === true ? plan : applySystemPlan(plan);
1708
1915
  break;
1709
1916
  }
@@ -1822,32 +2029,39 @@ async function main() {
1822
2029
  break;
1823
2030
  }
1824
2031
  case "hook-refresh": {
1825
- const [kind, first, second] = rest;
1826
- let event;
1827
- if (kind === "commit")
1828
- event = { kind };
1829
- else if (kind === "checkout" && first && second) {
1830
- event = { kind, before: first, after: second };
1831
- }
1832
- else if (kind === "merge" && first && second) {
1833
- event = { kind, before: first, after: second };
1834
- }
1835
- else if (kind === "rewrite" && first) {
1836
- event = { kind, inputPath: first };
2032
+ const lifecycleLease = acquireRepairLease(repo, "lifecycle-hook");
2033
+ try {
2034
+ const [kind, first, second] = rest;
2035
+ let event;
2036
+ if (kind === "commit")
2037
+ event = { kind };
2038
+ else if (kind === "checkout" && first && second) {
2039
+ event = { kind, before: first, after: second };
2040
+ }
2041
+ else if (kind === "merge" && first && second) {
2042
+ event = { kind, before: first, after: second };
2043
+ }
2044
+ else if (kind === "rewrite" && first) {
2045
+ event = { kind, inputPath: first };
2046
+ }
2047
+ else {
2048
+ throw new Error("knodin hook-refresh: invalid lifecycle event");
2049
+ }
2050
+ const shared = await opportunisticSharedRestore();
2051
+ const indexed = shared?.restored
2052
+ ? []
2053
+ : await refreshFromGitEvent(repo, event, (target, files) => engine.index(target, files));
2054
+ result = { indexed, ...(shared ? { sharedRestore: shared } : {}) };
1837
2055
  }
1838
- else {
1839
- throw new Error("knodin hook-refresh: invalid lifecycle event");
2056
+ finally {
2057
+ lifecycleLease.release();
1840
2058
  }
1841
- const shared = await opportunisticSharedRestore();
1842
- const indexed = shared?.restored
1843
- ? []
1844
- : await refreshFromGitEvent(repo, event, (target, files) => engine.index(target, files));
1845
- result = { indexed, ...(shared ? { sharedRestore: shared } : {}) };
1846
2059
  break;
1847
2060
  }
1848
2061
  case "init": {
1849
- const scope = await chooseInitScope(rawRest, repo);
1850
- const agents = scope === "team" ? [] : integrationAgents(repo);
2062
+ const requestedScope = explicitScope(rawRest);
2063
+ const scope = requestedScope ?? readRepositoryIntegrationConfig(repo)?.scope ?? "cli-only";
2064
+ const agents = requestedScope && scope !== "team" ? integrationAgents(repo) : [];
1851
2065
  const renderer = createInitRenderer();
1852
2066
  const activity = createIndexActivityReporter(repo);
1853
2067
  activity.start();
@@ -1862,9 +2076,10 @@ async function main() {
1862
2076
  }
1863
2077
  paths = await initializeRepository(repo, {
1864
2078
  command: runtimeCommand,
1865
- index: (target, options) => engine.index(target, undefined, false, options),
2079
+ index: indexOrSeed(engine, KNODIN_SCHEMA_VERSION),
1866
2080
  scope,
1867
2081
  agents,
2082
+ configureIntegration: requestedScope !== null,
1868
2083
  onProgress: (event) => {
1869
2084
  activity.update(event);
1870
2085
  renderer.onProgress(event);
@@ -1909,16 +2124,17 @@ async function main() {
1909
2124
  const agents = scope === "team" ? [] : integrationAgents(repo);
1910
2125
  const paths = await initializeRepository(repo, {
1911
2126
  command: runtimeCommand,
1912
- index: async () => undefined,
2127
+ index: indexOrSeed(engine, KNODIN_SCHEMA_VERSION),
1913
2128
  scope,
1914
2129
  agents,
1915
2130
  allowTrackedTransition: true,
1916
2131
  auditConfigurationChanges: true,
2132
+ configureIntegration: true,
1917
2133
  });
1918
2134
  result = {
1919
2135
  status: "success",
1920
2136
  message: `knodin agent integration changed to ${scope}.`,
1921
- graphInitialization: "unchanged",
2137
+ graphInitialization: "refreshed",
1922
2138
  nextAction: "run `knodin status` and reload the configured client",
1923
2139
  paths,
1924
2140
  };
@@ -2068,7 +2284,9 @@ async function main() {
2068
2284
  }
2069
2285
  const progressMode = resolveRepairProgressMode(options, process.env, process.stderr.isTTY);
2070
2286
  const renderer = progressMode === "tty"
2071
- ? createProgressWorkerRenderer("repair-progress-worker", { type: "start" })
2287
+ ? createProgressWorkerRenderer("repair-progress-worker", {
2288
+ type: "start",
2289
+ })
2072
2290
  : (() => {
2073
2291
  const direct = createRepairProgressRenderer({
2074
2292
  mode: progressMode,
@@ -2211,6 +2429,29 @@ async function main() {
2211
2429
  }));
2212
2430
  break;
2213
2431
  }
2432
+ case "seal": {
2433
+ const output = selectorValue("--output");
2434
+ if (!output)
2435
+ throw new Error("knodin seal requires --output <path.sqlite>");
2436
+ result = await runSeal({
2437
+ repoPath: repo,
2438
+ outputPath: output,
2439
+ // Embeddings dominate artifact size and code lookup is served
2440
+ // lexically plus by graph traversal, so they are stripped unless
2441
+ // asked for.
2442
+ stripEmbeddings: !rest.includes("--keep-embeddings"),
2443
+ includeExcluded: rest.includes("--include-excluded"),
2444
+ });
2445
+ break;
2446
+ }
2447
+ case "sealed": {
2448
+ if (!rest[0])
2449
+ throw new Error("knodin sealed requires an artifact path");
2450
+ result = await runSealedQuery(rest[0], rest[1], {
2451
+ strictCompat: rest.includes("--strict-compat"),
2452
+ });
2453
+ break;
2454
+ }
2214
2455
  case "pack": {
2215
2456
  const action = rest[0];
2216
2457
  const diffScope = selectorValue("--diff-scope");
@@ -2509,7 +2750,10 @@ async function main() {
2509
2750
  else if (action === "export")
2510
2751
  result = exportTelemetry(repo, readTelemetryRecords(repo, input, retentionDays), selectorValue("--output"), readSessionEvents(repo, retentionDays));
2511
2752
  else if (action === "clear")
2512
- result = { ...clearTelemetry(repo, input), session: clearSessionTelemetry(repo) };
2753
+ result = {
2754
+ ...clearTelemetry(repo, input),
2755
+ session: clearSessionTelemetry(repo),
2756
+ };
2513
2757
  else
2514
2758
  throw new Error("knodin telemetry requires enable, disable, status, report, export, or clear");
2515
2759
  break;
@@ -2581,7 +2825,11 @@ async function main() {
2581
2825
  result =
2582
2826
  action === "preview"
2583
2827
  ? persistDiagnosticsPreview(repo, options)
2584
- : collectDiagnostics(repo, { ...options, outputPath: outputOption, previewId });
2828
+ : collectDiagnostics(repo, {
2829
+ ...options,
2830
+ outputPath: outputOption,
2831
+ previewId,
2832
+ });
2585
2833
  }
2586
2834
  else {
2587
2835
  throw new Error("knodin diagnostics requires enable, status, preview, archive, inspect, clear, or disable");
@@ -2717,6 +2965,8 @@ catch (err) {
2717
2965
  "wiki",
2718
2966
  "visualize",
2719
2967
  "pack",
2968
+ "seal",
2969
+ "sealed",
2720
2970
  "compress",
2721
2971
  "prs",
2722
2972
  "worktrees",