knodin 0.8.6 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +6 -0
  2. package/dist/bin/cli.js +459 -95
  3. package/dist/bin/launcher.js +0 -0
  4. package/dist/fixtures/update-verification/config/application.xml +4 -0
  5. package/dist/fixtures/update-verification/dbt/manifest.json +17 -0
  6. package/dist/fixtures/update-verification/dbt/models/accounts.sql +1 -0
  7. package/dist/fixtures/update-verification/dbt/models/contacts.sql +1 -0
  8. package/dist/fixtures/update-verification/fixture.json +147 -0
  9. package/dist/fixtures/update-verification/force-app/main/default/classes/UpdateFixture.cls +14 -0
  10. package/dist/fixtures/update-verification/force-app/main/default/flows/Update_Verification.flow-meta.xml +9 -0
  11. package/dist/fixtures/update-verification/package.json +5 -0
  12. package/dist/fixtures/update-verification/prisma/schema.prisma +10 -0
  13. package/dist/fixtures/update-verification/sql/functions.sql +16 -0
  14. package/dist/fixtures/update-verification/src/CsharpFixture.cs +6 -0
  15. package/dist/fixtures/update-verification/src/JavaFixture.java +9 -0
  16. package/dist/fixtures/update-verification/src/javascript.js +7 -0
  17. package/dist/fixtures/update-verification/src/python_fixture.py +6 -0
  18. package/dist/fixtures/update-verification/src/typescript.ts +7 -0
  19. package/dist/fixtures/update-verification/terraform/main.tf +7 -0
  20. package/dist/fixtures/update-verification/terraform/modules/verified-child/main.tf +3 -0
  21. package/dist/src/agent-integration.js +26 -3
  22. package/dist/src/backup-retention.js +811 -0
  23. package/dist/src/cli-model.js +66 -9
  24. package/dist/src/docs-sections.js +1 -0
  25. package/dist/src/doctor.js +142 -12
  26. package/dist/src/engine/embeddings.js +8 -1
  27. package/dist/src/engine/index.js +355 -292
  28. package/dist/src/init.js +223 -164
  29. package/dist/src/manager-update.js +455 -0
  30. package/dist/src/release-preflight.js +106 -95
  31. package/dist/src/repair-lease.js +77 -8
  32. package/dist/src/repository-management.js +219 -3
  33. package/dist/src/response-budget.js +5 -1
  34. package/dist/src/server.js +18 -5
  35. package/dist/src/shared-index/compatibility.js +4 -2
  36. package/dist/src/skill-management.js +149 -0
  37. package/dist/src/system-config.js +176 -16
  38. package/dist/src/system-management.js +508 -0
  39. package/dist/src/tools/knodin-tools.js +174 -22
  40. package/dist/src/update-ceremony.js +20 -24
  41. package/dist/src/update-coordination.js +158 -0
  42. package/dist/src/update-executor.js +336 -0
  43. package/dist/src/update-verifier.js +358 -0
  44. package/docs/BACKUP-RETENTION.md +54 -0
  45. package/docs/CLI.md +13 -0
  46. package/docs/DOCTOR-AND-UPDATES.md +44 -12
  47. package/docs/SIGNED-UPDATES.md +29 -22
  48. package/docs/SYSTEMS-AND-RELATIONSHIPS.md +27 -6
  49. package/docs/prompts/declare-multi-repository-system.md +18 -0
  50. package/docs/releases/0.8.7.md +27 -0
  51. package/docs/releases/0.9.0.md +43 -0
  52. package/package.json +11 -2
  53. package/roadmap/competitive-roadmap.md +8 -0
  54. package/skills/knodin-debug/SKILL.md +12 -0
  55. package/skills/knodin-debug/agents/openai.yaml +4 -0
  56. package/skills/knodin-explore/SKILL.md +11 -0
  57. package/skills/knodin-explore/agents/openai.yaml +4 -0
  58. package/skills/knodin-impact/SKILL.md +11 -0
  59. package/skills/knodin-impact/agents/openai.yaml +4 -0
  60. package/skills/knodin-lifecycle/SKILL.md +12 -0
  61. package/skills/knodin-lifecycle/agents/openai.yaml +4 -0
  62. package/skills/knodin-refactor/SKILL.md +12 -0
  63. package/skills/knodin-refactor/agents/openai.yaml +4 -0
  64. package/skills/knodin-review/SKILL.md +11 -0
  65. package/skills/knodin-review/agents/openai.yaml +4 -0
  66. package/skills/knodin-systems/SKILL.md +14 -0
  67. package/skills/knodin-systems/agents/openai.yaml +4 -0
package/dist/src/init.js CHANGED
@@ -7,6 +7,8 @@ import { compareBytes } from "./compare.js";
7
7
  import { isIndexableSourcePath } from "./engine/source-policy.js";
8
8
  import { lookupMirror } from "./engine/state-paths.js";
9
9
  import { inspectLefthookIntegration, installHookManagerIntegration, isActiveLefthookHook, } from "./hook-manager-integration.js";
10
+ import { acquireRepairLease } from "./repair-lease.js";
11
+ import { installKnodinSkills, removeKnodinSkills } from "./skill-management.js";
10
12
  import { registerInitializedWorktree } from "./worktree-lifecycle.js";
11
13
  const MANAGED_MARKER = "KNODIN MANAGED HOOK";
12
14
  const HOOK_NAMES = ["post-commit", "post-checkout", "post-merge", "post-rewrite"];
@@ -152,7 +154,10 @@ function configureMutationAuditTargets(repo) {
152
154
  });
153
155
  }
154
156
  for (const hookName of HOOK_NAMES) {
155
- add({ absolute: path.join(effectiveHooks, hookName), label: `${effectiveLabel}/${hookName}` });
157
+ add({
158
+ absolute: path.join(effectiveHooks, hookName),
159
+ label: `${effectiveLabel}/${hookName}`,
160
+ });
156
161
  add({
157
162
  absolute: path.join(effectiveHooks, `${hookName}.knodin-original`),
158
163
  label: `${effectiveLabel}/${hookName}.knodin-original`,
@@ -384,11 +389,11 @@ while :; do
384
389
  ARG1="$(sed -n '2p' "$EVENT")"
385
390
  ARG2="$(sed -n '3p' "$EVENT")"
386
391
  if [ "$KIND" = "commit" ]; then
387
- ${invocation} --repo "$REPO_ROOT" hook-refresh commit --json >> "$LOG_PATH" 2>&1
392
+ ${invocation} --repo "$REPO_ROOT" hook-refresh commit --json >> "$LOG_PATH" 2>> "$LOG_PATH.stderr"
388
393
  elif [ "$KIND" = "checkout" ] || [ "$KIND" = "merge" ]; then
389
- ${invocation} --repo "$REPO_ROOT" hook-refresh "$KIND" "$ARG1" "$ARG2" --json >> "$LOG_PATH" 2>&1
394
+ ${invocation} --repo "$REPO_ROOT" hook-refresh "$KIND" "$ARG1" "$ARG2" --json >> "$LOG_PATH" 2>> "$LOG_PATH.stderr"
390
395
  elif [ "$KIND" = "rewrite" ]; then
391
- ${invocation} --repo "$REPO_ROOT" hook-refresh rewrite "$ARG1" --json >> "$LOG_PATH" 2>&1
396
+ ${invocation} --repo "$REPO_ROOT" hook-refresh rewrite "$ARG1" --json >> "$LOG_PATH" 2>> "$LOG_PATH.stderr"
392
397
  else
393
398
  printf 'knodin background refresh: invalid queued event %s\n' "$KIND" >> "$LOG_PATH"
394
399
  false
@@ -700,6 +705,7 @@ This repository is indexed locally by knodin. Start with \`knodin status\`; if i
700
705
  - Use gateway operations \`explain\`, \`review\`, and \`query\` for source-evidenced context. CLI fallbacks are \`knodin explain\`, \`knodin review --scope compare\`, and \`knodin rename\`.
701
706
  - Direct reads are appropriate for files just authored in the current session; do not force a graph round trip when the agent already has fresh source context.
702
707
  - Run \`knodin index <changed-files>\` while working when you need immediately fresh graph evidence. Managed Git hooks installed by \`knodin init\` refresh the graph after commits, checkouts, merges, and rewrites.
708
+ - When indexing is active, run \`knodin wait --fresh\` and recheck status before falling back to direct traversal.
703
709
  - Keep output truthful: local graph evidence can be stale or ambiguous; say so and repair/reindex instead of claiming certainty.
704
710
 
705
711
  <!-- knodin:end -->
@@ -959,9 +965,17 @@ export function inspectRepositoryIntegrationStatus(repo) {
959
965
  const receipt = readRepositoryIntegrationConfig(repo);
960
966
  const candidates = [
961
967
  { agent: "claude", path: ".mcp.json", marker: '"knodin"' },
962
- { agent: "codex", path: ".codex/config.toml", marker: 'mcp_servers."knodin"' },
968
+ {
969
+ agent: "codex",
970
+ path: ".codex/config.toml",
971
+ marker: 'mcp_servers."knodin"',
972
+ },
963
973
  { agent: "gemini", path: ".gemini/settings.json", marker: '"knodin"' },
964
- { agent: "antigravity", path: ".agents/mcp_config.json", marker: '"knodin"' },
974
+ {
975
+ agent: "antigravity",
976
+ path: ".agents/mcp_config.json",
977
+ marker: '"knodin"',
978
+ },
965
979
  {
966
980
  agent: "codex",
967
981
  path: "AGENTS.md",
@@ -1046,6 +1060,7 @@ export function detectTrackedTeamIntegration(repo) {
1046
1060
  export async function initializeRepository(repo, options) {
1047
1061
  const resolvedRepo = path.resolve(repo);
1048
1062
  const scope = options.scope ?? "personal";
1063
+ const configureIntegration = options.configureIntegration ?? true;
1049
1064
  const priorIntegration = readRepositoryIntegrationConfig(resolvedRepo);
1050
1065
  if (runGit(resolvedRepo, ["rev-parse", "--is-inside-work-tree"]).trim() !== "true") {
1051
1066
  throw new Error(`knodin init: not a Git worktree: ${resolvedRepo}`);
@@ -1064,173 +1079,217 @@ export async function initializeRepository(repo, options) {
1064
1079
  if (!path.isAbsolute(options.command[0])) {
1065
1080
  throw new Error("knodin init: packaged runtime command must be an absolute path");
1066
1081
  }
1067
- const mutationTargets = options.auditConfigurationChanges
1068
- ? configureMutationAuditTargets(resolvedRepo)
1069
- : [];
1070
- const beforeMutation = mutationSnapshot(mutationTargets);
1071
- if (scope !== "team" &&
1072
- !options.allowTrackedTransition &&
1073
- detectTrackedTeamIntegration(resolvedRepo)) {
1074
- throw new Error("knodin init: tracked team integration is active; use `knodin configure --scope personal` or `--scope cli-only` for an explicit commit-ready transition");
1075
- }
1076
- const knodinHooksDir = path.join(resolvedRepo, ".knodin", "hooks");
1077
- const hookInstallation = await prepareHookInstallation(resolvedRepo);
1078
- const hooksDir = hookInstallation.hooksDir;
1079
- await Promise.all([
1080
- fs.promises.mkdir(knodinHooksDir, { recursive: true }),
1081
- fs.promises.mkdir(hooksDir, { recursive: true }),
1082
- ]);
1083
- const backgroundPath = path.join(knodinHooksDir, "background-index.sh");
1084
- await fs.promises.writeFile(backgroundPath, backgroundScript(options.command), {
1085
- encoding: "utf-8",
1086
- mode: 0o755,
1087
- });
1088
- await fs.promises.chmod(backgroundPath, 0o755);
1089
- const hookManagerIntegration = installHookManagerIntegration(resolvedRepo);
1090
- for (const hookName of HOOK_NAMES) {
1091
- let preservedOriginal;
1092
- if (hookInstallation.trackedHooksDir) {
1093
- const relativeHook = `${hookInstallation.trackedHooksDir}/${hookName}`;
1094
- const sourceHook = path.join(resolvedRepo, relativeHook);
1095
- try {
1096
- const stat = await fs.promises.stat(sourceHook);
1097
- preservedOriginal =
1098
- stat.isFile() && (stat.mode & 0o111) !== 0 ? `repo:${relativeHook}` : null;
1099
- }
1100
- catch {
1101
- preservedOriginal = null;
1102
- }
1082
+ const lifecycleLease = acquireRepairLease(resolvedRepo, options.configureIntegration ? "configure" : "init");
1083
+ try {
1084
+ const mutationTargets = options.auditConfigurationChanges
1085
+ ? configureMutationAuditTargets(resolvedRepo)
1086
+ : [];
1087
+ const beforeMutation = mutationSnapshot(mutationTargets);
1088
+ if (configureIntegration &&
1089
+ scope !== "team" &&
1090
+ !options.allowTrackedTransition &&
1091
+ detectTrackedTeamIntegration(resolvedRepo)) {
1092
+ throw new Error("knodin init: tracked team integration is active; use `knodin configure --scope personal` or `--scope cli-only` for an explicit commit-ready transition");
1103
1093
  }
1104
- await installManagedHook(hooksDir, hookName, preservedOriginal);
1105
- }
1106
- for (const entry of await fs.promises.readdir(hooksDir, {
1107
- withFileTypes: true,
1108
- })) {
1109
- if (entry.isFile())
1110
- await preventLefthookAutoInstall(path.join(hooksDir, entry.name));
1111
- }
1112
- const indexResult = await options.index(resolvedRepo, {
1113
- onProgress: options.onProgress,
1114
- });
1115
- if (isIndexResult(indexResult) && indexResult.verification.status !== "healthy") {
1116
- throw new InitializationHealthError(indexResult);
1117
- }
1118
- const lifecycleRefresh = drainQueuedLifecycleEvents(resolvedRepo, backgroundPath);
1119
- await fs.promises.rm(path.join(knodinHooksDir, HOOK_FAILURE_FILE), { force: true });
1120
- let agentInstructionChanges = [];
1121
- if (scope === "team") {
1122
- const installed = await Promise.all([
1123
- installManagedAgentInstructions(resolvedRepo, "AGENTS.md"),
1124
- installManagedAgentInstructions(resolvedRepo, "GEMINI.md"),
1094
+ const knodinHooksDir = path.join(resolvedRepo, ".knodin", "hooks");
1095
+ const hookInstallation = await prepareHookInstallation(resolvedRepo);
1096
+ const hooksDir = hookInstallation.hooksDir;
1097
+ await Promise.all([
1098
+ fs.promises.mkdir(knodinHooksDir, { recursive: true }),
1099
+ fs.promises.mkdir(hooksDir, { recursive: true }),
1125
1100
  ]);
1126
- agentInstructionChanges = installed.filter((change) => change !== null);
1127
- }
1128
- else {
1129
- agentInstructionChanges = await removeManagedPersonalArtifacts(resolvedRepo, options.allowTrackedTransition);
1130
- }
1131
- const requestedAgents = options.agents ?? [];
1132
- const projectPathByAgent = {
1133
- codex: ".codex/config.toml",
1134
- gemini: ".gemini/settings.json",
1135
- copilot: ".vscode/mcp.json",
1136
- antigravity: ".agents/mcp_config.json",
1137
- };
1138
- const blockedAgents = [];
1139
- const externalConfigurationOutcomes = [];
1140
- if (scope === "personal" &&
1141
- options.allowTrackedTransition &&
1142
- priorIntegration?.scope === "team") {
1143
- const cleanup = configureProjectAgents({
1144
- repo: resolvedRepo,
1145
- scope: "cli-only",
1146
- agents: ["claude", "codex", "gemini", "copilot", "antigravity"],
1147
- run: options.runAgentCommand,
1101
+ const backgroundPath = path.join(knodinHooksDir, "background-index.sh");
1102
+ await fs.promises.writeFile(backgroundPath, backgroundScript(options.command), {
1103
+ encoding: "utf-8",
1104
+ mode: 0o755,
1148
1105
  });
1149
- if (cleanup.failed.length > 0)
1150
- throw new Error(`knodin configure: could not remove tracked team integration: ${cleanup.failed[0]?.message}`);
1151
- if (cleanup.removed.includes("claude"))
1152
- externalConfigurationOutcomes.push({
1153
- system: "claude-local-mcp",
1154
- state: "removal-attempted",
1106
+ await fs.promises.chmod(backgroundPath, 0o755);
1107
+ const hookManagerIntegration = installHookManagerIntegration(resolvedRepo);
1108
+ for (const hookName of HOOK_NAMES) {
1109
+ let preservedOriginal;
1110
+ if (hookInstallation.trackedHooksDir) {
1111
+ const relativeHook = `${hookInstallation.trackedHooksDir}/${hookName}`;
1112
+ const sourceHook = path.join(resolvedRepo, relativeHook);
1113
+ try {
1114
+ const stat = await fs.promises.stat(sourceHook);
1115
+ preservedOriginal =
1116
+ stat.isFile() && (stat.mode & 0o111) !== 0 ? `repo:${relativeHook}` : null;
1117
+ }
1118
+ catch {
1119
+ preservedOriginal = null;
1120
+ }
1121
+ }
1122
+ await installManagedHook(hooksDir, hookName, preservedOriginal);
1123
+ }
1124
+ for (const entry of await fs.promises.readdir(hooksDir, {
1125
+ withFileTypes: true,
1126
+ })) {
1127
+ if (entry.isFile())
1128
+ await preventLefthookAutoInstall(path.join(hooksDir, entry.name));
1129
+ }
1130
+ let agentInstructionChanges = [];
1131
+ let agentIntegration = {
1132
+ configured: [],
1133
+ removed: [],
1134
+ failed: [],
1135
+ projectFiles: [],
1136
+ };
1137
+ const externalConfigurationOutcomes = [];
1138
+ let excluded = [];
1139
+ if (configureIntegration && scope === "team") {
1140
+ const installed = await Promise.all([
1141
+ installManagedAgentInstructions(resolvedRepo, "AGENTS.md"),
1142
+ installManagedAgentInstructions(resolvedRepo, "GEMINI.md"),
1143
+ ]);
1144
+ agentInstructionChanges = installed.filter((change) => change !== null);
1145
+ }
1146
+ else if (configureIntegration) {
1147
+ agentInstructionChanges = await removeManagedPersonalArtifacts(resolvedRepo, options.allowTrackedTransition);
1148
+ }
1149
+ const requestedAgents = options.agents ?? [];
1150
+ const projectPathByAgent = {
1151
+ codex: ".codex/config.toml",
1152
+ gemini: ".gemini/settings.json",
1153
+ copilot: ".vscode/mcp.json",
1154
+ antigravity: ".agents/mcp_config.json",
1155
+ };
1156
+ const blockedAgents = [];
1157
+ if (configureIntegration) {
1158
+ if (scope === "personal" &&
1159
+ options.allowTrackedTransition &&
1160
+ priorIntegration?.scope === "team") {
1161
+ const cleanup = configureProjectAgents({
1162
+ repo: resolvedRepo,
1163
+ scope: "cli-only",
1164
+ agents: ["claude", "codex", "gemini", "copilot", "antigravity"],
1165
+ run: options.runAgentCommand,
1166
+ });
1167
+ if (cleanup.failed.length > 0)
1168
+ throw new Error(`knodin configure: could not remove tracked team integration: ${cleanup.failed[0]?.message}`);
1169
+ if (cleanup.removed.includes("claude"))
1170
+ externalConfigurationOutcomes.push({
1171
+ system: "claude-local-mcp",
1172
+ state: "removal-attempted",
1173
+ });
1174
+ }
1175
+ const eligibleAgents = scope === "team"
1176
+ ? requestedAgents
1177
+ : requestedAgents.filter((agent) => {
1178
+ const projectPath = agent === "claude" ? ".mcp.json" : projectPathByAgent[agent];
1179
+ if (!projectPath ||
1180
+ !isTracked(resolvedRepo, projectPath) ||
1181
+ (scope === "cli-only" && options.allowTrackedTransition))
1182
+ return true;
1183
+ if (options.allowTrackedTransition && priorIntegration?.scope === "team")
1184
+ return false;
1185
+ blockedAgents.push({
1186
+ agent,
1187
+ message: `${projectPath} is tracked; personal mode left it unchanged and did not exclude it`,
1188
+ });
1189
+ return false;
1190
+ });
1191
+ agentIntegration = configureProjectAgents({
1192
+ repo: resolvedRepo,
1193
+ scope,
1194
+ agents: eligibleAgents,
1195
+ run: options.runAgentCommand,
1155
1196
  });
1156
- }
1157
- const eligibleAgents = scope === "team"
1158
- ? requestedAgents
1159
- : requestedAgents.filter((agent) => {
1160
- const projectPath = agent === "claude" ? ".mcp.json" : projectPathByAgent[agent];
1161
- if (!projectPath ||
1162
- !isTracked(resolvedRepo, projectPath) ||
1163
- (scope === "cli-only" && options.allowTrackedTransition))
1164
- return true;
1165
- if (options.allowTrackedTransition && priorIntegration?.scope === "team")
1166
- return false;
1167
- blockedAgents.push({
1168
- agent,
1169
- message: `${projectPath} is tracked; personal mode left it unchanged and did not exclude it`,
1197
+ const skillClients = (scope === "team"
1198
+ ? ["codex", "claude", "gemini"]
1199
+ : agentIntegration.configured.filter((agent) => ["codex", "claude", "gemini"].includes(agent)));
1200
+ const skillIntegration = scope === "cli-only"
1201
+ ? removeKnodinSkills({
1202
+ repo: resolvedRepo,
1203
+ scope: "team",
1204
+ clients: ["codex", "claude", "gemini"],
1205
+ })
1206
+ : installKnodinSkills({
1207
+ repo: resolvedRepo,
1208
+ scope,
1209
+ clients: skillClients,
1210
+ });
1211
+ for (const issue of skillIntegration.issues)
1212
+ agentIntegration.failed.push({
1213
+ agent: skillClients.find((client) => issue.path.includes(`.${client}`)) ?? "codex",
1214
+ message: `${issue.path}: ${issue.message}`,
1215
+ });
1216
+ if (scope === "personal" && agentIntegration.configured.includes("claude"))
1217
+ externalConfigurationOutcomes.push({
1218
+ system: "claude-local-mcp",
1219
+ state: "configured-or-present",
1220
+ });
1221
+ if (scope === "cli-only" &&
1222
+ agentIntegration.removed.includes("claude") &&
1223
+ !externalConfigurationOutcomes.some(({ system, state }) => system === "claude-local-mcp" && state === "removal-attempted"))
1224
+ externalConfigurationOutcomes.push({
1225
+ system: "claude-local-mcp",
1226
+ state: "removal-attempted",
1227
+ });
1228
+ agentIntegration.failed.push(...blockedAgents);
1229
+ await writeRepositoryIntegrationConfig(resolvedRepo, {
1230
+ scope,
1231
+ agents: agentIntegration.configured,
1170
1232
  });
1171
- return false;
1172
- });
1173
- const agentIntegration = configureProjectAgents({
1174
- repo: resolvedRepo,
1175
- scope,
1176
- agents: eligibleAgents,
1177
- run: options.runAgentCommand,
1178
- });
1179
- if (scope === "personal" && agentIntegration.configured.includes("claude"))
1180
- externalConfigurationOutcomes.push({
1181
- system: "claude-local-mcp",
1182
- state: "configured-or-present",
1233
+ if (scope === "team") {
1234
+ await ensureTeamGitIgnore(resolvedRepo);
1235
+ await removeManagedExcludes(resolvedRepo);
1236
+ }
1237
+ else if (scope === "cli-only") {
1238
+ await removeManagedExcludes(resolvedRepo);
1239
+ }
1240
+ excluded =
1241
+ scope === "team"
1242
+ ? []
1243
+ : await excludeUntrackedPaths(resolvedRepo, [
1244
+ ...(isTracked(resolvedRepo, ".knodin") ? [] : [".knodin/"]),
1245
+ ...agentIntegration.projectFiles,
1246
+ ]);
1247
+ }
1248
+ if (!configureIntegration && !isTracked(resolvedRepo, ".knodin")) {
1249
+ excluded = await excludeUntrackedPaths(resolvedRepo, [".knodin/"]);
1250
+ }
1251
+ if (hookManagerIntegration) {
1252
+ const managerExcludes = await excludeUntrackedPaths(resolvedRepo, [
1253
+ hookManagerIntegration.localConfig,
1254
+ ]);
1255
+ excluded = [...new Set([...excluded, ...managerExcludes])].sort(compareBytes);
1256
+ }
1257
+ registerInitializedWorktree(resolvedRepo);
1258
+ // Configuration is graph-visible, so explicit configuration must complete
1259
+ // before the final index and health verification. Plain init reaches this
1260
+ // point without touching agent instructions, MCP files, skills, or receipts.
1261
+ const indexResult = await options.index(resolvedRepo, {
1262
+ onProgress: options.onProgress,
1183
1263
  });
1184
- if (scope === "cli-only" &&
1185
- agentIntegration.removed.includes("claude") &&
1186
- !externalConfigurationOutcomes.some(({ system, state }) => system === "claude-local-mcp" && state === "removal-attempted"))
1187
- externalConfigurationOutcomes.push({
1188
- system: "claude-local-mcp",
1189
- state: "removal-attempted",
1264
+ if (isIndexResult(indexResult) && indexResult.verification.status !== "healthy") {
1265
+ throw new InitializationHealthError(indexResult);
1266
+ }
1267
+ const lifecycleRefresh = drainQueuedLifecycleEvents(resolvedRepo, backgroundPath);
1268
+ await fs.promises.rm(path.join(knodinHooksDir, HOOK_FAILURE_FILE), {
1269
+ force: true,
1190
1270
  });
1191
- agentIntegration.failed.push(...blockedAgents);
1192
- await writeRepositoryIntegrationConfig(resolvedRepo, {
1193
- scope,
1194
- agents: agentIntegration.configured,
1195
- });
1196
- if (scope === "team") {
1197
- await ensureTeamGitIgnore(resolvedRepo);
1198
- await removeManagedExcludes(resolvedRepo);
1199
- }
1200
- else if (scope === "cli-only") {
1201
- await removeManagedExcludes(resolvedRepo);
1271
+ const detectedFilesystemChanges = filesystemChanges(beforeMutation, mutationSnapshot(mutationTargets, true));
1272
+ return {
1273
+ scope,
1274
+ knodin: ".knodin",
1275
+ database: ".knodin/db.sqlite",
1276
+ backgroundIndexer: ".knodin/hooks/background-index.sh",
1277
+ hooksDirectory: path.relative(resolvedRepo, hooksDir) || ".",
1278
+ gitHooks: [...HOOK_NAMES],
1279
+ claudeSettings: null,
1280
+ agentInstructions: scope === "team" ? "AGENTS.md" : null,
1281
+ agentIntegration,
1282
+ agentInstructionChanges,
1283
+ filesystemChanges: detectedFilesystemChanges,
1284
+ externalConfigurationOutcomes,
1285
+ excluded,
1286
+ lifecycleRefresh,
1287
+ hookManagerIntegration,
1288
+ };
1202
1289
  }
1203
- let excluded = scope === "team"
1204
- ? []
1205
- : await excludeUntrackedPaths(resolvedRepo, [
1206
- ...(isTracked(resolvedRepo, ".knodin") ? [] : [".knodin/"]),
1207
- ...agentIntegration.projectFiles,
1208
- ]);
1209
- if (hookManagerIntegration) {
1210
- const managerExcludes = await excludeUntrackedPaths(resolvedRepo, [
1211
- hookManagerIntegration.localConfig,
1212
- ]);
1213
- excluded = [...new Set([...excluded, ...managerExcludes])].sort(compareBytes);
1290
+ finally {
1291
+ lifecycleLease.release();
1214
1292
  }
1215
- registerInitializedWorktree(resolvedRepo);
1216
- const detectedFilesystemChanges = filesystemChanges(beforeMutation, mutationSnapshot(mutationTargets, true));
1217
- return {
1218
- scope,
1219
- knodin: ".knodin",
1220
- database: ".knodin/db.sqlite",
1221
- backgroundIndexer: ".knodin/hooks/background-index.sh",
1222
- hooksDirectory: path.relative(resolvedRepo, hooksDir) || ".",
1223
- gitHooks: [...HOOK_NAMES],
1224
- claudeSettings: null,
1225
- agentInstructions: scope === "team" ? "AGENTS.md" : null,
1226
- agentIntegration,
1227
- agentInstructionChanges,
1228
- filesystemChanges: detectedFilesystemChanges,
1229
- externalConfigurationOutcomes,
1230
- excluded,
1231
- lifecycleRefresh,
1232
- hookManagerIntegration,
1233
- };
1234
1293
  }
1235
1294
  async function matchesManagedFile(filePath, expected, executable) {
1236
1295
  try {