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.
- package/dist/bin/cli.js +341 -91
- package/dist/fixtures/update-verification/config/application.xml +4 -0
- package/dist/fixtures/update-verification/dbt/manifest.json +17 -0
- package/dist/fixtures/update-verification/dbt/models/accounts.sql +1 -0
- package/dist/fixtures/update-verification/dbt/models/contacts.sql +1 -0
- package/dist/fixtures/update-verification/fixture.json +147 -0
- package/dist/fixtures/update-verification/force-app/main/default/classes/UpdateFixture.cls +14 -0
- package/dist/fixtures/update-verification/force-app/main/default/flows/Update_Verification.flow-meta.xml +9 -0
- package/dist/fixtures/update-verification/package.json +5 -0
- package/dist/fixtures/update-verification/prisma/schema.prisma +10 -0
- package/dist/fixtures/update-verification/sql/functions.sql +16 -0
- package/dist/fixtures/update-verification/src/CsharpFixture.cs +6 -0
- package/dist/fixtures/update-verification/src/JavaFixture.java +9 -0
- package/dist/fixtures/update-verification/src/javascript.js +7 -0
- package/dist/fixtures/update-verification/src/python_fixture.py +6 -0
- package/dist/fixtures/update-verification/src/typescript.ts +7 -0
- package/dist/fixtures/update-verification/terraform/main.tf +7 -0
- package/dist/fixtures/update-verification/terraform/modules/verified-child/main.tf +3 -0
- package/dist/src/agent-integration.js +26 -3
- package/dist/src/backup-retention.js +811 -0
- package/dist/src/cli-model.js +49 -8
- package/dist/src/docs-sections.js +1 -0
- package/dist/src/doctor.js +142 -12
- package/dist/src/engine/embeddings.js +8 -1
- package/dist/src/engine/index.js +1272 -645
- package/dist/src/engine/parse-pool-resources.js +96 -0
- package/dist/src/engine/parse-pool.js +352 -0
- package/dist/src/engine/parse-protocol.js +1 -0
- package/dist/src/engine/parse-worker.js +51 -0
- package/dist/src/engine/reflink-copy.js +23 -0
- package/dist/src/engine/seal-command.js +116 -0
- package/dist/src/engine/seal.js +270 -0
- package/dist/src/engine/sealed-open.js +116 -0
- package/dist/src/engine/sealed-query.js +49 -0
- package/dist/src/init.js +251 -183
- package/dist/src/manager-update.js +455 -0
- package/dist/src/release-preflight.js +106 -95
- package/dist/src/repair-lease.js +77 -8
- package/dist/src/response-budget.js +5 -1
- package/dist/src/server.js +18 -5
- package/dist/src/shared-index/compatibility.js +4 -2
- package/dist/src/shared-index/selection.js +1 -1
- package/dist/src/tools/knodin-tools.js +85 -13
- package/dist/src/update-ceremony.js +20 -24
- package/dist/src/update-coordination.js +158 -0
- package/dist/src/update-executor.js +355 -0
- package/dist/src/update-verifier.js +358 -0
- package/dist/src/worktree-seed.js +172 -0
- package/docs/BACKUP-RETENTION.md +54 -0
- package/docs/CLI.md +6 -0
- package/docs/DOCTOR-AND-UPDATES.md +44 -12
- package/docs/SHARED-INDEX-CONTRACT.md +9 -0
- package/docs/SIGNED-UPDATES.md +29 -22
- package/docs/releases/0.10.0.md +127 -0
- package/docs/releases/0.9.0.md +43 -0
- package/package.json +8 -1
- 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";
|
|
@@ -42,6 +43,13 @@ function runGit(repo, args, encoding = "utf-8") {
|
|
|
42
43
|
cwd: repo,
|
|
43
44
|
encoding,
|
|
44
45
|
stdio: ["ignore", "pipe", "ignore"],
|
|
46
|
+
// A commit/checkout/merge touching many files (a large monorepo, an
|
|
47
|
+
// initial import, a mass rename) can produce a path list well past
|
|
48
|
+
// Node's 1 MiB execFileSync default, which throws rather than
|
|
49
|
+
// truncates. Match the 64 MiB bound already used for git output
|
|
50
|
+
// elsewhere in this codebase (see gitTrackedCandidates,
|
|
51
|
+
// execGitAbortable in src/engine/index.ts).
|
|
52
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
45
53
|
});
|
|
46
54
|
}
|
|
47
55
|
const MUTATION_AUDIT_MAX_ENTRIES = 2_048;
|
|
@@ -153,7 +161,10 @@ function configureMutationAuditTargets(repo) {
|
|
|
153
161
|
});
|
|
154
162
|
}
|
|
155
163
|
for (const hookName of HOOK_NAMES) {
|
|
156
|
-
add({
|
|
164
|
+
add({
|
|
165
|
+
absolute: path.join(effectiveHooks, hookName),
|
|
166
|
+
label: `${effectiveLabel}/${hookName}`,
|
|
167
|
+
});
|
|
157
168
|
add({
|
|
158
169
|
absolute: path.join(effectiveHooks, `${hookName}.knodin-original`),
|
|
159
170
|
label: `${effectiveLabel}/${hookName}.knodin-original`,
|
|
@@ -260,6 +271,24 @@ function diffPaths(repo, before, after) {
|
|
|
260
271
|
}
|
|
261
272
|
return nulPaths(repo, ["diff", "--name-only", before, after, "--"]);
|
|
262
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Runs a git-diff-shaped path computation and degrades to "no known changes"
|
|
276
|
+
* on failure instead of throwing. A lifecycle hook process crashing on an
|
|
277
|
+
* oversized or otherwise-failing git subprocess call has historically taken
|
|
278
|
+
* down the whole background refresh (exit code 1, nothing indexed at all);
|
|
279
|
+
* the per-query freshness guard and the next reconcile pass already detect
|
|
280
|
+
* and catch up any drift this misses, so failing soft here is strictly safer
|
|
281
|
+
* than failing hard.
|
|
282
|
+
*/
|
|
283
|
+
function gitPathsOrEmpty(compute, context) {
|
|
284
|
+
try {
|
|
285
|
+
return compute();
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
console.warn(`knodin: lifecycle hook refresh could not compute changed paths for ${context} (${error instanceof Error ? error.message : String(error)}); the next reconcile will catch up any drift.`);
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
263
292
|
/**
|
|
264
293
|
* Resolve changed paths from a Git lifecycle event and incrementally reconcile
|
|
265
294
|
* exactly the candidates accepted by the engine's full-repository policy.
|
|
@@ -270,17 +299,17 @@ export async function refreshFromGitEvent(repo, event, index) {
|
|
|
270
299
|
let rewriteInputToRemove;
|
|
271
300
|
try {
|
|
272
301
|
if (event.kind === "commit") {
|
|
273
|
-
changed = nulPaths(resolvedRepo, [
|
|
302
|
+
changed = gitPathsOrEmpty(() => nulPaths(resolvedRepo, [
|
|
274
303
|
"diff-tree",
|
|
275
304
|
"--root",
|
|
276
305
|
"--no-commit-id",
|
|
277
306
|
"--name-only",
|
|
278
307
|
"-r",
|
|
279
308
|
"HEAD",
|
|
280
|
-
]);
|
|
309
|
+
]), "a commit event");
|
|
281
310
|
}
|
|
282
311
|
else if (event.kind === "checkout" || event.kind === "merge") {
|
|
283
|
-
changed = diffPaths(resolvedRepo, event.before, event.after);
|
|
312
|
+
changed = gitPathsOrEmpty(() => diffPaths(resolvedRepo, event.before, event.after), `a ${event.kind} event`);
|
|
284
313
|
}
|
|
285
314
|
else {
|
|
286
315
|
const hooksRoot = path.join(resolvedRepo, ".knodin", "hooks");
|
|
@@ -312,7 +341,7 @@ export async function refreshFromGitEvent(repo, event, index) {
|
|
|
312
341
|
.map((line) => line.trim().split(/\s+/))
|
|
313
342
|
.filter((pair) => pair.length >= 2);
|
|
314
343
|
for (const [before, after] of pairs) {
|
|
315
|
-
changed.push(...diffPaths(resolvedRepo, before, after));
|
|
344
|
+
changed.push(...gitPathsOrEmpty(() => diffPaths(resolvedRepo, before, after), "a rewrite pair"));
|
|
316
345
|
}
|
|
317
346
|
}
|
|
318
347
|
const eligible = [...new Set(changed.map((file) => file.replaceAll("\\", "/")))]
|
|
@@ -385,11 +414,11 @@ while :; do
|
|
|
385
414
|
ARG1="$(sed -n '2p' "$EVENT")"
|
|
386
415
|
ARG2="$(sed -n '3p' "$EVENT")"
|
|
387
416
|
if [ "$KIND" = "commit" ]; then
|
|
388
|
-
${invocation} --repo "$REPO_ROOT" hook-refresh commit --json >> "$LOG_PATH" 2
|
|
417
|
+
${invocation} --repo "$REPO_ROOT" hook-refresh commit --json >> "$LOG_PATH" 2>> "$LOG_PATH.stderr"
|
|
389
418
|
elif [ "$KIND" = "checkout" ] || [ "$KIND" = "merge" ]; then
|
|
390
|
-
${invocation} --repo "$REPO_ROOT" hook-refresh "$KIND" "$ARG1" "$ARG2" --json >> "$LOG_PATH" 2
|
|
419
|
+
${invocation} --repo "$REPO_ROOT" hook-refresh "$KIND" "$ARG1" "$ARG2" --json >> "$LOG_PATH" 2>> "$LOG_PATH.stderr"
|
|
391
420
|
elif [ "$KIND" = "rewrite" ]; then
|
|
392
|
-
${invocation} --repo "$REPO_ROOT" hook-refresh rewrite "$ARG1" --json >> "$LOG_PATH" 2
|
|
421
|
+
${invocation} --repo "$REPO_ROOT" hook-refresh rewrite "$ARG1" --json >> "$LOG_PATH" 2>> "$LOG_PATH.stderr"
|
|
393
422
|
else
|
|
394
423
|
printf 'knodin background refresh: invalid queued event %s\n' "$KIND" >> "$LOG_PATH"
|
|
395
424
|
false
|
|
@@ -701,6 +730,7 @@ This repository is indexed locally by knodin. Start with \`knodin status\`; if i
|
|
|
701
730
|
- Use gateway operations \`explain\`, \`review\`, and \`query\` for source-evidenced context. CLI fallbacks are \`knodin explain\`, \`knodin review --scope compare\`, and \`knodin rename\`.
|
|
702
731
|
- 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
732
|
- 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.
|
|
733
|
+
- When indexing is active, run \`knodin wait --fresh\` and recheck status before falling back to direct traversal.
|
|
704
734
|
- Keep output truthful: local graph evidence can be stale or ambiguous; say so and repair/reindex instead of claiming certainty.
|
|
705
735
|
|
|
706
736
|
<!-- knodin:end -->
|
|
@@ -960,9 +990,17 @@ export function inspectRepositoryIntegrationStatus(repo) {
|
|
|
960
990
|
const receipt = readRepositoryIntegrationConfig(repo);
|
|
961
991
|
const candidates = [
|
|
962
992
|
{ agent: "claude", path: ".mcp.json", marker: '"knodin"' },
|
|
963
|
-
{
|
|
993
|
+
{
|
|
994
|
+
agent: "codex",
|
|
995
|
+
path: ".codex/config.toml",
|
|
996
|
+
marker: 'mcp_servers."knodin"',
|
|
997
|
+
},
|
|
964
998
|
{ agent: "gemini", path: ".gemini/settings.json", marker: '"knodin"' },
|
|
965
|
-
{
|
|
999
|
+
{
|
|
1000
|
+
agent: "antigravity",
|
|
1001
|
+
path: ".agents/mcp_config.json",
|
|
1002
|
+
marker: '"knodin"',
|
|
1003
|
+
},
|
|
966
1004
|
{
|
|
967
1005
|
agent: "codex",
|
|
968
1006
|
path: "AGENTS.md",
|
|
@@ -1047,6 +1085,7 @@ export function detectTrackedTeamIntegration(repo) {
|
|
|
1047
1085
|
export async function initializeRepository(repo, options) {
|
|
1048
1086
|
const resolvedRepo = path.resolve(repo);
|
|
1049
1087
|
const scope = options.scope ?? "personal";
|
|
1088
|
+
const configureIntegration = options.configureIntegration ?? true;
|
|
1050
1089
|
const priorIntegration = readRepositoryIntegrationConfig(resolvedRepo);
|
|
1051
1090
|
if (runGit(resolvedRepo, ["rev-parse", "--is-inside-work-tree"]).trim() !== "true") {
|
|
1052
1091
|
throw new Error(`knodin init: not a Git worktree: ${resolvedRepo}`);
|
|
@@ -1065,188 +1104,217 @@ export async function initializeRepository(repo, options) {
|
|
|
1065
1104
|
if (!path.isAbsolute(options.command[0])) {
|
|
1066
1105
|
throw new Error("knodin init: packaged runtime command must be an absolute path");
|
|
1067
1106
|
}
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
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
|
-
}
|
|
1107
|
+
const lifecycleLease = acquireRepairLease(resolvedRepo, options.configureIntegration ? "configure" : "init");
|
|
1108
|
+
try {
|
|
1109
|
+
const mutationTargets = options.auditConfigurationChanges
|
|
1110
|
+
? configureMutationAuditTargets(resolvedRepo)
|
|
1111
|
+
: [];
|
|
1112
|
+
const beforeMutation = mutationSnapshot(mutationTargets);
|
|
1113
|
+
if (configureIntegration &&
|
|
1114
|
+
scope !== "team" &&
|
|
1115
|
+
!options.allowTrackedTransition &&
|
|
1116
|
+
detectTrackedTeamIntegration(resolvedRepo)) {
|
|
1117
|
+
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
1118
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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"),
|
|
1119
|
+
const knodinHooksDir = path.join(resolvedRepo, ".knodin", "hooks");
|
|
1120
|
+
const hookInstallation = await prepareHookInstallation(resolvedRepo);
|
|
1121
|
+
const hooksDir = hookInstallation.hooksDir;
|
|
1122
|
+
await Promise.all([
|
|
1123
|
+
fs.promises.mkdir(knodinHooksDir, { recursive: true }),
|
|
1124
|
+
fs.promises.mkdir(hooksDir, { recursive: true }),
|
|
1126
1125
|
]);
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
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,
|
|
1126
|
+
const backgroundPath = path.join(knodinHooksDir, "background-index.sh");
|
|
1127
|
+
await fs.promises.writeFile(backgroundPath, backgroundScript(options.command), {
|
|
1128
|
+
encoding: "utf-8",
|
|
1129
|
+
mode: 0o755,
|
|
1149
1130
|
});
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1131
|
+
await fs.promises.chmod(backgroundPath, 0o755);
|
|
1132
|
+
const hookManagerIntegration = installHookManagerIntegration(resolvedRepo);
|
|
1133
|
+
for (const hookName of HOOK_NAMES) {
|
|
1134
|
+
let preservedOriginal;
|
|
1135
|
+
if (hookInstallation.trackedHooksDir) {
|
|
1136
|
+
const relativeHook = `${hookInstallation.trackedHooksDir}/${hookName}`;
|
|
1137
|
+
const sourceHook = path.join(resolvedRepo, relativeHook);
|
|
1138
|
+
try {
|
|
1139
|
+
const stat = await fs.promises.stat(sourceHook);
|
|
1140
|
+
preservedOriginal =
|
|
1141
|
+
stat.isFile() && (stat.mode & 0o111) !== 0 ? `repo:${relativeHook}` : null;
|
|
1142
|
+
}
|
|
1143
|
+
catch {
|
|
1144
|
+
preservedOriginal = null;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
await installManagedHook(hooksDir, hookName, preservedOriginal);
|
|
1148
|
+
}
|
|
1149
|
+
for (const entry of await fs.promises.readdir(hooksDir, {
|
|
1150
|
+
withFileTypes: true,
|
|
1151
|
+
})) {
|
|
1152
|
+
if (entry.isFile())
|
|
1153
|
+
await preventLefthookAutoInstall(path.join(hooksDir, entry.name));
|
|
1154
|
+
}
|
|
1155
|
+
let agentInstructionChanges = [];
|
|
1156
|
+
let agentIntegration = {
|
|
1157
|
+
configured: [],
|
|
1158
|
+
removed: [],
|
|
1159
|
+
failed: [],
|
|
1160
|
+
projectFiles: [],
|
|
1161
|
+
};
|
|
1162
|
+
const externalConfigurationOutcomes = [];
|
|
1163
|
+
let excluded = [];
|
|
1164
|
+
if (configureIntegration && scope === "team") {
|
|
1165
|
+
const installed = await Promise.all([
|
|
1166
|
+
installManagedAgentInstructions(resolvedRepo, "AGENTS.md"),
|
|
1167
|
+
installManagedAgentInstructions(resolvedRepo, "GEMINI.md"),
|
|
1168
|
+
]);
|
|
1169
|
+
agentInstructionChanges = installed.filter((change) => change !== null);
|
|
1170
|
+
}
|
|
1171
|
+
else if (configureIntegration) {
|
|
1172
|
+
agentInstructionChanges = await removeManagedPersonalArtifacts(resolvedRepo, options.allowTrackedTransition);
|
|
1173
|
+
}
|
|
1174
|
+
const requestedAgents = options.agents ?? [];
|
|
1175
|
+
const projectPathByAgent = {
|
|
1176
|
+
codex: ".codex/config.toml",
|
|
1177
|
+
gemini: ".gemini/settings.json",
|
|
1178
|
+
copilot: ".vscode/mcp.json",
|
|
1179
|
+
antigravity: ".agents/mcp_config.json",
|
|
1180
|
+
};
|
|
1181
|
+
const blockedAgents = [];
|
|
1182
|
+
if (configureIntegration) {
|
|
1183
|
+
if (scope === "personal" &&
|
|
1184
|
+
options.allowTrackedTransition &&
|
|
1185
|
+
priorIntegration?.scope === "team") {
|
|
1186
|
+
const cleanup = configureProjectAgents({
|
|
1187
|
+
repo: resolvedRepo,
|
|
1188
|
+
scope: "cli-only",
|
|
1189
|
+
agents: ["claude", "codex", "gemini", "copilot", "antigravity"],
|
|
1190
|
+
run: options.runAgentCommand,
|
|
1191
|
+
});
|
|
1192
|
+
if (cleanup.failed.length > 0)
|
|
1193
|
+
throw new Error(`knodin configure: could not remove tracked team integration: ${cleanup.failed[0]?.message}`);
|
|
1194
|
+
if (cleanup.removed.includes("claude"))
|
|
1195
|
+
externalConfigurationOutcomes.push({
|
|
1196
|
+
system: "claude-local-mcp",
|
|
1197
|
+
state: "removal-attempted",
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
const eligibleAgents = scope === "team"
|
|
1201
|
+
? requestedAgents
|
|
1202
|
+
: requestedAgents.filter((agent) => {
|
|
1203
|
+
const projectPath = agent === "claude" ? ".mcp.json" : projectPathByAgent[agent];
|
|
1204
|
+
if (!projectPath ||
|
|
1205
|
+
!isTracked(resolvedRepo, projectPath) ||
|
|
1206
|
+
(scope === "cli-only" && options.allowTrackedTransition))
|
|
1207
|
+
return true;
|
|
1208
|
+
if (options.allowTrackedTransition && priorIntegration?.scope === "team")
|
|
1209
|
+
return false;
|
|
1210
|
+
blockedAgents.push({
|
|
1211
|
+
agent,
|
|
1212
|
+
message: `${projectPath} is tracked; personal mode left it unchanged and did not exclude it`,
|
|
1213
|
+
});
|
|
1214
|
+
return false;
|
|
1215
|
+
});
|
|
1216
|
+
agentIntegration = configureProjectAgents({
|
|
1217
|
+
repo: resolvedRepo,
|
|
1218
|
+
scope,
|
|
1219
|
+
agents: eligibleAgents,
|
|
1220
|
+
run: options.runAgentCommand,
|
|
1156
1221
|
});
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1222
|
+
const skillClients = (scope === "team"
|
|
1223
|
+
? ["codex", "claude", "gemini"]
|
|
1224
|
+
: agentIntegration.configured.filter((agent) => ["codex", "claude", "gemini"].includes(agent)));
|
|
1225
|
+
const skillIntegration = scope === "cli-only"
|
|
1226
|
+
? removeKnodinSkills({
|
|
1227
|
+
repo: resolvedRepo,
|
|
1228
|
+
scope: "team",
|
|
1229
|
+
clients: ["codex", "claude", "gemini"],
|
|
1230
|
+
})
|
|
1231
|
+
: installKnodinSkills({
|
|
1232
|
+
repo: resolvedRepo,
|
|
1233
|
+
scope,
|
|
1234
|
+
clients: skillClients,
|
|
1235
|
+
});
|
|
1236
|
+
for (const issue of skillIntegration.issues)
|
|
1237
|
+
agentIntegration.failed.push({
|
|
1238
|
+
agent: skillClients.find((client) => issue.path.includes(`.${client}`)) ?? "codex",
|
|
1239
|
+
message: `${issue.path}: ${issue.message}`,
|
|
1240
|
+
});
|
|
1241
|
+
if (scope === "personal" && agentIntegration.configured.includes("claude"))
|
|
1242
|
+
externalConfigurationOutcomes.push({
|
|
1243
|
+
system: "claude-local-mcp",
|
|
1244
|
+
state: "configured-or-present",
|
|
1245
|
+
});
|
|
1246
|
+
if (scope === "cli-only" &&
|
|
1247
|
+
agentIntegration.removed.includes("claude") &&
|
|
1248
|
+
!externalConfigurationOutcomes.some(({ system, state }) => system === "claude-local-mcp" && state === "removal-attempted"))
|
|
1249
|
+
externalConfigurationOutcomes.push({
|
|
1250
|
+
system: "claude-local-mcp",
|
|
1251
|
+
state: "removal-attempted",
|
|
1252
|
+
});
|
|
1253
|
+
agentIntegration.failed.push(...blockedAgents);
|
|
1254
|
+
await writeRepositoryIntegrationConfig(resolvedRepo, {
|
|
1255
|
+
scope,
|
|
1256
|
+
agents: agentIntegration.configured,
|
|
1171
1257
|
});
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1258
|
+
if (scope === "team") {
|
|
1259
|
+
await ensureTeamGitIgnore(resolvedRepo);
|
|
1260
|
+
await removeManagedExcludes(resolvedRepo);
|
|
1261
|
+
}
|
|
1262
|
+
else if (scope === "cli-only") {
|
|
1263
|
+
await removeManagedExcludes(resolvedRepo);
|
|
1264
|
+
}
|
|
1265
|
+
excluded =
|
|
1266
|
+
scope === "team"
|
|
1267
|
+
? []
|
|
1268
|
+
: await excludeUntrackedPaths(resolvedRepo, [
|
|
1269
|
+
...(isTracked(resolvedRepo, ".knodin") ? [] : [".knodin/"]),
|
|
1270
|
+
...agentIntegration.projectFiles,
|
|
1271
|
+
]);
|
|
1272
|
+
}
|
|
1273
|
+
if (!configureIntegration && !isTracked(resolvedRepo, ".knodin")) {
|
|
1274
|
+
excluded = await excludeUntrackedPaths(resolvedRepo, [".knodin/"]);
|
|
1275
|
+
}
|
|
1276
|
+
if (hookManagerIntegration) {
|
|
1277
|
+
const managerExcludes = await excludeUntrackedPaths(resolvedRepo, [
|
|
1278
|
+
hookManagerIntegration.localConfig,
|
|
1279
|
+
]);
|
|
1280
|
+
excluded = [...new Set([...excluded, ...managerExcludes])].sort(compareBytes);
|
|
1281
|
+
}
|
|
1282
|
+
registerInitializedWorktree(resolvedRepo);
|
|
1283
|
+
// Configuration is graph-visible, so explicit configuration must complete
|
|
1284
|
+
// before the final index and health verification. Plain init reaches this
|
|
1285
|
+
// point without touching agent instructions, MCP files, skills, or receipts.
|
|
1286
|
+
const indexResult = await options.index(resolvedRepo, {
|
|
1287
|
+
onProgress: options.onProgress,
|
|
1199
1288
|
});
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1289
|
+
if (isIndexResult(indexResult) && indexResult.verification.status !== "healthy") {
|
|
1290
|
+
throw new InitializationHealthError(indexResult);
|
|
1291
|
+
}
|
|
1292
|
+
const lifecycleRefresh = drainQueuedLifecycleEvents(resolvedRepo, backgroundPath);
|
|
1293
|
+
await fs.promises.rm(path.join(knodinHooksDir, HOOK_FAILURE_FILE), {
|
|
1294
|
+
force: true,
|
|
1206
1295
|
});
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1296
|
+
const detectedFilesystemChanges = filesystemChanges(beforeMutation, mutationSnapshot(mutationTargets, true));
|
|
1297
|
+
return {
|
|
1298
|
+
scope,
|
|
1299
|
+
knodin: ".knodin",
|
|
1300
|
+
database: ".knodin/db.sqlite",
|
|
1301
|
+
backgroundIndexer: ".knodin/hooks/background-index.sh",
|
|
1302
|
+
hooksDirectory: path.relative(resolvedRepo, hooksDir) || ".",
|
|
1303
|
+
gitHooks: [...HOOK_NAMES],
|
|
1304
|
+
claudeSettings: null,
|
|
1305
|
+
agentInstructions: scope === "team" ? "AGENTS.md" : null,
|
|
1306
|
+
agentIntegration,
|
|
1307
|
+
agentInstructionChanges,
|
|
1308
|
+
filesystemChanges: detectedFilesystemChanges,
|
|
1309
|
+
externalConfigurationOutcomes,
|
|
1310
|
+
excluded,
|
|
1311
|
+
lifecycleRefresh,
|
|
1312
|
+
hookManagerIntegration,
|
|
1313
|
+
};
|
|
1215
1314
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
}
|
|
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);
|
|
1315
|
+
finally {
|
|
1316
|
+
lifecycleLease.release();
|
|
1230
1317
|
}
|
|
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
1318
|
}
|
|
1251
1319
|
async function matchesManagedFile(filePath, expected, executable) {
|
|
1252
1320
|
try {
|