knodin 0.7.6 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -7
- package/benchmarks/competitors/SYNTHESIS.md +66 -0
- package/dist/bin/cli.js +2164 -108
- package/dist/bin/launcher.js +25 -3
- package/dist/src/agent-integration.js +304 -0
- package/dist/src/artifact-refresh.js +82 -0
- package/dist/src/cli-args.js +292 -0
- package/dist/src/cli-model.js +384 -0
- package/dist/src/codeflow-replay.js +81 -0
- package/dist/src/compact-structural.js +96 -0
- package/dist/src/compare.js +39 -0
- package/dist/src/competitive-cold-mcp.js +40 -0
- package/dist/src/competitive-constraints.js +21 -0
- package/dist/src/competitive-manifest.js +411 -0
- package/dist/src/competitive-measurement.js +183 -0
- package/dist/src/competitive-runner.js +487 -0
- package/dist/src/competitive-sandbox.js +108 -0
- package/dist/src/context-export.js +423 -0
- package/dist/src/context.js +102 -0
- package/dist/src/deterministic-random.js +34 -0
- package/dist/src/diagnostics-write-helper.js +473 -0
- package/dist/src/diagnostics.js +1476 -0
- package/dist/src/docs-sections.js +141 -0
- package/dist/src/doctor.js +382 -0
- package/dist/src/engine/ann-hnsw.js +261 -0
- package/dist/src/engine/embeddings.js +193 -0
- package/dist/src/engine/file-walker.js +49 -0
- package/dist/src/engine/git-history.js +289 -0
- package/dist/src/engine/index.js +14238 -0
- package/dist/src/engine/perf.js +115 -0
- package/dist/src/engine/prune.js +112 -0
- package/dist/src/engine/sarif-import.js +341 -0
- package/dist/src/engine/scip-import.js +423 -0
- package/dist/src/engine/source-policy.js +85 -0
- package/dist/src/engine/sqlite.js +71 -0
- package/dist/src/engine/state-paths.js +175 -0
- package/dist/src/engine/symbol-delete.js +58 -0
- package/dist/src/execution-profile.js +208 -0
- package/dist/src/failure-diagnosis.js +655 -0
- package/dist/src/fleet.js +7 -0
- package/dist/src/git-executable.js +31 -0
- package/dist/src/graph-layout.js +173 -0
- package/dist/src/graph-query-health.js +115 -0
- package/dist/src/hook-manager-integration.js +156 -0
- package/dist/src/index-activity.js +126 -0
- package/dist/src/init-progress-worker.js +106 -2
- package/dist/src/init-progress.js +155 -0
- package/dist/src/init.js +1295 -0
- package/dist/src/lifecycle-health.js +282 -0
- package/dist/src/lsp-readonly.js +217 -0
- package/dist/src/mcp-graph-worker.js +69 -0
- package/dist/src/mcp-reliability.js +154 -0
- package/dist/src/mcp-worker-supervisor.js +350 -0
- package/dist/src/mirror.js +290 -0
- package/dist/src/node-runtime.js +157 -0
- package/dist/src/output-compression.js +630 -0
- package/dist/src/output-telemetry.js +368 -0
- package/dist/src/pr-triage.js +638 -0
- package/dist/src/progressive-evidence.js +477 -0
- package/dist/src/pure-compression-cli.js +102 -0
- package/dist/src/relationship-adapters.js +377 -0
- package/dist/src/release-attestation.js +533 -0
- package/dist/src/release-preflight.js +513 -0
- package/dist/src/repair-lease.js +85 -0
- package/dist/src/repair-progress-worker.js +120 -2
- package/dist/src/repair-progress.js +262 -0
- package/dist/src/repository-init-process.js +177 -0
- package/dist/src/repository-management.js +1261 -0
- package/dist/src/response-budget.js +196 -0
- package/dist/src/server.js +217 -0
- package/dist/src/structural-fast-path.js +344 -0
- package/dist/src/structural-snapshot.js +37 -0
- package/dist/src/system-config.js +638 -0
- package/dist/src/terminal-help.js +83 -0
- package/dist/src/tools/knodin-tools.js +1640 -0
- package/dist/src/update-ceremony.js +162 -0
- package/dist/src/update-policy.js +944 -0
- package/dist/src/update-trust.js +504 -0
- package/dist/src/version.js +13 -0
- package/dist/src/visualization.js +515 -0
- package/dist/src/wait-for-fresh.js +98 -0
- package/dist/src/worktree-lifecycle.js +234 -0
- package/docs/BEHAVIORAL-CONTRACT.md +72 -0
- package/docs/CLI.md +20 -1
- package/docs/COMPARISON.md +403 -0
- package/docs/COMPETITIVE-LANDSCAPE-2026-08.md +267 -0
- package/docs/CONTAINED-EXECUTION.md +77 -0
- package/docs/DIAGNOSTICS.md +80 -0
- package/docs/GIT-HISTORY-REVIEW.md +39 -0
- package/docs/HANDOFF.md +180 -0
- package/docs/INSTALLATION.md +21 -18
- package/docs/MCP.md +59 -8
- package/docs/PROGRESSIVE-EVIDENCE.md +37 -0
- package/docs/PT-ACCESS-RECOMMENDATION.md +89 -0
- package/docs/RELEASE-0.3-EVIDENCE.md +73 -0
- package/docs/REPOSITORIES-AND-WORKTREES.md +18 -6
- package/docs/SCIP-IMPORT.md +62 -0
- package/docs/SIGNED-UPDATES.md +151 -0
- package/docs/TELEMETRY.md +46 -0
- package/docs/TOKEN-OPTIMIZER-SCORECARD.md +79 -0
- package/docs/assets/knodin-favicon.svg +4 -0
- package/docs/releases/0.3.0.md +46 -0
- package/docs/releases/0.4.0.md +68 -0
- package/docs/releases/0.4.1.md +28 -0
- package/docs/releases/0.4.2.md +27 -0
- package/docs/releases/0.4.3.md +23 -0
- package/docs/releases/0.5.0.md +29 -0
- package/docs/releases/0.5.1.md +17 -0
- package/docs/releases/0.6.0.md +18 -0
- package/docs/releases/0.7.0.md +24 -0
- package/docs/releases/0.7.1.md +21 -0
- package/docs/releases/0.7.2.md +21 -0
- package/docs/releases/0.7.3.md +23 -0
- package/docs/releases/0.7.4.md +17 -0
- package/docs/releases/0.7.5.md +20 -0
- package/docs/releases/0.8.0.md +74 -0
- package/docs/releases/0.8.2.md +34 -0
- package/package.json +127 -4
- package/roadmap/competitive-roadmap.md +3801 -0
- package/schemas/release-attestation-v1.schema.json +210 -0
- package/schemas/support-bundle-v2.schema.json +212 -0
- package/dist/chunks/chunk-DMQAGX77.js +0 -654
- package/dist/chunks/chunk-F4Z3Z766.js +0 -4
- package/dist/chunks/chunk-SIJAQVSX.js +0 -3
- package/dist/chunks/chunk-X6M4HUUE.js +0 -2
- package/dist/chunks/chunk-YPRMY2LP.js +0 -8
- package/dist/chunks/pure-compression-cli-4TA2TQD5.js +0 -5
- package/dist/chunks/server-7EDF4CBY.js +0 -14
- package/dist/chunks/structural-fast-path-KD5KQSPX.js +0 -4
- package/docs/releases/0.7.6.md +0 -25
package/dist/bin/launcher.js
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { handoffCurrentProcessToSupportedNodeRuntime } from "../src/node-runtime.js";
|
|
3
|
+
let runtime;
|
|
4
|
+
try {
|
|
5
|
+
runtime = handoffCurrentProcessToSupportedNodeRuntime();
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9
|
+
process.stderr.write(`knodin: ${message}\n`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
if (runtime.kind === "relaunched") {
|
|
13
|
+
if (runtime.signal)
|
|
14
|
+
process.kill(process.pid, runtime.signal);
|
|
15
|
+
process.exit(runtime.status ?? 1);
|
|
16
|
+
}
|
|
17
|
+
const argv = process.argv.slice(2);
|
|
18
|
+
const { tryStructuralFastPath } = await import("../src/structural-fast-path.js");
|
|
19
|
+
const fast = tryStructuralFastPath(argv);
|
|
20
|
+
if (!fast) {
|
|
21
|
+
const { tryPureCompressionFastPath } = await import("../src/pure-compression-cli.js");
|
|
22
|
+
if (!(await tryPureCompressionFastPath(argv)))
|
|
23
|
+
await import("./cli.js");
|
|
24
|
+
}
|
|
25
|
+
if (process.env.KNODIN_BENCH_PROCESS_UPTIME === "1")
|
|
26
|
+
process.stderr.write(`KNODIN_PROCESS_UPTIME_MS=${(process.uptime() * 1000).toFixed(3)}\n`);
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import child_process from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export function parseInitScope(value) {
|
|
5
|
+
if (value === "personal" || value === "team" || value === "cli-only")
|
|
6
|
+
return value;
|
|
7
|
+
throw new Error("knodin: --scope must be personal, team, or cli-only");
|
|
8
|
+
}
|
|
9
|
+
const MCP_NAME = "knodin";
|
|
10
|
+
const TOML_START = "# knodin:start";
|
|
11
|
+
const TOML_END = "# knodin:end";
|
|
12
|
+
const TOML_BLOCK = `${TOML_START}
|
|
13
|
+
[mcp_servers."${MCP_NAME}"]
|
|
14
|
+
command = "knodin"
|
|
15
|
+
args = ["serve"]
|
|
16
|
+
${TOML_END}`;
|
|
17
|
+
function errorMessage(error) {
|
|
18
|
+
return error instanceof Error ? error.message : String(error);
|
|
19
|
+
}
|
|
20
|
+
function commandPath(command) {
|
|
21
|
+
try {
|
|
22
|
+
const resolver = process.platform === "win32" ? "where.exe" : "which";
|
|
23
|
+
return (child_process
|
|
24
|
+
.execFileSync(resolver, [command], {
|
|
25
|
+
encoding: "utf8",
|
|
26
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
27
|
+
})
|
|
28
|
+
.split(/\r?\n/, 1)[0]
|
|
29
|
+
?.trim() || null);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function hasGitHubCopilotExtension() {
|
|
36
|
+
const code = commandPath("code");
|
|
37
|
+
if (!code)
|
|
38
|
+
return false;
|
|
39
|
+
try {
|
|
40
|
+
const extensions = child_process.execFileSync(code, ["--list-extensions"], {
|
|
41
|
+
encoding: "utf8",
|
|
42
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
43
|
+
});
|
|
44
|
+
return extensions
|
|
45
|
+
.split(/\r?\n/)
|
|
46
|
+
.some((extension) => ["github.copilot", "github.copilot-chat"].includes(extension.toLowerCase()));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Detect primary MCP-capable coding agents without launching them. */
|
|
53
|
+
export function detectSupportedAgents() {
|
|
54
|
+
const detected = [];
|
|
55
|
+
if (commandPath("claude"))
|
|
56
|
+
detected.push("claude");
|
|
57
|
+
if (commandPath("codex"))
|
|
58
|
+
detected.push("codex");
|
|
59
|
+
if (commandPath("gemini"))
|
|
60
|
+
detected.push("gemini");
|
|
61
|
+
if (hasGitHubCopilotExtension())
|
|
62
|
+
detected.push("copilot");
|
|
63
|
+
if (commandPath("antigravity") || commandPath("agy"))
|
|
64
|
+
detected.push("antigravity");
|
|
65
|
+
return detected;
|
|
66
|
+
}
|
|
67
|
+
function defaultRunner(command) {
|
|
68
|
+
try {
|
|
69
|
+
child_process.execFileSync(command.executable, command.args, {
|
|
70
|
+
cwd: command.cwd,
|
|
71
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (command.executable !== "claude" || !command.args.includes("add"))
|
|
76
|
+
throw error;
|
|
77
|
+
child_process.execFileSync("claude", ["mcp", "get", MCP_NAME], {
|
|
78
|
+
cwd: command.cwd,
|
|
79
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Configure command-backed personal integrations. Claude stores local scope
|
|
85
|
+
* outside the checkout; Codex/Gemini/Antigravity use excluded project files.
|
|
86
|
+
*/
|
|
87
|
+
export function configurePersonalAgents(options) {
|
|
88
|
+
const run = options.run ?? defaultRunner;
|
|
89
|
+
const configured = [];
|
|
90
|
+
const removed = [];
|
|
91
|
+
const failed = [];
|
|
92
|
+
for (const agent of options.detected) {
|
|
93
|
+
if (agent !== "claude")
|
|
94
|
+
continue;
|
|
95
|
+
const command = options.remove
|
|
96
|
+
? {
|
|
97
|
+
executable: "claude",
|
|
98
|
+
args: ["mcp", "remove", MCP_NAME, "--scope", "local"],
|
|
99
|
+
}
|
|
100
|
+
: {
|
|
101
|
+
executable: "claude",
|
|
102
|
+
args: ["mcp", "add", MCP_NAME, "--scope", "local", "--", "knodin", "serve"],
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
run(command);
|
|
106
|
+
(options.remove ? removed : configured).push(agent);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
// Claude reports a missing local registration as a remove failure. Keep
|
|
110
|
+
// this backward-compatible result conservative at the presentation layer:
|
|
111
|
+
// it means removal was attempted, not that absence was verified.
|
|
112
|
+
if (options.remove)
|
|
113
|
+
removed.push(agent);
|
|
114
|
+
else
|
|
115
|
+
failed.push({ agent, message: errorMessage(error) });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { configured, removed, failed, projectFiles: [] };
|
|
119
|
+
}
|
|
120
|
+
function readJsonObject(filePath) {
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
123
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
124
|
+
throw new Error("expected a JSON object");
|
|
125
|
+
return parsed;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (error.code === "ENOENT")
|
|
129
|
+
return {};
|
|
130
|
+
throw new Error(`${filePath}: ${errorMessage(error)}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function assertSafeProjectPath(repo, filePath) {
|
|
134
|
+
const relative = path.relative(repo, filePath);
|
|
135
|
+
if (relative.startsWith("..") || path.isAbsolute(relative))
|
|
136
|
+
throw new Error(`${filePath}: agent configuration must stay inside the repository`);
|
|
137
|
+
let current = repo;
|
|
138
|
+
for (const part of relative.split(path.sep)) {
|
|
139
|
+
current = path.join(current, part);
|
|
140
|
+
try {
|
|
141
|
+
if (fs.lstatSync(current).isSymbolicLink())
|
|
142
|
+
throw new Error(`${filePath}: refusing to follow a symbolic link`);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (error.code === "ENOENT")
|
|
146
|
+
break;
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function formatProjectJson(document) {
|
|
152
|
+
return `${JSON.stringify(document, null, "\t").replace(/"args": \[\n(\t+)"serve"\n\t+\]/g, '"args": ["serve"]')}\n`;
|
|
153
|
+
}
|
|
154
|
+
function removeEmptyParent(filePath) {
|
|
155
|
+
try {
|
|
156
|
+
fs.rmdirSync(path.dirname(filePath));
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? ""))
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function updateJsonMcp(filePath, remove) {
|
|
164
|
+
const document = readJsonObject(filePath);
|
|
165
|
+
const existing = document.mcpServers;
|
|
166
|
+
const servers = typeof existing === "object" && existing !== null && !Array.isArray(existing)
|
|
167
|
+
? { ...existing }
|
|
168
|
+
: {};
|
|
169
|
+
if (remove)
|
|
170
|
+
delete servers[MCP_NAME];
|
|
171
|
+
else
|
|
172
|
+
servers[MCP_NAME] = { command: "knodin", args: ["serve"] };
|
|
173
|
+
if (Object.keys(servers).length > 0)
|
|
174
|
+
document.mcpServers = servers;
|
|
175
|
+
else
|
|
176
|
+
delete document.mcpServers;
|
|
177
|
+
if (remove && Object.keys(document).length === 0) {
|
|
178
|
+
fs.rmSync(filePath, { force: true });
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
182
|
+
fs.writeFileSync(filePath, formatProjectJson(document), "utf-8");
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
function updateVsCodeMcp(filePath, remove) {
|
|
186
|
+
const document = readJsonObject(filePath);
|
|
187
|
+
const existing = document.servers;
|
|
188
|
+
if (existing !== undefined &&
|
|
189
|
+
(typeof existing !== "object" || existing === null || Array.isArray(existing)))
|
|
190
|
+
throw new Error(`${filePath}: expected "servers" to be an object`);
|
|
191
|
+
const servers = typeof existing === "object" && existing !== null && !Array.isArray(existing)
|
|
192
|
+
? { ...existing }
|
|
193
|
+
: {};
|
|
194
|
+
if (remove)
|
|
195
|
+
delete servers[MCP_NAME];
|
|
196
|
+
else
|
|
197
|
+
servers[MCP_NAME] = { type: "stdio", command: "knodin", args: ["serve"] };
|
|
198
|
+
if (Object.keys(servers).length > 0)
|
|
199
|
+
document.servers = servers;
|
|
200
|
+
else
|
|
201
|
+
delete document.servers;
|
|
202
|
+
if (remove && Object.keys(document).length === 0) {
|
|
203
|
+
fs.rmSync(filePath, { force: true });
|
|
204
|
+
removeEmptyParent(filePath);
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
208
|
+
fs.writeFileSync(filePath, formatProjectJson(document), "utf-8");
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
/** Strips a marker-delimited managed block, leaving surrounding user config intact. */
|
|
212
|
+
function removeManagedBlock(content, startMarker, endMarker) {
|
|
213
|
+
const start = content.indexOf(startMarker);
|
|
214
|
+
const end = content.indexOf(endMarker);
|
|
215
|
+
return start >= 0 && end >= start
|
|
216
|
+
? `${content.slice(0, start)}${content.slice(end + endMarker.length)}`.trim()
|
|
217
|
+
: content.trim();
|
|
218
|
+
}
|
|
219
|
+
function updateTomlMcp(filePath, remove) {
|
|
220
|
+
let existing = "";
|
|
221
|
+
try {
|
|
222
|
+
existing = fs.readFileSync(filePath, "utf-8");
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
if (error.code !== "ENOENT")
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
const without = removeManagedBlock(existing, TOML_START, TOML_END);
|
|
229
|
+
const next = remove ? without : [without, TOML_BLOCK].filter(Boolean).join("\n\n");
|
|
230
|
+
if (!next) {
|
|
231
|
+
fs.rmSync(filePath, { force: true });
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
235
|
+
fs.writeFileSync(filePath, `${next}\n`, "utf-8");
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
/** Merge or remove knodin's project-local agent adapters without replacing user configuration. */
|
|
239
|
+
export function configureProjectAgents(options) {
|
|
240
|
+
const repo = path.resolve(options.repo);
|
|
241
|
+
const remove = options.scope === "cli-only";
|
|
242
|
+
const agents = options.scope === "team" || options.scope === "cli-only"
|
|
243
|
+
? ["claude", "codex", "gemini", "copilot", "antigravity"]
|
|
244
|
+
: options.agents;
|
|
245
|
+
const commandResult = options.scope === "team"
|
|
246
|
+
? { configured: [], removed: [], failed: [], projectFiles: [] }
|
|
247
|
+
: configurePersonalAgents({
|
|
248
|
+
detected: agents,
|
|
249
|
+
remove,
|
|
250
|
+
run: options.run ?? ((command) => defaultRunner({ ...command, cwd: repo })),
|
|
251
|
+
});
|
|
252
|
+
const configured = [...commandResult.configured];
|
|
253
|
+
const removed = [...commandResult.removed];
|
|
254
|
+
const failed = [...commandResult.failed];
|
|
255
|
+
const projectFiles = [];
|
|
256
|
+
for (const agent of agents) {
|
|
257
|
+
try {
|
|
258
|
+
let relative = null;
|
|
259
|
+
let remains = false;
|
|
260
|
+
if (agent === "claude" && options.scope === "team") {
|
|
261
|
+
relative = ".mcp.json";
|
|
262
|
+
assertSafeProjectPath(repo, path.join(repo, relative));
|
|
263
|
+
remains = updateJsonMcp(path.join(repo, relative), false);
|
|
264
|
+
}
|
|
265
|
+
else if (agent === "claude") {
|
|
266
|
+
relative = ".mcp.json";
|
|
267
|
+
assertSafeProjectPath(repo, path.join(repo, relative));
|
|
268
|
+
remains = updateJsonMcp(path.join(repo, relative), true);
|
|
269
|
+
}
|
|
270
|
+
else if (agent === "codex") {
|
|
271
|
+
relative = ".codex/config.toml";
|
|
272
|
+
assertSafeProjectPath(repo, path.join(repo, relative));
|
|
273
|
+
remains = updateTomlMcp(path.join(repo, relative), remove);
|
|
274
|
+
}
|
|
275
|
+
else if (agent === "gemini") {
|
|
276
|
+
relative = ".gemini/settings.json";
|
|
277
|
+
assertSafeProjectPath(repo, path.join(repo, relative));
|
|
278
|
+
remains = updateJsonMcp(path.join(repo, relative), remove);
|
|
279
|
+
}
|
|
280
|
+
else if (agent === "copilot") {
|
|
281
|
+
relative = ".vscode/mcp.json";
|
|
282
|
+
assertSafeProjectPath(repo, path.join(repo, relative));
|
|
283
|
+
remains = updateVsCodeMcp(path.join(repo, relative), remove);
|
|
284
|
+
}
|
|
285
|
+
else if (agent === "antigravity") {
|
|
286
|
+
relative = ".agents/mcp_config.json";
|
|
287
|
+
assertSafeProjectPath(repo, path.join(repo, relative));
|
|
288
|
+
remains = updateJsonMcp(path.join(repo, relative), remove);
|
|
289
|
+
}
|
|
290
|
+
if (relative && remains)
|
|
291
|
+
projectFiles.push(relative);
|
|
292
|
+
if (agent === "claude" && options.scope === "team" && !configured.includes(agent))
|
|
293
|
+
configured.push(agent);
|
|
294
|
+
if (agent !== "claude" && !remove && !configured.includes(agent))
|
|
295
|
+
configured.push(agent);
|
|
296
|
+
if (remove && !removed.includes(agent))
|
|
297
|
+
removed.push(agent);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
failed.push({ agent, message: errorMessage(error) });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return { configured, removed, failed, projectFiles };
|
|
304
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { resolveStateDir } from "./engine/state-paths.js";
|
|
5
|
+
const REFRESH_TIMEOUT_MS = 30_000;
|
|
6
|
+
function defaultRuntime() {
|
|
7
|
+
return {
|
|
8
|
+
commandExists: (command) => {
|
|
9
|
+
const probe = spawnSync(command, ["--help"], { stdio: "ignore", timeout: 5_000 });
|
|
10
|
+
return (probe.status !== null ||
|
|
11
|
+
probe.error?.code !== "ENOENT");
|
|
12
|
+
},
|
|
13
|
+
run: (command, cwd) => {
|
|
14
|
+
const startedAt = performance.now();
|
|
15
|
+
const result = spawnSync(command[0], command.slice(1), {
|
|
16
|
+
stdio: "ignore",
|
|
17
|
+
timeout: REFRESH_TIMEOUT_MS,
|
|
18
|
+
cwd,
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
exitCode: result.status,
|
|
22
|
+
elapsedMs: Math.round(performance.now() - startedAt),
|
|
23
|
+
timedOut: result.signal === "SIGTERM" || result.signal === "SIGKILL",
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Explicitly refresh supported local, external graph artifacts. This is never a
|
|
30
|
+
* commit hook: callers opt in, each rebuild is time-bounded, and unavailable
|
|
31
|
+
* tools are recorded as skipped rather than being represented as fresh.
|
|
32
|
+
*/
|
|
33
|
+
export function refreshExternalGraphArtifacts(repoPath, event, runtime = defaultRuntime()) {
|
|
34
|
+
const repo = path.resolve(repoPath);
|
|
35
|
+
const gitNexusRunner = path.join(repo, ".gitnexus", "run.cjs");
|
|
36
|
+
const commands = [
|
|
37
|
+
fs.existsSync(gitNexusRunner)
|
|
38
|
+
? {
|
|
39
|
+
artifact: "gitnexus",
|
|
40
|
+
command: ["node", gitNexusRunner, "analyze"],
|
|
41
|
+
requiredCommand: "node",
|
|
42
|
+
}
|
|
43
|
+
: {
|
|
44
|
+
artifact: "gitnexus",
|
|
45
|
+
command: ["gitnexus", "analyze", repo],
|
|
46
|
+
requiredCommand: "gitnexus",
|
|
47
|
+
},
|
|
48
|
+
{ artifact: "graphify", command: ["graphify", "update", repo], requiredCommand: "graphify" },
|
|
49
|
+
];
|
|
50
|
+
const artifacts = commands.map(({ artifact, command, requiredCommand }) => {
|
|
51
|
+
if (!runtime.commandExists(requiredCommand)) {
|
|
52
|
+
return { artifact, state: "skipped", reason: `${artifact} is not installed` };
|
|
53
|
+
}
|
|
54
|
+
const result = runtime.run(command, repo);
|
|
55
|
+
if (result.exitCode === 0) {
|
|
56
|
+
return { artifact, state: "success", command, elapsedMs: result.elapsedMs };
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
artifact,
|
|
60
|
+
state: "failed",
|
|
61
|
+
command,
|
|
62
|
+
elapsedMs: result.elapsedMs,
|
|
63
|
+
reason: result.timedOut
|
|
64
|
+
? `timed out after ${REFRESH_TIMEOUT_MS}ms`
|
|
65
|
+
: `exited ${result.exitCode ?? "unknown"}`,
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
const successes = artifacts.filter((artifact) => artifact.state === "success").length;
|
|
69
|
+
const failures = artifacts.filter((artifact) => artifact.state === "failed").length;
|
|
70
|
+
const status = failures > 0 && successes === 0
|
|
71
|
+
? "failed"
|
|
72
|
+
: failures > 0 || successes > 0
|
|
73
|
+
? "partial"
|
|
74
|
+
: "skipped";
|
|
75
|
+
return { event, status, artifacts };
|
|
76
|
+
}
|
|
77
|
+
/** Persist an audit record locally; no source content or tool output is retained. */
|
|
78
|
+
export function writeArtifactRefreshRecord(repoPath, result) {
|
|
79
|
+
const knodinDir = resolveStateDir(repoPath);
|
|
80
|
+
fs.mkdirSync(knodinDir, { recursive: true });
|
|
81
|
+
fs.appendFileSync(path.join(knodinDir, "artifact-refresh.jsonl"), `${JSON.stringify({ at: new Date().toISOString(), ...result })}\n`, "utf8");
|
|
82
|
+
}
|