@xn-intenton-z2a/agentic-lib 7.1.61 → 7.1.62
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-bot.yml +27 -3
- package/.github/workflows/agentic-lib-init.yml +4 -4
- package/.github/workflows/agentic-lib-schedule.yml +3 -3
- package/.github/workflows/agentic-lib-test.yml +12 -4
- package/.github/workflows/agentic-lib-workflow.yml +59 -22
- package/README.md +51 -8
- package/agentic-lib.toml +88 -17
- package/bin/agentic-lib.js +69 -5
- package/package.json +2 -1
- package/src/actions/agentic-step/action.yml +2 -2
- package/src/actions/agentic-step/config-loader.js +108 -55
- package/src/actions/agentic-step/copilot.js +175 -10
- package/src/actions/agentic-step/index.js +37 -2
- package/src/actions/agentic-step/logging.js +75 -0
- package/src/actions/agentic-step/tasks/discussions.js +17 -1
- package/src/actions/agentic-step/tasks/maintain-features.js +21 -1
- package/src/actions/agentic-step/tasks/maintain-library.js +7 -0
- package/src/actions/agentic-step/tasks/review-issue.js +6 -1
- package/src/actions/agentic-step/tasks/supervise.js +30 -4
- package/src/actions/agentic-step/tasks/transform.js +42 -6
- package/src/agents/agentic-lib.yml +1 -3
- package/src/iterate.js +285 -0
- package/src/mcp/server.js +24 -127
- package/src/seeds/zero-README.md +2 -2
- package/src/seeds/zero-package.json +1 -1
package/src/iterate.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
// Copyright (C) 2025-2026 Polycode Limited
|
|
3
|
+
// src/iterate.js — Shared iteration loop for CLI and MCP server
|
|
4
|
+
//
|
|
5
|
+
// Runs N cycles of maintain → transform → fix, tracking transformation cost
|
|
6
|
+
// against a budget. Stops early on consecutive test passes, no-progress, or
|
|
7
|
+
// budget exhaustion.
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
10
|
+
import { resolve, join, dirname } from "path";
|
|
11
|
+
import { execSync } from "child_process";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const binPath = resolve(__dirname, "../bin/agentic-lib.js");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Take a snapshot of all file contents in a directory (recursive).
|
|
19
|
+
* Returns an object mapping relative paths to file content strings.
|
|
20
|
+
*/
|
|
21
|
+
export function snapshotDir(dirPath) {
|
|
22
|
+
const snapshot = {};
|
|
23
|
+
if (!existsSync(dirPath)) return snapshot;
|
|
24
|
+
try {
|
|
25
|
+
const files = readdirSync(dirPath, { recursive: true });
|
|
26
|
+
for (const f of files) {
|
|
27
|
+
const fp = join(dirPath, String(f));
|
|
28
|
+
try {
|
|
29
|
+
snapshot[String(f)] = readFileSync(fp, "utf8");
|
|
30
|
+
} catch {
|
|
31
|
+
// skip non-readable files
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// skip unreadable dirs
|
|
36
|
+
}
|
|
37
|
+
return snapshot;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Count the number of files that differ between two snapshots.
|
|
42
|
+
*/
|
|
43
|
+
export function countChanges(before, after) {
|
|
44
|
+
let changes = 0;
|
|
45
|
+
const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
46
|
+
for (const key of allKeys) {
|
|
47
|
+
if (before[key] !== after[key]) changes++;
|
|
48
|
+
}
|
|
49
|
+
return changes;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Run an agentic-lib CLI command (e.g. "transform --target /tmp/ws --model gpt-5-mini").
|
|
54
|
+
*/
|
|
55
|
+
export function runCli(args, cwd, timeoutMs = 300000) {
|
|
56
|
+
const cmd = `node ${binPath} ${args}`;
|
|
57
|
+
try {
|
|
58
|
+
const stdout = execSync(cmd, {
|
|
59
|
+
cwd: cwd || resolve(__dirname, ".."),
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
timeout: timeoutMs,
|
|
62
|
+
env: { ...process.env },
|
|
63
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
64
|
+
});
|
|
65
|
+
return { success: true, output: stdout };
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
output: `STDOUT:\n${err.stdout || ""}\nSTDERR:\n${err.stderr || ""}`,
|
|
70
|
+
exitCode: err.status || 1,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Run tests in a workspace directory.
|
|
77
|
+
*/
|
|
78
|
+
export function runTests(wsPath, timeoutMs = 120000) {
|
|
79
|
+
try {
|
|
80
|
+
const stdout = execSync("npm test 2>&1", {
|
|
81
|
+
cwd: wsPath,
|
|
82
|
+
encoding: "utf8",
|
|
83
|
+
timeout: timeoutMs,
|
|
84
|
+
env: { ...process.env },
|
|
85
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
86
|
+
});
|
|
87
|
+
return { success: true, output: stdout, exitCode: 0 };
|
|
88
|
+
} catch (err) {
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
output: `STDOUT:\n${err.stdout || ""}\nSTDERR:\n${err.stderr || ""}`,
|
|
92
|
+
exitCode: err.status || 1,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Read cumulative transformation cost from intentïon.md.
|
|
99
|
+
* Each cost line: `**agentic-lib transformation cost:** N`
|
|
100
|
+
*/
|
|
101
|
+
export function readTransformationCost(targetPath) {
|
|
102
|
+
const logPath = resolve(targetPath, "intentïon.md");
|
|
103
|
+
if (!existsSync(logPath)) return 0;
|
|
104
|
+
const content = readFileSync(logPath, "utf8");
|
|
105
|
+
const matches = content.matchAll(/\*\*agentic-lib transformation cost:\*\* (\d+)/g);
|
|
106
|
+
return [...matches].reduce((sum, m) => sum + parseInt(m[1], 10), 0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Read transformation budget from agentic-lib.toml.
|
|
111
|
+
* Falls back to 8 (the "recommended" profile default).
|
|
112
|
+
*/
|
|
113
|
+
export function readBudget(targetPath) {
|
|
114
|
+
const tomlPath = resolve(targetPath, "agentic-lib.toml");
|
|
115
|
+
if (!existsSync(tomlPath)) return 8;
|
|
116
|
+
try {
|
|
117
|
+
const content = readFileSync(tomlPath, "utf8");
|
|
118
|
+
const match = content.match(/transformation-budget\s*=\s*(\d+)/);
|
|
119
|
+
return match ? parseInt(match[1], 10) : 8;
|
|
120
|
+
} catch {
|
|
121
|
+
return 8;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Detect the source path from agentic-lib.toml, defaulting to "src/lib".
|
|
127
|
+
*/
|
|
128
|
+
function detectSourcePath(targetPath) {
|
|
129
|
+
const tomlPath = resolve(targetPath, "agentic-lib.toml");
|
|
130
|
+
if (!existsSync(tomlPath)) return "src/lib";
|
|
131
|
+
try {
|
|
132
|
+
const content = readFileSync(tomlPath, "utf8");
|
|
133
|
+
const match = content.match(/^source\s*=\s*"([^"]+)"/m);
|
|
134
|
+
return match ? match[1] : "src/lib";
|
|
135
|
+
} catch {
|
|
136
|
+
return "src/lib";
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Run an iteration loop: N cycles of steps, with stop conditions and budget tracking.
|
|
142
|
+
*
|
|
143
|
+
* @param {Object} options
|
|
144
|
+
* @param {string} options.targetPath - Workspace root directory
|
|
145
|
+
* @param {string} [options.model] - Copilot SDK model name
|
|
146
|
+
* @param {number} [options.maxCycles] - Max iterations (0 = use budget)
|
|
147
|
+
* @param {string[]} [options.steps] - Steps per cycle
|
|
148
|
+
* @param {boolean} [options.dryRun] - Skip actual Copilot calls
|
|
149
|
+
* @param {Function} [options.onCycleComplete] - Callback after each cycle
|
|
150
|
+
* @returns {Promise<{results: Array, totalCost: number, budget: number}>}
|
|
151
|
+
*/
|
|
152
|
+
export async function runIterationLoop({
|
|
153
|
+
targetPath,
|
|
154
|
+
model = "gpt-5-mini",
|
|
155
|
+
maxCycles = 0,
|
|
156
|
+
steps = ["maintain-features", "transform", "fix-code"],
|
|
157
|
+
dryRun = false,
|
|
158
|
+
onCycleComplete,
|
|
159
|
+
}) {
|
|
160
|
+
const budget = readBudget(targetPath);
|
|
161
|
+
let totalCost = readTransformationCost(targetPath);
|
|
162
|
+
const remaining = Math.max(0, budget - totalCost);
|
|
163
|
+
const effectiveMax = maxCycles > 0 ? Math.min(maxCycles, remaining) : remaining;
|
|
164
|
+
|
|
165
|
+
if (effectiveMax <= 0) {
|
|
166
|
+
return {
|
|
167
|
+
results: [{ stopped: true, reason: `budget already exhausted (${totalCost}/${budget})` }],
|
|
168
|
+
totalCost,
|
|
169
|
+
budget,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const sourcePath = detectSourcePath(targetPath);
|
|
174
|
+
const results = [];
|
|
175
|
+
let consecutivePasses = 0;
|
|
176
|
+
let consecutiveNoChanges = 0;
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < effectiveMax; i++) {
|
|
179
|
+
const cycleStart = Date.now();
|
|
180
|
+
const cycleSteps = [];
|
|
181
|
+
|
|
182
|
+
// Snapshot source files before
|
|
183
|
+
const srcBefore = snapshotDir(resolve(targetPath, sourcePath));
|
|
184
|
+
|
|
185
|
+
// Run steps
|
|
186
|
+
for (const step of steps) {
|
|
187
|
+
if (dryRun) {
|
|
188
|
+
cycleSteps.push({ step, success: true, elapsed: "0.0", output: "[dry-run]" });
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const stepStart = Date.now();
|
|
192
|
+
const result = runCli(`${step} --target ${targetPath} --model ${model}`, targetPath);
|
|
193
|
+
const stepElapsed = ((Date.now() - stepStart) / 1000).toFixed(1);
|
|
194
|
+
cycleSteps.push({
|
|
195
|
+
step,
|
|
196
|
+
success: result.success,
|
|
197
|
+
elapsed: stepElapsed,
|
|
198
|
+
output: result.output.substring(0, 500),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Run tests
|
|
203
|
+
const testResult = dryRun ? { success: true, output: "[dry-run]" } : runTests(targetPath);
|
|
204
|
+
const testsPassed = testResult.success;
|
|
205
|
+
|
|
206
|
+
// Snapshot source files after
|
|
207
|
+
const srcAfter = dryRun ? srcBefore : snapshotDir(resolve(targetPath, sourcePath));
|
|
208
|
+
const filesChanged = countChanges(srcBefore, srcAfter);
|
|
209
|
+
const cost = filesChanged > 0 ? 1 : 0;
|
|
210
|
+
totalCost += cost;
|
|
211
|
+
|
|
212
|
+
const cycleElapsed = ((Date.now() - cycleStart) / 1000).toFixed(1);
|
|
213
|
+
|
|
214
|
+
const record = {
|
|
215
|
+
cycle: i + 1,
|
|
216
|
+
steps: cycleSteps,
|
|
217
|
+
testsPassed,
|
|
218
|
+
filesChanged,
|
|
219
|
+
cost,
|
|
220
|
+
totalCost,
|
|
221
|
+
budget,
|
|
222
|
+
elapsed: cycleElapsed,
|
|
223
|
+
model,
|
|
224
|
+
};
|
|
225
|
+
results.push(record);
|
|
226
|
+
if (onCycleComplete) onCycleComplete(record);
|
|
227
|
+
|
|
228
|
+
// Stop conditions
|
|
229
|
+
if (testsPassed) {
|
|
230
|
+
consecutivePasses++;
|
|
231
|
+
if (consecutivePasses >= 2) {
|
|
232
|
+
results.push({ stopped: true, reason: "tests passed 2 consecutive cycles" });
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
consecutivePasses = 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (filesChanged === 0) {
|
|
240
|
+
consecutiveNoChanges++;
|
|
241
|
+
if (consecutiveNoChanges >= 2) {
|
|
242
|
+
results.push({ stopped: true, reason: "no progress — 2 cycles with no file changes" });
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
consecutiveNoChanges = 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (totalCost >= budget) {
|
|
250
|
+
results.push({ stopped: true, reason: `budget exhausted (${totalCost}/${budget})` });
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return { results, totalCost, budget };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Format iteration results into a human-readable markdown string.
|
|
260
|
+
*/
|
|
261
|
+
export function formatIterationResults(results, totalCost, budget, label = "Iterate") {
|
|
262
|
+
const lines = [`# ${label} Results`, `Budget: ${totalCost}/${budget}`, ""];
|
|
263
|
+
for (const r of results) {
|
|
264
|
+
if (r.stopped) {
|
|
265
|
+
lines.push(`**Stopped:** ${r.reason}`);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
lines.push(`## Cycle ${r.cycle} (${r.model})`);
|
|
269
|
+
lines.push(`- Elapsed: ${r.elapsed}s`);
|
|
270
|
+
lines.push(`- Files changed: ${r.filesChanged}`);
|
|
271
|
+
lines.push(`- Tests: ${r.testsPassed ? "PASS" : "FAIL"}`);
|
|
272
|
+
lines.push(`- Cost: ${r.cost} (total: ${r.totalCost}/${r.budget})`);
|
|
273
|
+
for (const s of r.steps) {
|
|
274
|
+
lines.push(` - ${s.step}: ${s.success ? "OK" : "FAIL"} (${s.elapsed}s)`);
|
|
275
|
+
}
|
|
276
|
+
lines.push("");
|
|
277
|
+
}
|
|
278
|
+
const completed = results.filter((r) => !r.stopped).length;
|
|
279
|
+
const lastPassed = results.filter((r) => !r.stopped).slice(-1)[0]?.testsPassed;
|
|
280
|
+
lines.push("## Summary");
|
|
281
|
+
lines.push(`- Cycles completed: ${completed}`);
|
|
282
|
+
lines.push(`- Final test status: ${lastPassed ? "PASS" : "FAIL"}`);
|
|
283
|
+
lines.push(`- Total cost: ${totalCost}/${budget}`);
|
|
284
|
+
return lines.join("\n");
|
|
285
|
+
}
|
package/src/mcp/server.js
CHANGED
|
@@ -227,7 +227,7 @@ const TOOLS = [
|
|
|
227
227
|
overrides: {
|
|
228
228
|
type: "object",
|
|
229
229
|
description:
|
|
230
|
-
"Individual tuning knob overrides. Keys: reasoning-effort, infinite-sessions,
|
|
230
|
+
"Individual tuning knob overrides. Keys: reasoning-effort, infinite-sessions, max-feature-files, max-source-files, max-source-chars, max-issues, max-summary-chars, max-discussion-comments",
|
|
231
231
|
},
|
|
232
232
|
},
|
|
233
233
|
required: ["workspace"],
|
|
@@ -482,115 +482,40 @@ async function handleIterate({ workspace, cycles = 3, steps }) {
|
|
|
482
482
|
return text(`Workspace "${workspace}" not found.`);
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
+
const { runIterationLoop, formatIterationResults } = await import("../iterate.js");
|
|
485
486
|
const stepsToRun = steps || ["maintain-features", "transform", "fix-code"];
|
|
486
|
-
const results = [];
|
|
487
|
-
let consecutivePasses = 0;
|
|
488
|
-
let consecutiveNoChanges = 0;
|
|
489
487
|
const startIterNum = (meta.iterations?.length || 0) + 1;
|
|
490
488
|
|
|
491
489
|
meta.status = "iterating";
|
|
492
490
|
writeMetadata(wsPath, meta);
|
|
493
491
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
iterSteps.push({
|
|
511
|
-
step,
|
|
512
|
-
success: result.success,
|
|
513
|
-
elapsed: stepElapsed,
|
|
514
|
-
output: result.output.substring(0, 500),
|
|
492
|
+
const { results, totalCost, budget } = await runIterationLoop({
|
|
493
|
+
targetPath: wsPath,
|
|
494
|
+
model: meta.model,
|
|
495
|
+
maxCycles: cycles,
|
|
496
|
+
steps: stepsToRun,
|
|
497
|
+
onCycleComplete: (record) => {
|
|
498
|
+
if (record.stopped) return;
|
|
499
|
+
// Persist each iteration to workspace metadata
|
|
500
|
+
meta.iterations.push({
|
|
501
|
+
number: startIterNum + record.cycle - 1,
|
|
502
|
+
profile: meta.profile,
|
|
503
|
+
model: meta.model,
|
|
504
|
+
steps: record.steps,
|
|
505
|
+
testsPassed: record.testsPassed,
|
|
506
|
+
filesChanged: record.filesChanged,
|
|
507
|
+
elapsed: record.elapsed,
|
|
515
508
|
});
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const testResult = runInWorkspace("npm test 2>&1", wsPath, 60000);
|
|
520
|
-
const testsPassed = testResult.success;
|
|
521
|
-
|
|
522
|
-
// Snapshot source files after
|
|
523
|
-
const srcAfter = snapshotDir(join(wsPath, "src/lib"));
|
|
524
|
-
const filesChanged = countChanges(srcBefore, srcAfter);
|
|
525
|
-
|
|
526
|
-
const iterElapsed = ((Date.now() - iterStart) / 1000).toFixed(1);
|
|
527
|
-
|
|
528
|
-
const iterRecord = {
|
|
529
|
-
number: iterNum,
|
|
530
|
-
profile: meta.profile,
|
|
531
|
-
model: meta.model,
|
|
532
|
-
steps: iterSteps,
|
|
533
|
-
testsPassed,
|
|
534
|
-
filesChanged,
|
|
535
|
-
elapsed: iterElapsed,
|
|
536
|
-
};
|
|
537
|
-
|
|
538
|
-
meta.iterations.push(iterRecord);
|
|
539
|
-
writeMetadata(wsPath, meta);
|
|
540
|
-
|
|
541
|
-
results.push(iterRecord);
|
|
542
|
-
|
|
543
|
-
// Check stop conditions
|
|
544
|
-
if (testsPassed) {
|
|
545
|
-
consecutivePasses++;
|
|
546
|
-
if (consecutivePasses >= 2) {
|
|
547
|
-
results.push({ stopped: true, reason: "tests passed for 2 consecutive iterations" });
|
|
548
|
-
break;
|
|
549
|
-
}
|
|
550
|
-
} else {
|
|
551
|
-
consecutivePasses = 0;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
if (filesChanged === 0) {
|
|
555
|
-
consecutiveNoChanges++;
|
|
556
|
-
if (consecutiveNoChanges >= 2) {
|
|
557
|
-
results.push({ stopped: true, reason: "no files changed for 2 consecutive iterations (stalled)" });
|
|
558
|
-
break;
|
|
559
|
-
}
|
|
560
|
-
} else {
|
|
561
|
-
consecutiveNoChanges = 0;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
509
|
+
writeMetadata(wsPath, meta);
|
|
510
|
+
},
|
|
511
|
+
});
|
|
564
512
|
|
|
565
513
|
meta.status = "ready";
|
|
566
514
|
writeMetadata(wsPath, meta);
|
|
567
515
|
|
|
568
|
-
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
if (r.stopped) {
|
|
572
|
-
lines.push(`**Stopped early:** ${r.reason}`);
|
|
573
|
-
continue;
|
|
574
|
-
}
|
|
575
|
-
lines.push(`## Iteration ${r.number} (${r.model}, ${r.profile})`);
|
|
576
|
-
lines.push(`- Elapsed: ${r.elapsed}s`);
|
|
577
|
-
lines.push(`- Files changed: ${r.filesChanged}`);
|
|
578
|
-
lines.push(`- Tests: ${r.testsPassed ? "PASS" : "FAIL"}`);
|
|
579
|
-
for (const s of r.steps) {
|
|
580
|
-
lines.push(` - ${s.step}: ${s.success ? "OK" : "FAIL"} (${s.elapsed}s)`);
|
|
581
|
-
}
|
|
582
|
-
lines.push("");
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
const totalIters = results.filter((r) => !r.stopped).length;
|
|
586
|
-
const lastPassed = results.filter((r) => !r.stopped).slice(-1)[0]?.testsPassed;
|
|
587
|
-
lines.push("## Summary");
|
|
588
|
-
lines.push(`- Iterations completed: ${totalIters}`);
|
|
589
|
-
lines.push(`- Final test status: ${lastPassed ? "PASS" : "FAIL"}`);
|
|
590
|
-
lines.push(`- Total iterations for this workspace: ${meta.iterations.length}`);
|
|
591
|
-
lines.push(`- Profile: ${meta.profile} | Model: ${meta.model}`);
|
|
592
|
-
|
|
593
|
-
return text(lines.join("\n"));
|
|
516
|
+
const output = formatIterationResults(results, totalCost, budget, `Iterate: ${workspace}`);
|
|
517
|
+
const extra = `\n- Total iterations for this workspace: ${meta.iterations.length}\n- Profile: ${meta.profile} | Model: ${meta.model}`;
|
|
518
|
+
return text(output + extra);
|
|
594
519
|
}
|
|
595
520
|
|
|
596
521
|
async function handleRunTests({ workspace }) {
|
|
@@ -841,34 +766,6 @@ function profileDefaultModel(profile) {
|
|
|
841
766
|
return models[profile] || "gpt-5-mini";
|
|
842
767
|
}
|
|
843
768
|
|
|
844
|
-
function snapshotDir(dirPath) {
|
|
845
|
-
const snapshot = {};
|
|
846
|
-
if (!existsSync(dirPath)) return snapshot;
|
|
847
|
-
try {
|
|
848
|
-
const files = readdirSync(dirPath, { recursive: true });
|
|
849
|
-
for (const f of files) {
|
|
850
|
-
const fp = join(dirPath, String(f));
|
|
851
|
-
try {
|
|
852
|
-
snapshot[String(f)] = readFileSync(fp, "utf8");
|
|
853
|
-
} catch {
|
|
854
|
-
// skip non-readable files
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
} catch {
|
|
858
|
-
// skip unreadable dirs
|
|
859
|
-
}
|
|
860
|
-
return snapshot;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
function countChanges(before, after) {
|
|
864
|
-
let changes = 0;
|
|
865
|
-
const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
866
|
-
for (const key of allKeys) {
|
|
867
|
-
if (before[key] !== after[key]) changes++;
|
|
868
|
-
}
|
|
869
|
-
return changes;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
769
|
// ─── MCP Server ─────────────────────────────────────────────────────
|
|
873
770
|
|
|
874
771
|
const toolHandlers = {
|
package/src/seeds/zero-README.md
CHANGED
|
@@ -60,8 +60,8 @@ source = "src/lib/"
|
|
|
60
60
|
tests = "tests/unit/"
|
|
61
61
|
|
|
62
62
|
[limits]
|
|
63
|
-
feature-issues = 2 # max concurrent feature issues
|
|
64
|
-
attempts-per-issue = 2 # max retries per issue
|
|
63
|
+
max-feature-issues = 2 # max concurrent feature issues
|
|
64
|
+
max-attempts-per-issue = 2 # max retries per issue
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
## Updating
|