@xn-intenton-z2a/agentic-lib 7.2.5 → 7.2.7
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/.github/workflows/agentic-lib-init.yml +56 -0
- package/.github/workflows/agentic-lib-test.yml +7 -2
- package/.github/workflows/agentic-lib-workflow.yml +50 -3
- package/README.md +88 -17
- package/agentic-lib.toml +7 -0
- package/bin/agentic-lib.js +260 -496
- package/package.json +2 -1
- package/src/actions/agentic-step/config-loader.js +9 -0
- package/src/actions/agentic-step/index.js +104 -7
- package/src/actions/agentic-step/tasks/direct.js +435 -0
- package/src/actions/agentic-step/tasks/supervise.js +107 -180
- package/src/agents/agent-apply-fix.md +5 -2
- package/src/agents/agent-director.md +58 -0
- package/src/agents/agent-discovery.md +52 -0
- package/src/agents/agent-issue-resolution.md +18 -0
- package/src/agents/agent-iterate.md +45 -0
- package/src/agents/agent-supervisor.md +22 -50
- package/src/copilot/agents.js +39 -0
- package/src/copilot/config.js +308 -0
- package/src/copilot/context.js +318 -0
- package/src/copilot/hybrid-session.js +330 -0
- package/src/copilot/logger.js +43 -0
- package/src/copilot/sdk.js +36 -0
- package/src/copilot/session.js +372 -0
- package/src/copilot/tasks/fix-code.js +73 -0
- package/src/copilot/tasks/maintain-features.js +61 -0
- package/src/copilot/tasks/maintain-library.js +66 -0
- package/src/copilot/tasks/transform.js +120 -0
- package/src/copilot/tools.js +141 -0
- package/src/mcp/server.js +43 -25
- package/src/seeds/zero-README.md +31 -0
- package/src/seeds/zero-behaviour.test.js +12 -4
- package/src/seeds/zero-package.json +1 -1
- package/src/seeds/zero-playwright.config.js +1 -0
package/bin/agentic-lib.js
CHANGED
|
@@ -51,7 +51,9 @@ Tasks (run Copilot SDK transformations):
|
|
|
51
51
|
fix-code Fix failing tests
|
|
52
52
|
|
|
53
53
|
Iterator:
|
|
54
|
-
iterate
|
|
54
|
+
iterate Single Copilot SDK session — reads, writes, tests, iterates autonomously
|
|
55
|
+
iterate --here Discover the project and generate a MISSION.md, then iterate
|
|
56
|
+
iterate --list-missions List available built-in mission seeds
|
|
55
57
|
|
|
56
58
|
MCP Server:
|
|
57
59
|
mcp Start MCP server (for Claude Code, Cursor, etc.)
|
|
@@ -62,17 +64,28 @@ Options:
|
|
|
62
64
|
--dry-run Show what would be done without making changes
|
|
63
65
|
--target <path> Target repository (default: current directory)
|
|
64
66
|
--mission <name> Mission seed name (default: hamming-distance) [purge only]
|
|
67
|
+
--mission-file <path> Use a custom mission file instead of a built-in seed
|
|
68
|
+
--list-missions List available built-in mission seeds
|
|
69
|
+
--here Discover the project and generate a MISSION.md, then iterate
|
|
70
|
+
--agent <name> Use a specific agent prompt (e.g. agent-iterate, agent-discovery)
|
|
65
71
|
--model <name> Copilot SDK model (default: claude-sonnet-4)
|
|
66
|
-
--
|
|
67
|
-
--
|
|
72
|
+
--timeout <ms> Session timeout in milliseconds (default: 600000)
|
|
73
|
+
--issue <N> GitHub issue number (fetched via gh CLI for context)
|
|
74
|
+
--pr <N> GitHub PR number (fetched via gh CLI for context)
|
|
75
|
+
--discussion <url> GitHub Discussion URL (fetched via gh CLI for context)
|
|
68
76
|
|
|
69
77
|
Examples:
|
|
70
78
|
npx @xn-intenton-z2a/agentic-lib init
|
|
71
79
|
npx @xn-intenton-z2a/agentic-lib transform
|
|
72
80
|
npx @xn-intenton-z2a/agentic-lib maintain-features --model gpt-5-mini
|
|
73
81
|
npx @xn-intenton-z2a/agentic-lib reset --dry-run
|
|
74
|
-
npx @xn-intenton-z2a/agentic-lib iterate --mission fizz-buzz
|
|
75
|
-
npx @xn-intenton-z2a/agentic-lib iterate --
|
|
82
|
+
npx @xn-intenton-z2a/agentic-lib iterate --mission fizz-buzz
|
|
83
|
+
npx @xn-intenton-z2a/agentic-lib iterate --mission-file ~/my-mission.md
|
|
84
|
+
npx @xn-intenton-z2a/agentic-lib iterate --here
|
|
85
|
+
npx @xn-intenton-z2a/agentic-lib iterate --list-missions
|
|
86
|
+
npx @xn-intenton-z2a/agentic-lib iterate --model gpt-5-mini --timeout 300000
|
|
87
|
+
npx @xn-intenton-z2a/agentic-lib iterate --agent agent-issue-resolution --issue 42
|
|
88
|
+
npx @xn-intenton-z2a/agentic-lib iterate --agent agent-apply-fix --pr 123
|
|
76
89
|
`.trim();
|
|
77
90
|
|
|
78
91
|
if (!command || command === "--help" || command === "-h" || command === "help") {
|
|
@@ -99,6 +112,20 @@ const cyclesIdx = flags.indexOf("--cycles");
|
|
|
99
112
|
const cycles = cyclesIdx >= 0 ? parseInt(flags[cyclesIdx + 1], 10) : 0;
|
|
100
113
|
const stepsIdx = flags.indexOf("--steps");
|
|
101
114
|
const stepsFlag = stepsIdx >= 0 ? flags[stepsIdx + 1] : "";
|
|
115
|
+
const timeoutIdx = flags.indexOf("--timeout");
|
|
116
|
+
const timeoutMs = timeoutIdx >= 0 ? parseInt(flags[timeoutIdx + 1], 10) : 600000;
|
|
117
|
+
const listMissions = flags.includes("--list-missions");
|
|
118
|
+
const missionFileIdx = flags.indexOf("--mission-file");
|
|
119
|
+
const missionFile = missionFileIdx >= 0 ? resolve(flags[missionFileIdx + 1]) : "";
|
|
120
|
+
const hereMode = flags.includes("--here");
|
|
121
|
+
const agentIdx = flags.indexOf("--agent");
|
|
122
|
+
const agentFlag = agentIdx >= 0 ? flags[agentIdx + 1] : "";
|
|
123
|
+
const issueIdx = flags.indexOf("--issue");
|
|
124
|
+
const issueNumber = issueIdx >= 0 ? parseInt(flags[issueIdx + 1], 10) : 0;
|
|
125
|
+
const prIdx = flags.indexOf("--pr");
|
|
126
|
+
const prNumber = prIdx >= 0 ? parseInt(flags[prIdx + 1], 10) : 0;
|
|
127
|
+
const discussionIdx = flags.indexOf("--discussion");
|
|
128
|
+
const discussionUrl = discussionIdx >= 0 ? flags[discussionIdx + 1] : "";
|
|
102
129
|
|
|
103
130
|
// ─── Task Commands ───────────────────────────────────────────────────
|
|
104
131
|
|
|
@@ -137,51 +164,203 @@ runInit();
|
|
|
137
164
|
// ─── Iterator ────────────────────────────────────────────────────────
|
|
138
165
|
|
|
139
166
|
async function runIterate() {
|
|
140
|
-
|
|
167
|
+
// --list-missions: show available seeds and exit
|
|
168
|
+
if (listMissions) {
|
|
169
|
+
const missionsDir = resolve(srcDir, "seeds/missions");
|
|
170
|
+
const files = readdirSync(missionsDir).filter((f) => f.endsWith(".md")).sort();
|
|
171
|
+
console.log("\nAvailable missions:\n");
|
|
172
|
+
for (const f of files) {
|
|
173
|
+
const name = f.replace(".md", "");
|
|
174
|
+
const content = readFileSync(resolve(missionsDir, f), "utf8");
|
|
175
|
+
const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#")) || "";
|
|
176
|
+
console.log(` ${name.padEnd(22)} ${firstLine.trim()}`);
|
|
177
|
+
}
|
|
178
|
+
console.log(`\nUsage: npx @xn-intenton-z2a/agentic-lib iterate --mission <name>`);
|
|
179
|
+
console.log(` npx @xn-intenton-z2a/agentic-lib iterate --mission-file <path>\n`);
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const { loadAgentPrompt } = await import("../src/copilot/agents.js");
|
|
184
|
+
|
|
185
|
+
// Resolve mission file path from config if available
|
|
186
|
+
let config;
|
|
187
|
+
try {
|
|
188
|
+
const { loadConfig } = await import("../src/copilot/config.js");
|
|
189
|
+
config = loadConfig(resolve(target, "agentic-lib.toml"));
|
|
190
|
+
} catch {
|
|
191
|
+
config = { tuning: {}, model: "gpt-5-mini", paths: {} };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// --here mode: run discovery agent to generate MISSION.md, then iterate
|
|
195
|
+
if (hereMode) {
|
|
196
|
+
// Determine mission output path: --mission-file > config paths.mission > default
|
|
197
|
+
const missionOutPath = missionFile
|
|
198
|
+
|| (config.paths?.mission?.path ? resolve(target, config.paths.mission.path) : "")
|
|
199
|
+
|| resolve(target, "MISSION.md");
|
|
200
|
+
|
|
201
|
+
const targetMission = resolve(target, "MISSION.md");
|
|
202
|
+
if (existsSync(targetMission)) {
|
|
203
|
+
console.log(`Using existing MISSION.md in ${target}`);
|
|
204
|
+
} else {
|
|
205
|
+
console.log("");
|
|
206
|
+
console.log("=== Discovery Phase ===");
|
|
207
|
+
console.log(`Target: ${target}`);
|
|
208
|
+
console.log(`Output: ${missionOutPath}`);
|
|
209
|
+
console.log("");
|
|
210
|
+
|
|
211
|
+
const discoveryPrompt = loadAgentPrompt("agent-discovery");
|
|
212
|
+
const { runHybridSession } = await import("../src/copilot/hybrid-session.js");
|
|
213
|
+
const effectiveModel = model || config.model || "gpt-5-mini";
|
|
214
|
+
|
|
215
|
+
const discoveryResult = await runHybridSession({
|
|
216
|
+
workspacePath: target,
|
|
217
|
+
model: effectiveModel,
|
|
218
|
+
tuning: config.tuning || {},
|
|
219
|
+
timeoutMs,
|
|
220
|
+
agentPrompt: discoveryPrompt,
|
|
221
|
+
userPrompt: [
|
|
222
|
+
"Explore this project directory and generate a MISSION.md file.",
|
|
223
|
+
`Write the mission file to: ${missionOutPath}`,
|
|
224
|
+
"",
|
|
225
|
+
"Examine the project structure, source code, tests, and documentation.",
|
|
226
|
+
"Then write a focused, achievable MISSION.md based on what you find.",
|
|
227
|
+
].join("\n"),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
console.log("");
|
|
231
|
+
console.log(`Discovery: ${discoveryResult.success ? "completed" : "finished"} (${discoveryResult.toolCalls} tool calls, ${discoveryResult.sessionTime}s)`);
|
|
232
|
+
|
|
233
|
+
if (!existsSync(missionOutPath)) {
|
|
234
|
+
console.error("Discovery did not produce a mission file. Cannot proceed.");
|
|
235
|
+
return 1;
|
|
236
|
+
}
|
|
237
|
+
console.log("");
|
|
238
|
+
|
|
239
|
+
// --here + --mission-file: stop after generating the mission file
|
|
240
|
+
if (missionFile) {
|
|
241
|
+
console.log(`Mission file written to: ${missionOutPath}`);
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Copy to MISSION.md in target if discovery wrote elsewhere
|
|
246
|
+
if (missionOutPath !== targetMission && !existsSync(targetMission)) {
|
|
247
|
+
copyFileSync(missionOutPath, targetMission);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
141
250
|
|
|
142
|
-
|
|
143
|
-
|
|
251
|
+
// Fall through to normal iterate with the generated mission
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Guard: require a mission source or existing MISSION.md in the target
|
|
255
|
+
if (!hereMode && !missionFile && missionIdx < 0) {
|
|
256
|
+
const targetMission = resolve(target, "MISSION.md");
|
|
257
|
+
if (!existsSync(targetMission)) {
|
|
258
|
+
console.error("No mission specified and no MISSION.md found in target directory.");
|
|
259
|
+
console.error("");
|
|
260
|
+
console.error("Usage:");
|
|
261
|
+
console.error(" iterate --mission <name> Use a built-in mission seed");
|
|
262
|
+
console.error(" iterate --mission-file <path> Use a custom mission file");
|
|
263
|
+
console.error(" iterate --here Discover and generate a mission");
|
|
264
|
+
console.error(" iterate --list-missions List available built-in missions");
|
|
265
|
+
console.error("");
|
|
266
|
+
console.error("Or run from a directory that already has a MISSION.md.");
|
|
267
|
+
return 1;
|
|
268
|
+
}
|
|
269
|
+
console.log(`Using existing MISSION.md in ${target}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// --mission-file: copy custom file as MISSION.md, run init --purge with empty mission
|
|
273
|
+
if (!hereMode && missionFile) {
|
|
274
|
+
if (!existsSync(missionFile)) {
|
|
275
|
+
console.error(`Mission file not found: ${missionFile}`);
|
|
276
|
+
return 1;
|
|
277
|
+
}
|
|
278
|
+
console.log(`\n=== Init with custom mission: ${missionFile} ===\n`);
|
|
279
|
+
execSync(
|
|
280
|
+
`node ${resolve(pkgRoot, "bin/agentic-lib.js")} init --purge --mission empty --target ${target}`,
|
|
281
|
+
{ encoding: "utf8", timeout: 60000, stdio: "inherit" },
|
|
282
|
+
);
|
|
283
|
+
// Overwrite MISSION.md with the custom file
|
|
284
|
+
copyFileSync(missionFile, resolve(target, "MISSION.md"));
|
|
285
|
+
console.log(` COPY: ${missionFile} → MISSION.md\n`);
|
|
286
|
+
} else if (!hereMode && missionIdx >= 0) {
|
|
287
|
+
// --mission: use a built-in seed
|
|
144
288
|
console.log(`\n=== Init with mission: ${mission} ===\n`);
|
|
145
|
-
|
|
289
|
+
execSync(
|
|
146
290
|
`node ${resolve(pkgRoot, "bin/agentic-lib.js")} init --purge --mission ${mission} --target ${target}`,
|
|
147
291
|
{ encoding: "utf8", timeout: 60000, stdio: "inherit" },
|
|
148
292
|
);
|
|
149
293
|
}
|
|
150
294
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
295
|
+
const { runHybridSession } = await import("../src/copilot/hybrid-session.js");
|
|
296
|
+
const { gatherLocalContext, gatherGitHubContext, buildUserPrompt } = await import("../src/copilot/context.js");
|
|
297
|
+
|
|
298
|
+
// Load agent prompt: --agent flag > default agent-iterate
|
|
299
|
+
const agentName = agentFlag || "agent-iterate";
|
|
300
|
+
let agentPrompt;
|
|
301
|
+
try {
|
|
302
|
+
agentPrompt = loadAgentPrompt(agentName);
|
|
303
|
+
console.log(`Agent: ${agentName}`);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
console.error(`Failed to load agent: ${err.message}`);
|
|
306
|
+
return 1;
|
|
307
|
+
}
|
|
154
308
|
|
|
309
|
+
const effectiveModel = model || config.model || "gpt-5-mini";
|
|
155
310
|
console.log("");
|
|
156
311
|
console.log("=== agentic-lib iterate ===");
|
|
157
312
|
console.log(`Target: ${target}`);
|
|
158
|
-
console.log(`Model: ${
|
|
159
|
-
console.log(`
|
|
160
|
-
console.log(`
|
|
161
|
-
console.log(`
|
|
313
|
+
console.log(`Model: ${effectiveModel}`);
|
|
314
|
+
if (issueNumber) console.log(`Issue: #${issueNumber}`);
|
|
315
|
+
if (prNumber) console.log(`PR: #${prNumber}`);
|
|
316
|
+
if (discussionUrl) console.log(`Discussion: ${discussionUrl}`);
|
|
162
317
|
console.log("");
|
|
163
318
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
319
|
+
// Gather context for the agent
|
|
320
|
+
const localContext = gatherLocalContext(target, config);
|
|
321
|
+
|
|
322
|
+
// Optionally gather GitHub context
|
|
323
|
+
let githubContext;
|
|
324
|
+
if (issueNumber || prNumber || discussionUrl) {
|
|
325
|
+
console.log("Fetching GitHub context...");
|
|
326
|
+
githubContext = gatherGitHubContext({
|
|
327
|
+
issueNumber: issueNumber || undefined,
|
|
328
|
+
prNumber: prNumber || undefined,
|
|
329
|
+
discussionUrl: discussionUrl || undefined,
|
|
330
|
+
workspacePath: target,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Build context-aware user prompt
|
|
335
|
+
const userPrompt = buildUserPrompt(agentName, localContext, githubContext, { tuning: config.tuning });
|
|
336
|
+
|
|
337
|
+
const result = await runHybridSession({
|
|
338
|
+
workspacePath: target,
|
|
339
|
+
model: effectiveModel,
|
|
340
|
+
tuning: config.tuning || {},
|
|
341
|
+
timeoutMs,
|
|
342
|
+
agentPrompt,
|
|
343
|
+
userPrompt,
|
|
344
|
+
writablePaths: config.writablePaths?.length > 0 ? config.writablePaths : undefined,
|
|
177
345
|
});
|
|
178
346
|
|
|
179
347
|
console.log("");
|
|
180
|
-
console.log(
|
|
348
|
+
console.log("=== Results ===");
|
|
349
|
+
console.log(`Success: ${result.success}`);
|
|
350
|
+
console.log(`Tests passed: ${result.testsPassed}`);
|
|
351
|
+
console.log(`Session time: ${result.sessionTime}s`);
|
|
352
|
+
console.log(`Total time: ${result.totalTime}s`);
|
|
353
|
+
console.log(`Tool calls: ${result.toolCalls}`);
|
|
354
|
+
console.log(`Test runs: ${result.testRuns}`);
|
|
355
|
+
console.log(`Files written: ${result.filesWritten}`);
|
|
356
|
+
console.log(`Tokens: ${result.tokensIn + result.tokensOut} (in=${result.tokensIn} out=${result.tokensOut})`);
|
|
357
|
+
console.log(`End reason: ${result.endReason}`);
|
|
358
|
+
if (result.narrative) console.log(`Narrative: ${result.narrative}`);
|
|
359
|
+
if (result.agentMessage) console.log(`Agent: ${result.agentMessage}`);
|
|
360
|
+
if (result.errors.length > 0) console.log(`Errors: ${result.errors.length}`);
|
|
181
361
|
console.log("");
|
|
182
362
|
|
|
183
|
-
|
|
184
|
-
return lastCycle?.testsPassed ? 0 : 1;
|
|
363
|
+
return result.success ? 0 : 1;
|
|
185
364
|
}
|
|
186
365
|
|
|
187
366
|
// ─── Task Runner ─────────────────────────────────────────────────────
|
|
@@ -194,107 +373,68 @@ async function runTask(taskName) {
|
|
|
194
373
|
console.log(`Dry-run: ${dryRun}`);
|
|
195
374
|
console.log("");
|
|
196
375
|
|
|
197
|
-
//
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
resolve(target, ".github/agentic-lib/actions/agentic-step/node_modules/@github/copilot-sdk/dist/index.js"),
|
|
202
|
-
];
|
|
203
|
-
const sdkPath = sdkLocations.find((p) => existsSync(p));
|
|
204
|
-
if (!sdkPath) {
|
|
205
|
-
console.error("ERROR: @github/copilot-sdk not found.");
|
|
206
|
-
console.error("Run: cd .github/agentic-lib/actions/agentic-step && npm ci");
|
|
207
|
-
return 1;
|
|
208
|
-
}
|
|
209
|
-
const { CopilotClient, approveAll, defineTool } = await import(sdkPath);
|
|
210
|
-
|
|
211
|
-
// Load config
|
|
212
|
-
const config = await loadTaskConfig();
|
|
213
|
-
const writablePaths = getWritablePathsFromConfig(config);
|
|
214
|
-
const readOnlyPaths = getReadOnlyPathsFromConfig(config);
|
|
376
|
+
// Load config from shared module
|
|
377
|
+
const { loadConfig } = await import("../src/copilot/config.js");
|
|
378
|
+
const config = loadConfig(resolve(target, "agentic-lib.toml"));
|
|
379
|
+
const effectiveModel = model || config.model;
|
|
215
380
|
|
|
216
|
-
console.log(`[config] supervisor=${config.supervisor
|
|
217
|
-
console.log(`[config] writable=${writablePaths.join(", ")}`);
|
|
381
|
+
console.log(`[config] supervisor=${config.supervisor}`);
|
|
382
|
+
console.log(`[config] writable=${config.writablePaths.join(", ")}`);
|
|
218
383
|
console.log(`[config] test=${config.testScript}`);
|
|
219
384
|
console.log("");
|
|
220
385
|
|
|
221
|
-
// Build task-specific prompt
|
|
222
|
-
const { systemMessage, prompt } = buildTaskPrompt(taskName, config, writablePaths, readOnlyPaths);
|
|
223
|
-
|
|
224
|
-
if (!prompt) {
|
|
225
|
-
return 0; // buildTaskPrompt already logged why
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
console.log(`[prompt] ${prompt.length} chars`);
|
|
229
|
-
console.log("");
|
|
230
|
-
|
|
231
386
|
if (dryRun) {
|
|
232
|
-
console.log("=== DRY RUN —
|
|
233
|
-
console.log("");
|
|
234
|
-
console.log(prompt);
|
|
387
|
+
console.log("=== DRY RUN — task would run but not sending to Copilot ===");
|
|
235
388
|
return 0;
|
|
236
389
|
}
|
|
237
390
|
|
|
238
|
-
//
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
// Set up auth
|
|
242
|
-
const copilotToken = process.env.COPILOT_GITHUB_TOKEN;
|
|
243
|
-
if (!copilotToken) {
|
|
244
|
-
console.error("ERROR: COPILOT_GITHUB_TOKEN is required. Set it in your environment.");
|
|
245
|
-
return 1;
|
|
246
|
-
}
|
|
247
|
-
console.log("[auth] Using COPILOT_GITHUB_TOKEN");
|
|
248
|
-
const clientOptions = {};
|
|
249
|
-
const env = { ...process.env };
|
|
250
|
-
env.GITHUB_TOKEN = copilotToken;
|
|
251
|
-
env.GH_TOKEN = copilotToken;
|
|
252
|
-
clientOptions.env = env;
|
|
253
|
-
|
|
254
|
-
const client = new CopilotClient(clientOptions);
|
|
391
|
+
// Change to target directory so relative paths in config work
|
|
392
|
+
const originalCwd = process.cwd();
|
|
393
|
+
process.chdir(target);
|
|
255
394
|
|
|
256
395
|
try {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const preview = event?.data?.content?.substring(0, 120) || "";
|
|
272
|
-
console.log(`[event] ${type}: ${preview}...`);
|
|
273
|
-
} else if (type === "tool.call") {
|
|
274
|
-
const name = event?.data?.name || "?";
|
|
275
|
-
const args = JSON.stringify(event?.data?.arguments || {}).substring(0, 200);
|
|
276
|
-
console.log(`[event] tool.call: ${name}(${args})`);
|
|
277
|
-
} else if (type === "session.error") {
|
|
278
|
-
console.error(`[event] ERROR: ${JSON.stringify(event?.data || event)}`);
|
|
279
|
-
} else if (type !== "session.idle") {
|
|
280
|
-
console.log(`[event] ${type}`);
|
|
396
|
+
const context = {
|
|
397
|
+
config,
|
|
398
|
+
writablePaths: config.writablePaths,
|
|
399
|
+
model: effectiveModel,
|
|
400
|
+
testCommand: config.testScript,
|
|
401
|
+
logger: { info: console.log, warning: console.warn, error: console.error, debug: () => {} },
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
let result;
|
|
405
|
+
switch (taskName) {
|
|
406
|
+
case "transform": {
|
|
407
|
+
const { transform } = await import("../src/copilot/tasks/transform.js");
|
|
408
|
+
result = await transform(context);
|
|
409
|
+
break;
|
|
281
410
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
411
|
+
case "maintain-features": {
|
|
412
|
+
const { maintainFeatures } = await import("../src/copilot/tasks/maintain-features.js");
|
|
413
|
+
result = await maintainFeatures(context);
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
case "maintain-library": {
|
|
417
|
+
const { maintainLibrary } = await import("../src/copilot/tasks/maintain-library.js");
|
|
418
|
+
result = await maintainLibrary(context);
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
case "fix-code": {
|
|
422
|
+
const { fixCode } = await import("../src/copilot/tasks/fix-code.js");
|
|
423
|
+
result = await fixCode(context);
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
default:
|
|
427
|
+
console.error(`Unknown task: ${taskName}`);
|
|
428
|
+
return 1;
|
|
429
|
+
}
|
|
291
430
|
|
|
292
431
|
console.log("");
|
|
293
|
-
console.log(`=== ${taskName} completed
|
|
294
|
-
console.log(
|
|
295
|
-
console.log(
|
|
432
|
+
console.log(`=== ${taskName} completed ===`);
|
|
433
|
+
console.log(`Outcome: ${result.outcome}`);
|
|
434
|
+
if (result.details) console.log(`Details: ${result.details}`);
|
|
435
|
+
if (result.tokensUsed) console.log(`Tokens: ${result.tokensUsed} (in=${result.inputTokens} out=${result.outputTokens})`);
|
|
436
|
+
if (result.narrative) console.log(`Narrative: ${result.narrative}`);
|
|
296
437
|
console.log("");
|
|
297
|
-
|
|
298
438
|
return 0;
|
|
299
439
|
} catch (err) {
|
|
300
440
|
console.error("");
|
|
@@ -303,386 +443,10 @@ async function runTask(taskName) {
|
|
|
303
443
|
if (err.stack) console.error(err.stack);
|
|
304
444
|
return 1;
|
|
305
445
|
} finally {
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// ─── Task Config + Prompts ───────────────────────────────────────────
|
|
311
|
-
|
|
312
|
-
async function loadTaskConfig() {
|
|
313
|
-
const tomlPath = resolve(target, "agentic-lib.toml");
|
|
314
|
-
|
|
315
|
-
if (!existsSync(tomlPath)) {
|
|
316
|
-
throw new Error(`Config file not found: ${tomlPath}. Create agentic-lib.toml in the project root.`);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
console.log(`[config] Loading ${tomlPath}`);
|
|
320
|
-
const { parse } = await import("smol-toml");
|
|
321
|
-
const toml = parse(readFileSync(tomlPath, "utf8"));
|
|
322
|
-
return {
|
|
323
|
-
missionPath: toml.paths?.mission || "MISSION.md",
|
|
324
|
-
sourcePath: toml.paths?.source || "src/lib/",
|
|
325
|
-
testsPath: toml.paths?.tests || "tests/unit/",
|
|
326
|
-
featuresPath: toml.paths?.features || "features/",
|
|
327
|
-
libraryPath: toml.paths?.docs || "library/",
|
|
328
|
-
sourcesPath: toml.paths?.["library-sources"] || "SOURCES.md",
|
|
329
|
-
examplesPath: toml.paths?.examples || "examples/",
|
|
330
|
-
readmePath: toml.paths?.readme || "README.md",
|
|
331
|
-
depsPath: toml.paths?.dependencies || "package.json",
|
|
332
|
-
testScript: toml.execution?.test || "npm ci && npm test",
|
|
333
|
-
featureLimit: toml.limits?.["max-feature-issues"] || 2,
|
|
334
|
-
intentionPath: toml.bot?.["log-file"] || "intentïon.md",
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function getWritablePathsFromConfig(config) {
|
|
339
|
-
return [
|
|
340
|
-
config.sourcePath,
|
|
341
|
-
config.testsPath,
|
|
342
|
-
config.featuresPath,
|
|
343
|
-
config.libraryPath,
|
|
344
|
-
config.examplesPath,
|
|
345
|
-
config.readmePath,
|
|
346
|
-
config.depsPath,
|
|
347
|
-
].filter(Boolean);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function getReadOnlyPathsFromConfig(config) {
|
|
351
|
-
return [config.missionPath, config.sourcesPath].filter(Boolean);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function readOptional(relPath) {
|
|
355
|
-
try {
|
|
356
|
-
return readFileSync(resolve(target, relPath), "utf8");
|
|
357
|
-
} catch (err) {
|
|
358
|
-
console.debug(`[readOptional] ${relPath}: ${err.message}`);
|
|
359
|
-
return "";
|
|
446
|
+
process.chdir(originalCwd);
|
|
360
447
|
}
|
|
361
448
|
}
|
|
362
449
|
|
|
363
|
-
function scanDir(relPath, extensions, opts = {}) {
|
|
364
|
-
const { fileLimit = 10, contentLimit } = opts;
|
|
365
|
-
const dir = resolve(target, relPath);
|
|
366
|
-
const exts = Array.isArray(extensions) ? extensions : [extensions];
|
|
367
|
-
if (!existsSync(dir)) return [];
|
|
368
|
-
try {
|
|
369
|
-
return readdirSync(dir, { recursive: true })
|
|
370
|
-
.filter((f) => exts.some((ext) => String(f).endsWith(ext)))
|
|
371
|
-
.slice(0, fileLimit)
|
|
372
|
-
.map((f) => {
|
|
373
|
-
try {
|
|
374
|
-
const content = readFileSync(resolve(dir, String(f)), "utf8");
|
|
375
|
-
return { name: String(f), content: contentLimit ? content.substring(0, contentLimit) : content };
|
|
376
|
-
} catch (err) {
|
|
377
|
-
console.debug(`[scanDir] ${dir}/${f}: ${err.message}`);
|
|
378
|
-
return { name: String(f), content: "" };
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
} catch (err) {
|
|
382
|
-
console.debug(`[scanDir] ${dir}: ${err.message}`);
|
|
383
|
-
return [];
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function formatPaths(writable, readOnly) {
|
|
388
|
-
return [
|
|
389
|
-
"## File Paths",
|
|
390
|
-
"### Writable (you may modify these)",
|
|
391
|
-
writable.length > 0 ? writable.map((p) => `- ${p}`).join("\n") : "- (none)",
|
|
392
|
-
"",
|
|
393
|
-
"### Read-Only (for context only, do NOT modify)",
|
|
394
|
-
readOnly.length > 0 ? readOnly.map((p) => `- ${p}`).join("\n") : "- (none)",
|
|
395
|
-
].join("\n");
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function buildTaskPrompt(taskName, config, writablePaths, readOnlyPaths) {
|
|
399
|
-
const pathsSection = formatPaths(writablePaths, readOnlyPaths);
|
|
400
|
-
|
|
401
|
-
switch (taskName) {
|
|
402
|
-
case "transform":
|
|
403
|
-
return buildTransformPrompt(config, pathsSection);
|
|
404
|
-
case "maintain-features":
|
|
405
|
-
return buildMaintainFeaturesPrompt(config, pathsSection);
|
|
406
|
-
case "maintain-library":
|
|
407
|
-
return buildMaintainLibraryPrompt(config, pathsSection);
|
|
408
|
-
case "fix-code":
|
|
409
|
-
return buildFixCodePrompt(config, pathsSection);
|
|
410
|
-
default:
|
|
411
|
-
console.error(`Unknown task: ${taskName}`);
|
|
412
|
-
return { systemMessage: "", prompt: null };
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function buildTransformPrompt(config, pathsSection) {
|
|
417
|
-
const mission = readOptional(config.missionPath);
|
|
418
|
-
if (!mission) {
|
|
419
|
-
console.error(`No mission file found at ${config.missionPath}`);
|
|
420
|
-
return { systemMessage: "", prompt: null };
|
|
421
|
-
}
|
|
422
|
-
console.log(`[context] Mission: ${mission.substring(0, 80).trim()}...`);
|
|
423
|
-
|
|
424
|
-
const features = scanDir(config.featuresPath, ".md");
|
|
425
|
-
const sourceFiles = scanDir(config.sourcePath, [".js", ".ts"], { contentLimit: 2000 });
|
|
426
|
-
console.log(`[context] Features: ${features.length}, Source files: ${sourceFiles.length}`);
|
|
427
|
-
|
|
428
|
-
return {
|
|
429
|
-
systemMessage:
|
|
430
|
-
"You are an autonomous code transformation agent. Your goal is to advance the repository toward its mission by making the most impactful change possible in a single step.",
|
|
431
|
-
prompt: [
|
|
432
|
-
"## Instructions",
|
|
433
|
-
"Transform the repository toward its mission by identifying the next best action.",
|
|
434
|
-
"",
|
|
435
|
-
"## Mission",
|
|
436
|
-
mission,
|
|
437
|
-
"",
|
|
438
|
-
`## Current Features (${features.length})`,
|
|
439
|
-
...features.map((f) => `### ${f.name}\n${f.content.substring(0, 500)}`),
|
|
440
|
-
"",
|
|
441
|
-
`## Current Source Files (${sourceFiles.length})`,
|
|
442
|
-
...sourceFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
|
|
443
|
-
"",
|
|
444
|
-
"## Output Artifacts",
|
|
445
|
-
"If your changes produce output artifacts (plots, visualizations, data files, usage examples),",
|
|
446
|
-
`save them to the \`${config.examplesPath || "examples/"}\` directory.`,
|
|
447
|
-
"This directory is for demonstrating what the code can do.",
|
|
448
|
-
"",
|
|
449
|
-
"## Your Task",
|
|
450
|
-
"Analyze the mission, features, and source code.",
|
|
451
|
-
"Determine the single most impactful next step.",
|
|
452
|
-
"Then implement that step by writing files.",
|
|
453
|
-
"",
|
|
454
|
-
pathsSection,
|
|
455
|
-
"",
|
|
456
|
-
"## Constraints",
|
|
457
|
-
`- Run \`${config.testScript}\` to validate your changes`,
|
|
458
|
-
].join("\n"),
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function buildMaintainFeaturesPrompt(config, pathsSection) {
|
|
463
|
-
const mission = readOptional(config.missionPath);
|
|
464
|
-
if (!mission) {
|
|
465
|
-
console.error(`No mission file found at ${config.missionPath}`);
|
|
466
|
-
return { systemMessage: "", prompt: null };
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const features = scanDir(config.featuresPath, ".md");
|
|
470
|
-
const libraryDocs = scanDir(config.libraryPath, ".md", { contentLimit: 1000 });
|
|
471
|
-
console.log(`[context] Mission loaded, features: ${features.length}, library: ${libraryDocs.length}`);
|
|
472
|
-
|
|
473
|
-
return {
|
|
474
|
-
systemMessage:
|
|
475
|
-
"You are a feature lifecycle manager. Create, update, and prune feature specification files to keep the project focused on its mission.",
|
|
476
|
-
prompt: [
|
|
477
|
-
"## Instructions",
|
|
478
|
-
"Maintain the feature set by creating, updating, or pruning features.",
|
|
479
|
-
"",
|
|
480
|
-
"## Mission",
|
|
481
|
-
mission,
|
|
482
|
-
"",
|
|
483
|
-
`## Current Features (${features.length}/${config.featureLimit} max)`,
|
|
484
|
-
...features.map((f) => `### ${f.name}\n${f.content}`),
|
|
485
|
-
"",
|
|
486
|
-
libraryDocs.length > 0 ? `## Library Documents (${libraryDocs.length})` : "",
|
|
487
|
-
...libraryDocs.map((d) => `### ${d.name}\n${d.content}`),
|
|
488
|
-
"",
|
|
489
|
-
"## Your Task",
|
|
490
|
-
`1. Review each existing feature — if it is already implemented or irrelevant, delete it.`,
|
|
491
|
-
`2. If there are fewer than ${config.featureLimit} features, create new features aligned with the mission.`,
|
|
492
|
-
"3. Ensure each feature has clear, testable acceptance criteria.",
|
|
493
|
-
"",
|
|
494
|
-
pathsSection,
|
|
495
|
-
"",
|
|
496
|
-
"## Constraints",
|
|
497
|
-
`- Maximum ${config.featureLimit} feature files`,
|
|
498
|
-
"- Feature files must be markdown with a descriptive filename",
|
|
499
|
-
].join("\n"),
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function buildMaintainLibraryPrompt(config, pathsSection) {
|
|
504
|
-
const sources = readOptional(config.sourcesPath);
|
|
505
|
-
if (!sources.trim()) {
|
|
506
|
-
console.log("No SOURCES.md or empty — nothing to maintain.");
|
|
507
|
-
return { systemMessage: "", prompt: null };
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const libraryDocs = scanDir(config.libraryPath, ".md", { contentLimit: 500 });
|
|
511
|
-
console.log(`[context] Sources loaded, library: ${libraryDocs.length}`);
|
|
512
|
-
|
|
513
|
-
return {
|
|
514
|
-
systemMessage:
|
|
515
|
-
"You are a knowledge librarian. Maintain a library of technical documents extracted from web sources.",
|
|
516
|
-
prompt: [
|
|
517
|
-
"## Instructions",
|
|
518
|
-
"Maintain the library by updating documents from sources.",
|
|
519
|
-
"",
|
|
520
|
-
"## Sources",
|
|
521
|
-
sources,
|
|
522
|
-
"",
|
|
523
|
-
`## Current Library Documents (${libraryDocs.length})`,
|
|
524
|
-
...libraryDocs.map((d) => `### ${d.name}\n${d.content}`),
|
|
525
|
-
"",
|
|
526
|
-
"## Your Task",
|
|
527
|
-
"1. Read each URL in SOURCES.md and extract technical content.",
|
|
528
|
-
"2. Create or update library documents based on the source content.",
|
|
529
|
-
"3. Remove library documents that no longer have corresponding sources.",
|
|
530
|
-
"",
|
|
531
|
-
pathsSection,
|
|
532
|
-
].join("\n"),
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function buildFixCodePrompt(config, pathsSection) {
|
|
537
|
-
// Run tests and capture output
|
|
538
|
-
console.log(`[fix-code] Running: ${config.testScript}`);
|
|
539
|
-
let testOutput;
|
|
540
|
-
try {
|
|
541
|
-
testOutput = execSync(config.testScript, { cwd: target, encoding: "utf8", timeout: 120000 });
|
|
542
|
-
console.log("[fix-code] Tests pass — nothing to fix.");
|
|
543
|
-
return { systemMessage: "", prompt: null };
|
|
544
|
-
} catch (err) {
|
|
545
|
-
testOutput = `STDOUT:\n${err.stdout || ""}\nSTDERR:\n${err.stderr || ""}`;
|
|
546
|
-
console.log(`[fix-code] Tests failing — ${testOutput.length} chars of output`);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const sourceFiles = scanDir(config.sourcePath, [".js", ".ts"], { contentLimit: 2000 });
|
|
550
|
-
const testFiles = scanDir(config.testsPath, [".js", ".ts", ".test.js"], { contentLimit: 2000 });
|
|
551
|
-
|
|
552
|
-
return {
|
|
553
|
-
systemMessage:
|
|
554
|
-
"You are an autonomous coding agent fixing failing tests. Make minimal, targeted changes to fix the test failures.",
|
|
555
|
-
prompt: [
|
|
556
|
-
"## Instructions",
|
|
557
|
-
"Fix the failing tests by modifying the source code.",
|
|
558
|
-
"",
|
|
559
|
-
"## Test Output (failing)",
|
|
560
|
-
"```",
|
|
561
|
-
testOutput.substring(0, 5000),
|
|
562
|
-
"```",
|
|
563
|
-
"",
|
|
564
|
-
`## Source Files (${sourceFiles.length})`,
|
|
565
|
-
...sourceFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
|
|
566
|
-
"",
|
|
567
|
-
`## Test Files (${testFiles.length})`,
|
|
568
|
-
...testFiles.map((f) => `### ${f.name}\n\`\`\`\n${f.content}\n\`\`\``),
|
|
569
|
-
"",
|
|
570
|
-
pathsSection,
|
|
571
|
-
"",
|
|
572
|
-
"## Constraints",
|
|
573
|
-
`- Run \`${config.testScript}\` to validate your fixes`,
|
|
574
|
-
"- Make minimal changes to fix the failing tests",
|
|
575
|
-
].join("\n"),
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// ─── CLI Tools for Copilot SDK ───────────────────────────────────────
|
|
580
|
-
|
|
581
|
-
function createCliTools(writablePaths, defineTool) {
|
|
582
|
-
const readFile = defineTool("read_file", {
|
|
583
|
-
description: "Read the contents of a file.",
|
|
584
|
-
parameters: {
|
|
585
|
-
type: "object",
|
|
586
|
-
properties: { path: { type: "string", description: "File path to read" } },
|
|
587
|
-
required: ["path"],
|
|
588
|
-
},
|
|
589
|
-
handler: ({ path }) => {
|
|
590
|
-
const resolved = resolve(target, path);
|
|
591
|
-
console.log(` [tool] read_file: ${resolved}`);
|
|
592
|
-
if (!existsSync(resolved)) return { error: `File not found: ${resolved}` };
|
|
593
|
-
try {
|
|
594
|
-
return { content: readFileSync(resolved, "utf8") };
|
|
595
|
-
} catch (err) {
|
|
596
|
-
return { error: err.message };
|
|
597
|
-
}
|
|
598
|
-
},
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
const writeFile = defineTool("write_file", {
|
|
602
|
-
description: "Write content to a file. Parent directories are created automatically.",
|
|
603
|
-
parameters: {
|
|
604
|
-
type: "object",
|
|
605
|
-
properties: {
|
|
606
|
-
path: { type: "string", description: "File path to write" },
|
|
607
|
-
content: { type: "string", description: "Content to write" },
|
|
608
|
-
},
|
|
609
|
-
required: ["path", "content"],
|
|
610
|
-
},
|
|
611
|
-
handler: ({ path, content }) => {
|
|
612
|
-
const resolved = resolve(target, path);
|
|
613
|
-
const isWritable = writablePaths.some((wp) => path.startsWith(wp) || resolved.startsWith(resolve(target, wp)));
|
|
614
|
-
console.log(` [tool] write_file: ${resolved} (${content.length} chars, writable=${isWritable})`);
|
|
615
|
-
if (!isWritable && !dryRun) {
|
|
616
|
-
return { error: `Path not writable: ${path}. Writable: ${writablePaths.join(", ")}` };
|
|
617
|
-
}
|
|
618
|
-
if (dryRun) {
|
|
619
|
-
console.log(` [tool] DRY RUN — would write ${content.length} chars to ${resolved}`);
|
|
620
|
-
return { success: true, dryRun: true };
|
|
621
|
-
}
|
|
622
|
-
try {
|
|
623
|
-
const dir = dirname(resolved);
|
|
624
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
625
|
-
writeFileSync(resolved, content, "utf8");
|
|
626
|
-
return { success: true, path: resolved };
|
|
627
|
-
} catch (err) {
|
|
628
|
-
return { error: err.message };
|
|
629
|
-
}
|
|
630
|
-
},
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
const listFiles = defineTool("list_files", {
|
|
634
|
-
description: "List files and directories at the given path.",
|
|
635
|
-
parameters: {
|
|
636
|
-
type: "object",
|
|
637
|
-
properties: {
|
|
638
|
-
path: { type: "string", description: "Directory path to list" },
|
|
639
|
-
recursive: { type: "boolean", description: "List recursively" },
|
|
640
|
-
},
|
|
641
|
-
required: ["path"],
|
|
642
|
-
},
|
|
643
|
-
handler: ({ path, recursive }) => {
|
|
644
|
-
const resolved = resolve(target, path);
|
|
645
|
-
console.log(` [tool] list_files: ${resolved}`);
|
|
646
|
-
if (!existsSync(resolved)) return { error: `Not found: ${resolved}` };
|
|
647
|
-
try {
|
|
648
|
-
const entries = readdirSync(resolved, { withFileTypes: true, recursive: !!recursive });
|
|
649
|
-
return { files: entries.map((e) => (e.isDirectory() ? `${e.name}/` : e.name)) };
|
|
650
|
-
} catch (err) {
|
|
651
|
-
return { error: err.message };
|
|
652
|
-
}
|
|
653
|
-
},
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
const runCommand = defineTool("run_command", {
|
|
657
|
-
description: "Run a shell command and return stdout/stderr.",
|
|
658
|
-
parameters: {
|
|
659
|
-
type: "object",
|
|
660
|
-
properties: {
|
|
661
|
-
command: { type: "string", description: "Shell command to execute" },
|
|
662
|
-
cwd: { type: "string", description: "Working directory" },
|
|
663
|
-
},
|
|
664
|
-
required: ["command"],
|
|
665
|
-
},
|
|
666
|
-
handler: ({ command: cmd, cwd }) => {
|
|
667
|
-
const workDir = cwd ? resolve(target, cwd) : target;
|
|
668
|
-
console.log(` [tool] run_command: ${cmd} (cwd=${workDir})`);
|
|
669
|
-
const blocked = /\bgit\s+(commit|push|add|reset|checkout|rebase|merge|stash)\b/;
|
|
670
|
-
if (blocked.test(cmd)) {
|
|
671
|
-
console.log(` [tool] BLOCKED git write command: ${cmd}`);
|
|
672
|
-
return { error: "Git write commands are not allowed. Use read_file/write_file tools instead." };
|
|
673
|
-
}
|
|
674
|
-
try {
|
|
675
|
-
const stdout = execSync(cmd, { cwd: workDir, encoding: "utf8", timeout: 120000 });
|
|
676
|
-
return { stdout, exitCode: 0 };
|
|
677
|
-
} catch (err) {
|
|
678
|
-
return { stdout: err.stdout || "", stderr: err.stderr || "", exitCode: err.status || 1 };
|
|
679
|
-
}
|
|
680
|
-
},
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
return [readFile, writeFile, listFiles, runCommand];
|
|
684
|
-
}
|
|
685
|
-
|
|
686
450
|
// ─── Init Runner ─────────────────────────────────────────────────────
|
|
687
451
|
|
|
688
452
|
function initTransformFile(src, dst, label) {
|