knodin 0.8.7 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/bin/cli.js +307 -85
  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 +35 -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 +355 -292
  26. package/dist/src/init.js +222 -179
  27. package/dist/src/manager-update.js +455 -0
  28. package/dist/src/release-preflight.js +106 -95
  29. package/dist/src/repair-lease.js +77 -8
  30. package/dist/src/response-budget.js +5 -1
  31. package/dist/src/server.js +18 -5
  32. package/dist/src/shared-index/compatibility.js +4 -2
  33. package/dist/src/tools/knodin-tools.js +69 -10
  34. package/dist/src/update-ceremony.js +20 -24
  35. package/dist/src/update-coordination.js +158 -0
  36. package/dist/src/update-executor.js +336 -0
  37. package/dist/src/update-verifier.js +358 -0
  38. package/docs/BACKUP-RETENTION.md +54 -0
  39. package/docs/CLI.md +6 -0
  40. package/docs/DOCTOR-AND-UPDATES.md +44 -12
  41. package/docs/SIGNED-UPDATES.md +29 -22
  42. package/docs/releases/0.9.0.md +43 -0
  43. package/package.json +7 -1
  44. package/roadmap/competitive-roadmap.md +8 -0
package/dist/src/init.js CHANGED
@@ -7,6 +7,7 @@ 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";
10
11
  import { installKnodinSkills, removeKnodinSkills } from "./skill-management.js";
11
12
  import { registerInitializedWorktree } from "./worktree-lifecycle.js";
12
13
  const MANAGED_MARKER = "KNODIN MANAGED HOOK";
@@ -153,7 +154,10 @@ function configureMutationAuditTargets(repo) {
153
154
  });
154
155
  }
155
156
  for (const hookName of HOOK_NAMES) {
156
- add({ absolute: path.join(effectiveHooks, hookName), label: `${effectiveLabel}/${hookName}` });
157
+ add({
158
+ absolute: path.join(effectiveHooks, hookName),
159
+ label: `${effectiveLabel}/${hookName}`,
160
+ });
157
161
  add({
158
162
  absolute: path.join(effectiveHooks, `${hookName}.knodin-original`),
159
163
  label: `${effectiveLabel}/${hookName}.knodin-original`,
@@ -385,11 +389,11 @@ while :; do
385
389
  ARG1="$(sed -n '2p' "$EVENT")"
386
390
  ARG2="$(sed -n '3p' "$EVENT")"
387
391
  if [ "$KIND" = "commit" ]; then
388
- ${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"
389
393
  elif [ "$KIND" = "checkout" ] || [ "$KIND" = "merge" ]; then
390
- ${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"
391
395
  elif [ "$KIND" = "rewrite" ]; then
392
- ${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"
393
397
  else
394
398
  printf 'knodin background refresh: invalid queued event %s\n' "$KIND" >> "$LOG_PATH"
395
399
  false
@@ -701,6 +705,7 @@ This repository is indexed locally by knodin. Start with \`knodin status\`; if i
701
705
  - Use gateway operations \`explain\`, \`review\`, and \`query\` for source-evidenced context. CLI fallbacks are \`knodin explain\`, \`knodin review --scope compare\`, and \`knodin rename\`.
702
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.
703
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.
704
709
  - Keep output truthful: local graph evidence can be stale or ambiguous; say so and repair/reindex instead of claiming certainty.
705
710
 
706
711
  <!-- knodin:end -->
@@ -960,9 +965,17 @@ export function inspectRepositoryIntegrationStatus(repo) {
960
965
  const receipt = readRepositoryIntegrationConfig(repo);
961
966
  const candidates = [
962
967
  { agent: "claude", path: ".mcp.json", marker: '"knodin"' },
963
- { 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
+ },
964
973
  { agent: "gemini", path: ".gemini/settings.json", marker: '"knodin"' },
965
- { agent: "antigravity", path: ".agents/mcp_config.json", marker: '"knodin"' },
974
+ {
975
+ agent: "antigravity",
976
+ path: ".agents/mcp_config.json",
977
+ marker: '"knodin"',
978
+ },
966
979
  {
967
980
  agent: "codex",
968
981
  path: "AGENTS.md",
@@ -1047,6 +1060,7 @@ export function detectTrackedTeamIntegration(repo) {
1047
1060
  export async function initializeRepository(repo, options) {
1048
1061
  const resolvedRepo = path.resolve(repo);
1049
1062
  const scope = options.scope ?? "personal";
1063
+ const configureIntegration = options.configureIntegration ?? true;
1050
1064
  const priorIntegration = readRepositoryIntegrationConfig(resolvedRepo);
1051
1065
  if (runGit(resolvedRepo, ["rev-parse", "--is-inside-work-tree"]).trim() !== "true") {
1052
1066
  throw new Error(`knodin init: not a Git worktree: ${resolvedRepo}`);
@@ -1065,188 +1079,217 @@ export async function initializeRepository(repo, options) {
1065
1079
  if (!path.isAbsolute(options.command[0])) {
1066
1080
  throw new Error("knodin init: packaged runtime command must be an absolute path");
1067
1081
  }
1068
- const mutationTargets = options.auditConfigurationChanges
1069
- ? configureMutationAuditTargets(resolvedRepo)
1070
- : [];
1071
- const beforeMutation = mutationSnapshot(mutationTargets);
1072
- if (scope !== "team" &&
1073
- !options.allowTrackedTransition &&
1074
- detectTrackedTeamIntegration(resolvedRepo)) {
1075
- 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");
1076
- }
1077
- const knodinHooksDir = path.join(resolvedRepo, ".knodin", "hooks");
1078
- const hookInstallation = await prepareHookInstallation(resolvedRepo);
1079
- const hooksDir = hookInstallation.hooksDir;
1080
- await Promise.all([
1081
- fs.promises.mkdir(knodinHooksDir, { recursive: true }),
1082
- fs.promises.mkdir(hooksDir, { recursive: true }),
1083
- ]);
1084
- const backgroundPath = path.join(knodinHooksDir, "background-index.sh");
1085
- await fs.promises.writeFile(backgroundPath, backgroundScript(options.command), {
1086
- encoding: "utf-8",
1087
- mode: 0o755,
1088
- });
1089
- await fs.promises.chmod(backgroundPath, 0o755);
1090
- const hookManagerIntegration = installHookManagerIntegration(resolvedRepo);
1091
- for (const hookName of HOOK_NAMES) {
1092
- let preservedOriginal;
1093
- if (hookInstallation.trackedHooksDir) {
1094
- const relativeHook = `${hookInstallation.trackedHooksDir}/${hookName}`;
1095
- const sourceHook = path.join(resolvedRepo, relativeHook);
1096
- try {
1097
- const stat = await fs.promises.stat(sourceHook);
1098
- preservedOriginal =
1099
- stat.isFile() && (stat.mode & 0o111) !== 0 ? `repo:${relativeHook}` : null;
1100
- }
1101
- catch {
1102
- preservedOriginal = null;
1103
- }
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");
1104
1093
  }
1105
- await installManagedHook(hooksDir, hookName, preservedOriginal);
1106
- }
1107
- for (const entry of await fs.promises.readdir(hooksDir, {
1108
- withFileTypes: true,
1109
- })) {
1110
- if (entry.isFile())
1111
- await preventLefthookAutoInstall(path.join(hooksDir, entry.name));
1112
- }
1113
- const indexResult = await options.index(resolvedRepo, {
1114
- onProgress: options.onProgress,
1115
- });
1116
- if (isIndexResult(indexResult) && indexResult.verification.status !== "healthy") {
1117
- throw new InitializationHealthError(indexResult);
1118
- }
1119
- const lifecycleRefresh = drainQueuedLifecycleEvents(resolvedRepo, backgroundPath);
1120
- await fs.promises.rm(path.join(knodinHooksDir, HOOK_FAILURE_FILE), { force: true });
1121
- let agentInstructionChanges = [];
1122
- if (scope === "team") {
1123
- const installed = await Promise.all([
1124
- installManagedAgentInstructions(resolvedRepo, "AGENTS.md"),
1125
- 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 }),
1126
1100
  ]);
1127
- agentInstructionChanges = installed.filter((change) => change !== null);
1128
- }
1129
- else {
1130
- agentInstructionChanges = await removeManagedPersonalArtifacts(resolvedRepo, options.allowTrackedTransition);
1131
- }
1132
- const requestedAgents = options.agents ?? [];
1133
- const projectPathByAgent = {
1134
- codex: ".codex/config.toml",
1135
- gemini: ".gemini/settings.json",
1136
- copilot: ".vscode/mcp.json",
1137
- antigravity: ".agents/mcp_config.json",
1138
- };
1139
- const blockedAgents = [];
1140
- const externalConfigurationOutcomes = [];
1141
- if (scope === "personal" &&
1142
- options.allowTrackedTransition &&
1143
- priorIntegration?.scope === "team") {
1144
- const cleanup = configureProjectAgents({
1145
- repo: resolvedRepo,
1146
- scope: "cli-only",
1147
- agents: ["claude", "codex", "gemini", "copilot", "antigravity"],
1148
- 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,
1149
1105
  });
1150
- if (cleanup.failed.length > 0)
1151
- throw new Error(`knodin configure: could not remove tracked team integration: ${cleanup.failed[0]?.message}`);
1152
- if (cleanup.removed.includes("claude"))
1153
- externalConfigurationOutcomes.push({
1154
- system: "claude-local-mcp",
1155
- 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,
1156
1196
  });
1157
- }
1158
- const eligibleAgents = scope === "team"
1159
- ? requestedAgents
1160
- : requestedAgents.filter((agent) => {
1161
- const projectPath = agent === "claude" ? ".mcp.json" : projectPathByAgent[agent];
1162
- if (!projectPath ||
1163
- !isTracked(resolvedRepo, projectPath) ||
1164
- (scope === "cli-only" && options.allowTrackedTransition))
1165
- return true;
1166
- if (options.allowTrackedTransition && priorIntegration?.scope === "team")
1167
- return false;
1168
- blockedAgents.push({
1169
- agent,
1170
- 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,
1171
1232
  });
1172
- return false;
1173
- });
1174
- const agentIntegration = configureProjectAgents({
1175
- repo: resolvedRepo,
1176
- scope,
1177
- agents: eligibleAgents,
1178
- run: options.runAgentCommand,
1179
- });
1180
- const skillClients = (scope === "team"
1181
- ? ["codex", "claude", "gemini"]
1182
- : agentIntegration.configured.filter((agent) => ["codex", "claude", "gemini"].includes(agent)));
1183
- const skillIntegration = scope === "cli-only"
1184
- ? removeKnodinSkills({
1185
- repo: resolvedRepo,
1186
- scope: "team",
1187
- clients: ["codex", "claude", "gemini"],
1188
- })
1189
- : installKnodinSkills({ repo: resolvedRepo, scope, clients: skillClients });
1190
- for (const issue of skillIntegration.issues)
1191
- agentIntegration.failed.push({
1192
- agent: skillClients.find((client) => issue.path.includes(`.${client}`)) ?? "codex",
1193
- message: `${issue.path}: ${issue.message}`,
1194
- });
1195
- if (scope === "personal" && agentIntegration.configured.includes("claude"))
1196
- externalConfigurationOutcomes.push({
1197
- system: "claude-local-mcp",
1198
- 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,
1199
1263
  });
1200
- if (scope === "cli-only" &&
1201
- agentIntegration.removed.includes("claude") &&
1202
- !externalConfigurationOutcomes.some(({ system, state }) => system === "claude-local-mcp" && state === "removal-attempted"))
1203
- externalConfigurationOutcomes.push({
1204
- system: "claude-local-mcp",
1205
- 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,
1206
1270
  });
1207
- agentIntegration.failed.push(...blockedAgents);
1208
- await writeRepositoryIntegrationConfig(resolvedRepo, {
1209
- scope,
1210
- agents: agentIntegration.configured,
1211
- });
1212
- if (scope === "team") {
1213
- await ensureTeamGitIgnore(resolvedRepo);
1214
- await removeManagedExcludes(resolvedRepo);
1215
- }
1216
- else if (scope === "cli-only") {
1217
- 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
+ };
1218
1289
  }
1219
- let excluded = scope === "team"
1220
- ? []
1221
- : await excludeUntrackedPaths(resolvedRepo, [
1222
- ...(isTracked(resolvedRepo, ".knodin") ? [] : [".knodin/"]),
1223
- ...agentIntegration.projectFiles,
1224
- ]);
1225
- if (hookManagerIntegration) {
1226
- const managerExcludes = await excludeUntrackedPaths(resolvedRepo, [
1227
- hookManagerIntegration.localConfig,
1228
- ]);
1229
- excluded = [...new Set([...excluded, ...managerExcludes])].sort(compareBytes);
1290
+ finally {
1291
+ lifecycleLease.release();
1230
1292
  }
1231
- registerInitializedWorktree(resolvedRepo);
1232
- const detectedFilesystemChanges = filesystemChanges(beforeMutation, mutationSnapshot(mutationTargets, true));
1233
- return {
1234
- scope,
1235
- knodin: ".knodin",
1236
- database: ".knodin/db.sqlite",
1237
- backgroundIndexer: ".knodin/hooks/background-index.sh",
1238
- hooksDirectory: path.relative(resolvedRepo, hooksDir) || ".",
1239
- gitHooks: [...HOOK_NAMES],
1240
- claudeSettings: null,
1241
- agentInstructions: scope === "team" ? "AGENTS.md" : null,
1242
- agentIntegration,
1243
- agentInstructionChanges,
1244
- filesystemChanges: detectedFilesystemChanges,
1245
- externalConfigurationOutcomes,
1246
- excluded,
1247
- lifecycleRefresh,
1248
- hookManagerIntegration,
1249
- };
1250
1293
  }
1251
1294
  async function matchesManagedFile(filePath, expected, executable) {
1252
1295
  try {