forgedock 1.24.2 → 1.24.3
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/outcome-codes.mjs +1 -0
- package/bin/engine/runlog.mjs +16 -1
- package/bin/engine-cli.mjs +1 -1
- package/bin/engine.mjs +16 -2
- package/docs/OUTCOME-CODES.md +1 -0
- package/package.json +1 -1
|
@@ -36,6 +36,7 @@ export const OUTCOME_CODES = Object.freeze({
|
|
|
36
36
|
BASE_CONFLICT: "base.conflict",
|
|
37
37
|
RATE_LIMITED: "rate_limited",
|
|
38
38
|
HOST_BUSY: "host.busy",
|
|
39
|
+
PATH_SWITCH_CONFLICT: "path.switch_conflict",
|
|
39
40
|
ENGINE_ERROR: "engine.error",
|
|
40
41
|
INFRA_DISK_QUOTA: "infra.disk_quota",
|
|
41
42
|
INFRA_CLI_SPAWN_TRANSIENT: "infra.cli_spawn_transient",
|
package/bin/engine/runlog.mjs
CHANGED
|
@@ -56,7 +56,22 @@ export function readLog(dir, issue) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/** Fold events into a RunState. The commit rule lives here. */
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* #4109: the run log is shared by the full and the core path. Keep only the events of runs whose `path` (from RUN_START;
|
|
61
|
+
* missing means "full", for historic logs) equals `path`, so one path never replays the other's commits or terminals.
|
|
62
|
+
*/
|
|
63
|
+
export function scopeEvents(events, path = "full") {
|
|
64
|
+
let cur = "full";
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const e of events) {
|
|
67
|
+
if (e.event === "RUN_START") cur = e.path ?? "full";
|
|
68
|
+
if (cur === path) out.push(e);
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function deriveState(allEvents, { path = "full" } = {}) {
|
|
74
|
+
const events = scopeEvents(allEvents, path);
|
|
60
75
|
/** @type {import("./phases.mjs").RunState} */
|
|
61
76
|
const s = { v: 0, run: null, issue: null, lane: "staging", committed: [],
|
|
62
77
|
phase: null, branch: null, pr: null, terminal: false,
|
package/bin/engine-cli.mjs
CHANGED
|
@@ -510,7 +510,7 @@ export async function runFromCli(argv, deps = {}) {
|
|
|
510
510
|
return res;
|
|
511
511
|
}
|
|
512
512
|
// forge#3932: refused before any state write (open or unreadable **Depends on**); nothing ran.
|
|
513
|
-
if (res.terminalReason === "dependency-open" || res.terminalReason === "dependency-unreadable") {
|
|
513
|
+
if (res.terminalReason === "dependency-open" || res.terminalReason === "dependency-unreadable" || res.code === "path.switch_conflict") {
|
|
514
514
|
console.log(res.detail ?? "run refused: dependency check");
|
|
515
515
|
return res;
|
|
516
516
|
}
|
package/bin/engine.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { readFileSync, rmSync, readdirSync } from "node:fs";
|
|
|
8
8
|
import { execFileSync } from "node:child_process";
|
|
9
9
|
import { createHash, scryptSync } from "node:crypto";
|
|
10
10
|
import { hostname as osHostname, homedir } from "node:os";
|
|
11
|
-
import { appendEvent, readLog, deriveState, rewriteLog } from "./engine/runlog.mjs";
|
|
11
|
+
import { appendEvent, readLog, deriveState, rewriteLog, scopeEvents } from "./engine/runlog.mjs";
|
|
12
12
|
import { pickPhase, TERMINAL_REASONS, issueSnapshot, hasInvestigationExit, postCycleMarker, nextCycleNumber, newestCycle, cycleResetProvable, remediationHeadStale, decompositionRevoked, openDependency } from "./engine/phases.mjs";
|
|
13
13
|
import { reconcileState } from "./engine/reconcile.mjs";
|
|
14
14
|
import { makeProjector } from "./engine/projector.mjs";
|
|
@@ -440,7 +440,8 @@ export async function runIssue(opts) {
|
|
|
440
440
|
const projector = makeProjector(io);
|
|
441
441
|
|
|
442
442
|
// 1. Load + reconcile (GitHub wins).
|
|
443
|
-
|
|
443
|
+
// #4109: only this (full) path's own events: core-path commits and terminals in the shared log are never replayed here.
|
|
444
|
+
const local = scopeEvents(readLog(dir, issue)).length ? deriveState(readLog(dir, issue)) : null;
|
|
444
445
|
let remote = await projector.readState(issue);
|
|
445
446
|
let { state, action } = reconcileState(local, remote);
|
|
446
447
|
const leaseHeld = (r) => !!(r?.lease && r.lease.until > now() && r.lease.by !== agentId &&
|
|
@@ -495,6 +496,19 @@ export async function runIssue(opts) {
|
|
|
495
496
|
};
|
|
496
497
|
}
|
|
497
498
|
}
|
|
499
|
+
// #4109: an open core-path PR means the work already exists; a second implementation from the full path would duplicate it.
|
|
500
|
+
{
|
|
501
|
+
let prs = [];
|
|
502
|
+
try { prs = JSON.parse(String(await io.gh(["pr", "list", ...(repo ? ["-R", String(repo)] : []), "--head", `feat/issue-${issue}-lean`, "--state", "open", "--json", "number,headRefName"])) || "[]"); } catch { prs = []; }
|
|
503
|
+
prs = Array.isArray(prs) ? prs.filter((x) => x?.headRefName === `feat/issue-${issue}-lean`) : [];
|
|
504
|
+
if (Array.isArray(prs) && prs.length && Number.isInteger(prs[0]?.number)) {
|
|
505
|
+
return {
|
|
506
|
+
terminalReason: "needs-human",
|
|
507
|
+
code: OUTCOME_CODES.PATH_SWITCH_CONFLICT,
|
|
508
|
+
detail: `issue ${issue} has an open core-path PR #${prs[0].number}; rerun on the default path (base-sync) or close that PR first.`,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
498
512
|
// forge#3824: hoisted so RUN_START can carry the repo (lazy, memoised; null when unresolved).
|
|
499
513
|
let repoPromise = null;
|
|
500
514
|
const resolveRepo = () => (repoPromise ??= (async () => {
|
package/docs/OUTCOME-CODES.md
CHANGED
|
@@ -33,6 +33,7 @@ An absent or unknown code is recorded as `unclassified`, with the raw reason kep
|
|
|
33
33
|
| `ci.red` | CI failed | reserved | `REVIEW_RESULT` carries no CI field yet |
|
|
34
34
|
| `ci.timeout` | CI timed out | reserved | `REVIEW_RESULT` carries no CI field yet |
|
|
35
35
|
| `base.conflict` | Base-branch conflict blocked the PR | reserved | `REVIEW_RESULT` carries no conflict field yet |
|
|
36
|
+
| `path.switch_conflict` | The full path was started on an issue that has an open core-path PR (`feat/issue-<n>-lean`); refused to avoid a second implementation. Rerun on the default path (base-sync) or close that PR first | engine | `bin/engine.mjs` admission |
|
|
36
37
|
| `host.busy` | Core run deferred at admission: free memory, free tmp space or load per core outside `engine.admission` limits; no lease taken, nothing posted | lean | `bin/lean/admission.mjs` |
|
|
37
38
|
| `rate_limited` | Session or usage limit outlasted the pause budget | engine | `err.usageLimit` / `err.resetAtEpochMs` |
|
|
38
39
|
| `engine.error` | Engine or tool failure (missing key/SDK, CLI crash, max turns, unresolved args, runner threw every attempt) | engine | `err.code` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgedock",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.3",
|
|
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"
|