forgedock 1.25.0 → 1.25.1
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/bin/engine-cli.mjs +30 -10
- package/bin/lean/prefilter.test.mjs +18 -0
- package/commands/orchestrate.md +4 -2
- package/package.json +1 -1
package/bin/engine-cli.mjs
CHANGED
|
@@ -825,13 +825,14 @@ function prefilterModel(argv) {
|
|
|
825
825
|
}
|
|
826
826
|
|
|
827
827
|
/**
|
|
828
|
-
* `forgedock triage <issue...> [--lane L] [--repo owner/repo] [--dry-run] [--prefilter-model M]` (#4123)
|
|
828
|
+
* `forgedock triage <issue...> [--parallel K] [--lane L] [--repo owner/repo] [--dry-run] [--prefilter-model M]` (#4123)
|
|
829
|
+
* Issues are checked through a bounded pool of K (default 8, #4141); each lane is fetched once; rows print in input order.
|
|
829
830
|
* Each issue is checked against its own lane (#4125); `--lane` is an assertion, a refused lane is reported and dropped.
|
|
830
831
|
* Runs only the already-fixed pre-filter: closes issues it proves fixed (with evidence) and prints the issues that
|
|
831
832
|
* still need a lane as the last line (`lanes: #1 #2`). `--dry-run` reports candidates without a model call or close.
|
|
832
833
|
*/
|
|
833
834
|
export async function triageFromCli(argv, deps = {}) {
|
|
834
|
-
const valued = ["--lane", "--repo", "--prefilter-model"];
|
|
835
|
+
const valued = ["--lane", "--repo", "--prefilter-model", "--parallel"];
|
|
835
836
|
const issues = [];
|
|
836
837
|
for (let i = 0; i < argv.length; i++) {
|
|
837
838
|
if (valued.includes(argv[i])) { i++; continue; }
|
|
@@ -845,17 +846,36 @@ export async function triageFromCli(argv, deps = {}) {
|
|
|
845
846
|
const leanIo = deps.leanIo ?? (await import("./lean/effects.mjs")).makeLeanIo({ repo });
|
|
846
847
|
const { prefilterIssue, formatPrefilterSummary } = await import("./lean/prefilter.mjs");
|
|
847
848
|
const branches = readBranches();
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
|
|
849
|
+
const parallel = flag(argv, "--parallel") == null ? 8 : parseInt(flag(argv, "--parallel"), 10);
|
|
850
|
+
if (!Number.isInteger(parallel) || parallel < 1) throw new Error("--parallel must be a positive integer");
|
|
851
|
+
// #4141: bounded pool. Each lane is fetched once (memoized promise); output stays in input order.
|
|
852
|
+
const fetched = new Map();
|
|
853
|
+
const fetchLane = (l) => {
|
|
854
|
+
if (!fetched.has(l)) fetched.set(l, leanIo.git(["fetch", "origin", l], { cwd: process.cwd() }).catch(() => { /* offline: use the local ref */ }));
|
|
855
|
+
return fetched.get(l);
|
|
856
|
+
};
|
|
857
|
+
const uniq = [...new Set(issues)];
|
|
858
|
+
const out = new Array(uniq.length);
|
|
859
|
+
const checkOne = async (n) => {
|
|
851
860
|
// #4125: each issue's own lane; `--lane` is an assertion. A refused lane never reaches the pre-filter or a lane.
|
|
852
861
|
const ln = await resolveLane({ issue: n, repo, io: leanIo, cwd: process.cwd(), branches, requested: lane, override: argv.includes("--lane-override") });
|
|
853
|
-
if (!ln.ok) {
|
|
854
|
-
|
|
862
|
+
if (!ln.ok) return { line: `#${n} → lane-refused (${ln.code}: ${ln.detail})`, row: { issue: n, outcome: "lane-refused", cost_usd: 0, prefilter: null } };
|
|
863
|
+
await fetchLane(ln.lane);
|
|
855
864
|
const pf = await prefilterIssue({ issue: n, repo, lane: ln.lane, cwd: process.cwd(), io: leanIo, dryRun: argv.includes("--dry-run"), ...prefilterModel(argv) });
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
865
|
+
return { line: `#${n} → ${pf.closed ? "closed" : pf.candidate ? "candidate" : "lane"} (${pf.detail})`, row: { issue: n, outcome: pf.closed ? "invalid" : "pending", cost_usd: pf.cost_usd, prefilter: pf } };
|
|
866
|
+
};
|
|
867
|
+
let next = 0, printed = 0;
|
|
868
|
+
const flush = () => { while (printed < out.length && out[printed]) console.log(out[printed++].line); };
|
|
869
|
+
await Promise.all(Array.from({ length: Math.min(parallel, uniq.length) }, async () => {
|
|
870
|
+
while (next < uniq.length) {
|
|
871
|
+
const i = next++;
|
|
872
|
+
try { out[i] = await checkOne(uniq[i]); } catch (e) {
|
|
873
|
+
out[i] = { line: `#${uniq[i]} → lane (pre-filter error: ${String(e?.message ?? e).slice(0, 120)})`, row: { issue: uniq[i], outcome: "pending", cost_usd: 0, prefilter: { closed: false, candidate: false, detail: "pre-filter error", cost_usd: 0 } } };
|
|
874
|
+
}
|
|
875
|
+
flush();
|
|
876
|
+
}
|
|
877
|
+
}));
|
|
878
|
+
const rows = out.map((r) => r.row);
|
|
859
879
|
const lanes = rows.filter((r) => r.prefilter && !r.prefilter.closed).map((r) => r.issue);
|
|
860
880
|
console.log(formatPrefilterSummary(rows));
|
|
861
881
|
console.log(`lanes: ${lanes.map((n) => `#${n}`).join(" ") || "(none)"}`);
|
|
@@ -194,6 +194,24 @@ describe("orchestrate with the pre-filter (#4123)", () => {
|
|
|
194
194
|
await assert.rejects(triageFromCli([], {}), /usage/);
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
+
it("#4141 triage runs a bounded parallel pool, fetches each lane once, and prints rows in input order", async () => {
|
|
198
|
+
const issues = {}; for (let n = 1; n <= 6; n++) issues[n] = { body: `b${n}` };
|
|
199
|
+
const w = world({ issues, prs: [], verdict: { verdict: "VALID", evidence: [] } });
|
|
200
|
+
let live = 0, peak = 0, fetches = 0;
|
|
201
|
+
const gh0 = w.io.gh, git0 = w.io.git;
|
|
202
|
+
w.io.gh = async (a, o) => { live++; peak = Math.max(peak, live); await new Promise((r) => setTimeout(r, 5 + (Number(a.find((x) => /^\d+$/.test(x))) || 0) % 3)); live--; return gh0(a, o); };
|
|
203
|
+
w.io.git = async (a, o) => { if (a[0] === "fetch") fetches++; return git0(a, o); };
|
|
204
|
+
const logs = []; const l = console.log; console.log = (s) => logs.push(String(s));
|
|
205
|
+
let res;
|
|
206
|
+
try { res = await triageFromCli(["1", "2", "3", "4", "5", "6", "--repo", "o/r", "--lane", "staging", "--parallel", "3"], { leanIo: w.io }); } finally { console.log = l; }
|
|
207
|
+
const order = logs.filter((s) => /^#\d+ → /.test(s)).map((s) => Number(s.slice(1, s.indexOf(" "))));
|
|
208
|
+
assert.deepEqual(order, [1, 2, 3, 4, 5, 6]);
|
|
209
|
+
assert.ok(peak > 1 && peak <= 3, `peak ${peak}`);
|
|
210
|
+
assert.equal(fetches, 1);
|
|
211
|
+
assert.deepEqual(res.rows.map((r) => r.issue), [1, 2, 3, 4, 5, 6]);
|
|
212
|
+
await assert.rejects(triageFromCli(["1", "--repo", "o/r", "--parallel", "0"], { leanIo: w.io }), /--parallel/);
|
|
213
|
+
});
|
|
214
|
+
|
|
197
215
|
it("#4125 triage checks each issue against its own lane: a refused lane (--lane main, missing milestone branch) is reported and dropped", async () => {
|
|
198
216
|
const w = world({ issues: { 12: { body: "x" }, 13: { body: "y", milestone: { title: "Later" } } } });
|
|
199
217
|
const logs = []; const l = console.log; console.log = (s) => logs.push(String(s));
|
package/commands/orchestrate.md
CHANGED
|
@@ -33,12 +33,14 @@ Do this yourself from issue text and the code. Spawn no sub-agents here.
|
|
|
33
33
|
for the whole set:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
forgedock triage <N...>
|
|
36
|
+
forgedock triage <N...> --parallel <K>
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
It closes, with the evidence on the issue and no lane, every issue that a merged PR already fixed (one
|
|
40
40
|
short read-only validation on a small model) or an auto-filed CI failure whose workflow is green again
|
|
41
|
-
(the green run link). Its last line is `lanes: #A #B ...`: continue with only
|
|
41
|
+
(the green run link). It checks K issues at a time. Its last line is `lanes: #A #B ...`: continue with only
|
|
42
|
+
those issues. This is the only pre-filter in this command: lanes run `forgedock run-issue`, which does not
|
|
43
|
+
pre-filter again. Keep its
|
|
42
44
|
`pre-filter:` line for the report.
|
|
43
45
|
3. **Triage.** For each issue, read it once (`gh issue view N --json title,body,labels`) and record:
|
|
44
46
|
- **Ready / skip.** Skip when it is labelled `needs-human` or `workflow:merged`, or an open PR already
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgedock",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.1",
|
|
4
4
|
"description": "Autonomous development pipeline for coding agents. GitHub as institutional memory, with native Claude Code and OpenCode entrypoints.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"forgedock": "bin/forgedock.mjs"
|