iceboundrock / iceboundrock/AutoForge

EPIC: AutoForge hardening roadmap toward unattended operation

Open
#23 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Python
Stars
0
Forks
0
Avg merge
8h 56m
Merged PRs (30d)
40

Description

Tracking EPIC for all open engineering work on AutoForge / 智铸, derived from the AutoForge Engineering Review (2026-09-06, commit a039525), the PR #1 review follow-ups, the consolidated reviews of PR #27 / #28 / #29 / #32, the PR #38 (hosted CI) review, the eleven review rounds of PR #44 (LOCAL mode), the merge-gate PRs #58 / #60 / #62 / #63, the replan follow-up PRs #66 / #70 / #72, and the REVIEW-payload PRs #76 / #79.

Current recommended autonomous operation level: 2 of 5 — supervised single-issue run to READY_FOR_MERGE, human merges.

MERGE and UPDATE_EPIC are implemented and verified controller-side (#8, #3, #12), the REVIEW/FIX loop is bounded (#9), corrupt state fails loudly (#11), REPLAN_REEXECUTE landed as a durable transaction (PR #32), the controller lock is keyed by the git common dir and held for the whole command (#16), ci is a real required check on main (#31, PR #38, with safety.protected_merge_paths guarding .github/workflows/), and LOCAL mode — feature Markdown → implement → review → fix with no GitHub — landed in PR #44 with its own workspace-identity and filesystem-boundary design (ADR 0001).

Phase 0 is complete as of 2026-09-13. safety.allow_merge is the only key that opens the gate (#18, PR #58), doctor verifies that main really requires ci (#41, PR #60), the protected-path check paginates the changed-files listing (#43, PR #62), and the pre-merge step compares the check definition against the base branch and re-runs merge.verification_commands at the reviewed HEAD (#42, PR #63). #39 (pin CI actions to commit SHAs) was declined. The gate's own preconditions are therefore met; enabling safety.allow_merge for a supervised run is now an operator decision, while unattended merge (Level 4) still needs Level 3 plus the one merge-path follow-up (#68) surfaced by the replan work.

Phase 1 is complete as of 2026-09-16. The persisted replan transaction is schema-validated and fails closed on corruption, with the state protocol bumped to 2 (#35, PR #66); the window rules are scoped, the replan budget is idempotent on replay, and REPLAN steps are accounted (#36, PR #70); runlog metadata is redacted and every crash window, dry-run and activation path is exercised through save → reload → step (#37, PR #72); the REVIEW payload is bounded at the parser — finding count, id, title, location and required_resolution length — with rejection, never clipping (#34, PR #76); and an accepted review's redacted resolutions stay inside the persisted evidence bound, so a replan can proceed without the evidence-truncated block (#33, PR #79). The instruction files were refactored into the scoped AGENTS.md + docs/agent-guides/ layout in the same period (#73, PR #74).

Last roadmap revision: 2026-09-16, after the merge of PR #79 (d4faddb). 33 issues open (excluding this EPIC), 10 of them filed from the Phase 1 reviews.

Dependency chains

Phase 0 (merge gate) — done: #18 #41 #43 #42 landed, #39 declined
 ├─ #59 reject unknown config keys in every section          — done (PR #64)
 │    └─ #65 subset YAML parser errors do not name the file  (cosmetic, independent)
 └─ #68 bind the clean review to the PR identity before MERGE (same shape as #66 P1 on the replan
        path; the only item left that gates the merge half of Level 4)

Phase 1 (replan) — done: #35 #36 #37 #34 #33 landed
 ├─ #71 step budget can block a resume inside the replan write window   (from PR #70)
 ├─ #69 restore automatic resume for the crash-before-close window      (from PR #66 R11-F1; low priority)
 └─ #67 status --json: derived journal defects beside the raw journal   (from PR #66; shares the
        output path with #6 — whichever lands second rebases)

Parser bounds (from #34 / PR #76):
 #77 bound the FIX payload (rationale length, resolution count, commit_sha shape)
  ├─ #78 decide whether one-line finding fields may carry control characters
  │      (same rejection rule; decide with #77 so the reviewer and fixer bounds land together)
  ├─ #19 SHA length drift (#77's commit_sha check is the same regex)
  └─ #53 executor capture bound + CONTROL_RESULT payload size (same rejection rule)
        └─ #17 non-UTF-8 agent output (same file, same change)

PR #44 follow-ups (log tree, executor, LOCAL launch accounting):
 #57 charge the LOCAL launch at the launch site          (bug, independent)
 #55 bound the append-side journal read  ─┐
 #56 bound the sibling-run directory scan ─┴─ subsumed by #51 journal redesign
 #53 bound stdout/stderr capture           ── #51 (what a journal line may hold)
 #54 narrow the LOCAL no-GitHub wording    ── #22 (README claims), #10 (the sandbox half)
 #52 O_TMPFILE on Linux                    (optional hardening, no dependency)

Redaction at the persistence boundary:
 #81 REMOTE FIX resolutions persisted unredacted (one line + test; from PR #79 R3-F1)
  └─ #6 redact status / status --json output (the output side of the same hole)
        └─ #21 redaction patterns: fine-grained PATs, Basic auth, JWTs, URL credentials

REVIEW → FIX handoff:
 #80 pass the verified review comment URL to the fixer — mostly already implemented
     (`_verify_review_comment` + `{{REVIEW_COMMENT_URL}}` in fix.md); residual scope only
  └─ #14 review-comment recovery probe (finding the comment by marker without the agent's URL)

#10 worktree + environment isolation (REMOTE mode)
 ├─ #14 review-comment / FIX recovery probes
 └─ #54's "real sandbox" half (filed there, not in #54)

#13 controller-owned EPIC batching + managed-section splice
 └─ absorbs #4 (prompt-side confinement of the roadmap edit)

#2 wire in / remove decide_next_phase ── #22 (the "all transition logic lives in transitions.py" claim)

#75 make check ↔ hosted CI (lockfile check, opt-in Python matrix) ── #40 (the PyYAML branch no
    environment runs is the same "what does CI actually exercise" question)

Execution roadmap

Phases are ordered by execution, not by severity. Phase numbering is unchanged from the 2026-09-12 revision: Phase 0 = merge gate, Phase 1 = replan hardening, Phase 2 = PR #44 follow-ups and payload bounds, Phase 3 = reliability, Phase 4 = EPIC ownership, Phase 5 = hygiene. This revision closes Phase 1, files the ten follow-ups from its reviews into their phases, adds #68 as the single Phase 0 follow-up, and triages #80 against what main already does.

Execution order (next 2026-09-16)

The suggested order across phases, smallest-and-most-independent first, dependencies respected. Each line is one PR unless noted.

  1. #81 — redact REMOTE FIX resolutions at the persistence boundary (one line + one test)
  2. #57 — charge the LOCAL launch at the launch site (bug, independent)
  3. #17 + #53 — executor decode errors, head+tail capture bound, CONTROL_RESULT payload size bound (one change)
  4. #77 + #78 — bound the FIX payload; decide the control-character rule for one-line fields in the same change (closes the parser-bound track opened by #34; touch _SHA_RE once for #19's SHA-length half)
  5. #55 + #56 — the two stopgap bounds, or #51 if the journal design is decided first (either subsumes both)
  6. #71 — let a replan journal past the destructive write finish under the step budget
  7. #68 — bind the clean review to the reviewed PR identity before MERGE (gates Level 4)
  8. #6 + #67 — status / status --json: redact state-derived output and add the derived journal-defect field (same output path, one PR)
  9. #15 — re-scope parse_pr_url escapes to the remaining call sites
  10. #14 (+ #80 residual) — REVIEW recovery probe, stale-review finding retention, FIX-entry HEAD drift; persist the canonical comment URL from the matched GitHub object while there
  11. #5 — explicit unblock path for BLOCKED runs
  12. #10 — REMOTE worktree + environment allow-list + anchor check (largest item; last of Level 3)
  13. Phase 4: #13 (absorbing #4), then the rest of #19
  14. Phase 5 as capacity allows: #21, #20, #65, #75, #40, #22 (after #54), #2, #7, #30, #52, #54
  15. #69 — last; low priority, recorded so the naive retry is not reintroduced
Resolved by PR #44 (closed 2026-09-12 after verification on main)

PR #44 (148c7f8) closed these by design. Each was re-verified against the merged code and its pinning tests before closing; no further work is planned.

  • #45 LOCAL --allow-dirty: baseline contents not pinned — dissolved: the snapshot content-hashes every regular file, so dirty baseline files are pinned like any other (test_workspace_shape_matrix[tracked-dirty-at-baseline])
  • #47 LOCAL mode with a submodule or nested repository — decided: unsupported in LOCAL v1; a .git entry below the root refuses the snapshot and names the local.exclude entry that would declare the subtree unreviewed (ADR 0001). Re-open as a feature request if recursive snapshots are wanted
  • #48 crash-shaped runtime artifacts excluded by name — dissolved: LOCAL state moved to <git common dir>/autoforge/state, so nothing is carved out of the fingerprint and is_runtime_artifact is gone
  • #49 clean tracked symlink's external target unbound — dissolved: the controller walk classifies every symlink by target text and refuses one resolving outside the tree, clean or dirty
  • #50 pathname-based runtime-path safety — answered: safefs.SafeRoot resolves every name with dir_fd= + O_NOFOLLOW; the root pathname is the one stated limit (test_the_root_pathname_is_resolved_normally_and_that_is_the_stated_limit)
Phase 0 — before safety.allow_merge is enabled anywhere (complete 2026-09-13)
  • #8 [AF-PROMPT-001] common.md forbids merging while merge.md instructs gh pr merge; decide who owns the merge (closed by #24, merge 27dc6a1)
  • #3 Controller-side GitHub verification before and after MERGE (AF-WORKFLOW-001 / AF-WORKFLOW-003) (closed by #25)
  • #12 [AF-WORKFLOW-001b] Verify next_issue_url before switching issues in MERGE / UPDATE_EPIC (closed by #26, merge ccb2616)
  • #9 [AF-WORKFLOW-002] Review-round cap and stagnation detection (closed by #27, merge d16fb92)
  • #11 [AF-CODE-001] run fails loudly on a corrupted state.json; --force moves it aside (closed by #28, merge bbb6afb)
  • #31 Hosted CI workflow (pytest, ruff, mypy) so the MERGE gate has a real required check (closed by #38; ci is required on main via ruleset 22792049, and safety.protected_merge_paths blocks a PR that edits .github/workflows/)
  • #18 [AF-SEC-001] Deprecated execution.allow_merge also opens the merge gate; unknown safety keys ignored — closed by PR #58 (704de3e): execution.allow_merge is a hard ConfigurationError, unknown safety keys are rejected, doctor prints the effective gate and its source (Config.allow_merge_source)
  • #41 doctor: verify the default branch really requires the ci check — closed by PR #60 (bb71184): read-only ruleset check, including the disabled-ruleset case the effective-rules read cannot see
  • #39 Pin CI actions to commit SHAs — declined 2026-09-13 (not planned); the mutable actions/checkout@v7 / astral-sh/setup-uv@v10.0.1 tags stay as an accepted risk, and #42's check-definition comparison is the compensating control
  • #43 Pre-merge gate: a PR with more than 100 changed files can never pass the protected-path check — closed by PR #62 (a784ed2): the listing paginates to the endpoint's cap; the changedFiles comparison and the incomplete-listing refusal are unchanged
  • #42 Pre-merge: a green ci proves the suite ran, not what it asserted — closed by PR #63 (4ca622f): safety.verify_check_definition compares the PR's check run jobs/steps against the base branch's own run, and merge.verification_commands re-run in a temporary export of the reviewed HEAD (premerge.py); landed without #10's dedicated worktree
Phase 0 follow-up (gates Level 4)
  • #68 Bind the clean review to the reviewed PR identity before MERGE, not only to the reviewed HEAD — _merge_step / _verify_pr_for_merge bind to state.reviewed_head_sha only (--match-head-commit); a current_pr_url substituted with a same-repository PR at the same HEAD on the same branch but a different base passes every gate and merges a PR whose base no review decided on. Same shape as the #66 round-4 P1 fix on the replan path (decision_pr_url). Persist the reviewed PR identity (and consider the reviewed base_ref) with the clean review, require current_pr_url to be the same PR by identity at READY_FOR_MERGE and before the MERGE write, BLOCKED on mismatch, never a silent re-bind. Less severe than the replan case (the merged code is the reviewed commit; safety.allow_merge still applies) but the same class
Phase 1 — replan hardening (follow-ups to PR #32; complete 2026-09-16)

REPLAN_REEXECUTE is the only phase that performs a destructive GitHub write on agent work. Its transaction is sound; these were fail-closed and evidence gaps around it.

  • #35 Replan follow-up: persisted-transaction validation, source-branch check, status handling — closed by PR #66 (c52d878): ReplanTransaction.from_dict schema-validates every field and per-stage requirement, a corrupt journal replays a durable REJECTED + BLOCKED on every resume, the source head_ref is required at PENDING, the decision is bound to the reviewed PR (decision_pr_url) and to the run, the close is reachable from VERIFIED only (R11-F1), state protocol bumped to 2, status prints journal: CORRUPT (...)
  • #36 Replan follow-up: trigger semantics, budget idempotence, step accounting — closed by PR #70 (c18454a): escalation_count and superseded_prs are idempotent on replay of a durable SUPERSEDED, stagnation_after_soft_threshold no longer escalates all-new-findings progress, REPLAN steps are counted, and the fresh-PENDING budget block is pinned (test_t2_*)
  • #37 Replan follow-up: runlog redaction + crash/dry-run/activation test gaps — closed by PR #72 (cff6241): events.jsonl error / metadata redaction is pinned, every crash window is exercised through save → reload → step, dry-run, activation source-drift and replacement-side confirmation failure are covered, and replan checkpoints and listings compare by GitHub identity, never canonical strings
  • #34 Bound the REVIEW payload the parser accepts (finding count and required_resolution length) — closed by PR #76 (c60abea): MAX_FINDINGS_PER_REVIEW, MAX_FINDING_ID_CHARS, title / location / required_resolution length bounds at ReviewResult.from_payload, rejection with a correctable message that names the size and the limit and never the text; the reviewer is told the bounds through REVIEW_BOUND_VARIABLES; the FIX prompt size is pinned against them (test_fix_prompt_size_is_bounded_by_the_review_bounds)
  • #33 Let a replan proceed when review evidence exceeded the persisted bounds — closed by PR #79 (d4faddb): an accepted review's redacted resolutions stay inside the persisted evidence bound (the bound is applied after redaction in loop_guard), so the parser cap from #34 makes the evidence_truncated block unreachable for a controller-accepted round
Phase 1 follow-ups (from the #66 / #70 reviews)
  • #71 Step budget can block a resume inside the replan write window, stranding a closed source PR — _step_once checks max_total_steps before _drive_replan, so a resume at SUPERSEDE_INTENT (receipt present), SUPERSEDED or COMPENSATING with step_count == max_total_steps enters terminal BLOCKED with the plain budget reason: source PR closed, replacement never installed as current_pr_url (or a decided reopen never carried out), superseded_prs / escalation_count untouched. Preferred: exempt the post-write stages from the gate (the step still counts; no agent is invoked there) so the budget ends the run at the next phase boundary; PENDING / PREPARED / VERIFIED keep blocking as pinned by test_t2_*. Alternatively keep blocking but name the txn id, both PRs and the manual step. State the rule in the loop-bounds section of workflow.md
  • #69 Restore automatic resume for the crash-before-close window with proof-grade evidence — low priority. #66 R11-F1 refuses every OPEN-at-intent resume because "close never ran" and "close landed, crash before receipt, human reopened" are the same evidence; the cost is that a sub-second crash between persisting SUPERSEDE_INTENT and gh pr close landing needs a human. The only acceptable design is the timeline-event watermark (monotonic, undeletable event ids read before the close; a closed event past the watermark blocks); the controller-posted pre-close marker turns absence of a comment into a licence for a destructive write and is rejected. Needs a GitHubClient timeline read, a new journal field required at SUPERSEDE_INTENT (protocol bump), fake support; the test_r11f1_*, test_w6_* and transient-close no-second-close assertions must keep holding
  • #67 status --json: surface the derived replan journal defects beside the raw journal — status prints journal: CORRUPT (...), --json prints the journal as persisted and a consumer cannot tell usable from corrupt without reimplementing the validation. Add a derived sibling field (replan_journal: {readable, stage, defects}) from the same from_dict call status uses; never merge into the raw object; a non-object journal keeps raising StateError. Shares the output path with #6
Phase 2 — log tree, executor, LOCAL launch accounting and payload bounds (follow-ups to PR #44 and PR #76)

The R11 statement "a journal refusal lands before a write-capable agent has done work" is true of the recovery read only. These close the remaining unbounded reads, the mis-charged launch, and the two agent payloads (FIX result, raw stdout) that are still accepted unbounded; they apply to both REMOTE and LOCAL runs except where marked.

  • #57 LOCAL: a pre-launch refusal inside _invoke_phase is charged as a write-agent launch — a refused journal, a ConfigurationError from validate_profile or a missing template each consume one of MAX_LOCAL_PHASE_ATTEMPTS with zero launches, and the eventual block reason is false. Charge at the launch site, immediately before provider.execute(), the way correction re-launches already are (_charge_local_launch)
  • #55 SafeRoot.append_text reads the existing file unbounded — the one journal read that happens after the agent ran bypasses MAX_EVENT_JOURNAL_BYTES and carries an oversized file forward (safefs.py:622 takes no limit, unlike read_bytes at :550). Stopgap that holds under any #51 design: a limit parameter on append_text mapped to the existing _refuse_journal StateError
  • #56 RunLogger lists every sibling run in logs/ before skipping it, with no budget — cost of opening the logger is O(entries in logs/); walk logs.subroot(run_id) one level deep with max_entries=MAX_EVENT_JOURNAL_RECORDS. The refusal must not consume a LOCAL launch (#57)
  • #53 Executor: agent stdout/stderr capture is unbounded and reaches the log tree whole — several in-memory copies (decode + redaction) before any bound; a head+tail capture bound with a truncation mark, and a CONTROL_RESULT payload size bound at parse time (rejected, not clipped, as in #34). Do #17 in the same change
  • #77 Bound the FIX payload the parser accepts — rationale has a minimum but no maximum and is persisted whole in state.last_fix_resolutions (and echoed into a LOCAL block_reason); commit_sha is never matched against _SHA_RE; follow_up_issue_url is unbounded before parse_issue_url quotes it; the resolutions list has no count bound before every element is parsed. Same policy as #34: MAX_FIX_RATIONALE_CHARS (≤ MAX_REQUIRED_RESOLUTION_CHARS, relation pinned), commit_sha must match _SHA_RE, count checked at MAX_FINDINGS_PER_REVIEW before parsing elements, URL length bounded, bounds told to the fixer through template variables. LOCAL REVIEW observations stay unbounded by design (parsed and discarded)
  • #78 Decide whether REVIEW one-line finding fields (title, location) may carry control characters — the render path is safe (escape_inline, growing fence) and the prompt stays bounded, but a control-character-filled title renders at 4–6× and persists at 6× (json.dumps \u00XX) the bytes of a real one, per round, for as many rounds as the loop runs. Decision: reject at parse time (correctable "keep it to one line" error) or keep render-only. The #76 R3 data point extends the question to control characters other than newline inside required_resolution. Decide together with #77 so the bound set lands once; escape_inline stays regardless
  • #51 RunLogger: the event journal is read whole on resume and rewritten whole on every append — the recovery read is bounded since R11 (64 MiB / record count); the O(journal)-per-event append remains. Choose between a persisted next-seq side record (option 2) and a segmented journal (option 3); whichever lands should subsume #55 and #56
  • #54 LOCAL mode: the documented no-GitHub promise is wider than what the controller enforces — README says a local run "never touches GitHub" and local_common.md says the rule list is "enforced by the controller"; only the git anchor (HEAD + branch) is checked. Narrow both to what ADR 0001 §2.2 / §8.1 already say and pin with the local-template lint; the sandbox half belongs to #10
  • #52 safefs: close the named-temporary observation window with O_TMPFILE on Linux — not a defect of the boundary (a planted link can only observe bytes the same UID can already read; it can never make a controller write replace anything). Platform-conditional _unnamed_tmp_at with the nlink re-check kept on both paths; document in ADR 0001 §8.10 (today safefs.py:75 documents why it is not done)
Phase 3 — reliability for unattended single-issue runs
  • #59 Config: unknown keys are rejected under safety only; a typo in any other section is silently ignored — closed by PR #64 (1252655): every section, review.replan, each profiles.<name> mapping and the top level reject unknown keys with the message shape safety already used
  • #81 REMOTE FIX persists agent-authored resolutions unredacted — PR #44 put redact_dict in front of state at four sites and missed the fourth: engine.py:4296 state.last_fix_resolutions = [r.to_dict() ...] (LOCAL FIX at :1875 is redacted). A valid FIX result whose rationale quotes a token lands in state.json and status --json. One line plus one REMOTE regression test mirroring test_findings_and_resolutions_are_redacted_before_they_are_persisted, covering REMOTE REVIEW findings in the same test. #6 is the output side of the same hole
  • #17 [AF-CODE-004] Executor crashes with UnicodeDecodeError on non-UTF-8 agent output — executor.py:108 still passes text=True with no encoding/errors; a single invalid byte escapes the AutoForgeError exit-code mapping and skips the log write. Smallest change in the phase; do it alongside #53
  • #15 [AF-CODE-002] Agent-returned URLs raise ConfigurationError and bypass attempt persistence — partly addressed for issue URLs (_verify_issue_selection raises VerificationError), but parse_pr_url still escapes several replan and merge paths (13 call sites in engine.py; see #35 N3); re-scope to the remaining call sites. #77's follow_up_issue_url bound removes one input from this class
  • #14 [AF-WORKFLOW-004] Review-comment recovery, stale-review finding retention, FIX-entry HEAD drift detection — REVIEW has no recovery probe analogous to _try_recover_pr, a stale review with findings consumes the round and empties open_findings, and _prepare_fix cannot tell a landed-then-crashed push from a fresh entry. A recovery probe must find the round's comment by heading + marker without the agent's URL; that is the same lookup #80's residual scope needs
  • #80 Output the link of the verified review comment and give it to the coding agent — triage 2026-09-16: mostly implemented on main. _verify_review_comment (since 339df68) already requires the claimed URL to be a comment on the current PR, found on GitHub, carrying the # AI Code Review — Round N heading, the <!-- ai-review-result --> marker, the bound reviewed_head_sha and a consistent needs_fix_round; the verified URL is persisted in state.last_review_comment_url, survives resume, and fix.md renders it as {{REVIEW_COMMENT_URL}} beside REVIEW_ROUND, REVIEWED_HEAD_SHA and HEAD_SHA. Residual scope: persist the canonical URL from the matched GitHub comment object (match.url) rather than the agent-supplied string, state in fix.md that the linked comment is the authoritative review for this round and no other comment may be substituted, and pin the multi-round / mixed-conversation / stale / resume cases from the issue as tests. Re-scope the issue to that or close it as satisfied
  • #10 [AF-ARCH-001] Isolate agents: dedicated worktree, allow-listed environment, state dir outside the repo — re-scope to REMOTE mode: PR #44 already gives LOCAL runs a state directory outside the reviewed tree (<git common dir>/autoforge/state) and a git-anchor check before and after every phase, and #42 landed its pre-merge verification in a temporary export rather than waiting for the worktree. REMOTE agents still run in cwd with dict(os.environ) (executor.py:96). Remaining: the per-issue worktree, the environment allow-list, reusing the LOCAL anchor check for REMOTE, and the sandbox question deferred from #54
  • #16 [AF-CODE-003] Controller lock is scoped to state-dir path, not repository, and only held per step — closed 2026-09-10: locking.repository_lock_path keys on the git common dir and cli wraps whole commands in engine.locked()
  • #5 Explicit unblock path for BLOCKED runs — every bound (round cap, stagnation, the replan limit, MAX_LOCAL_PHASE_ATTEMPTS, max_total_steps) is terminal, so an operator has no supported way to continue after resolving the cause other than hand-editing state.json (which now fails loudly on any unknown key and refuses a protocol-1 replan journal). Must re-run recovery inspection against live state and go through validate_transition; #71 is the one budget block that should not need it
Phase 4 — multi-issue / EPIC ownership
  • #13 [AF-WORKFLOW-005] Controller must own EPIC batching, counter reset after verified write, and managed-section splice — merged_since_epic_update is written and rendered into the prompt (engine.py:845) but never read by the controller to decide anything; record_epic_update() resets the counter before anything confirms the body changed
  • #4 update_epic prompt must confine edits to the managed roadmap section (absorbed by #13 if the controller performs the splice)
  • #19 [AF-PROMPT-002] Prompt/parser drift: SHA length (_SHA_RE still {7,40} at result_parser.py:38), code-fence rule, unverified finding_ids, reviewer read-only rule — the SHA half is touched by #77 (commit_sha must match _SHA_RE); land the regex change there and keep the prompt-side items here
Phase 5 — maintainability and hygiene
  • #20 [AF-CODE-006] PR recovery only searches the first 100 open PRs — scoped to the ANALYZE_EXECUTE adoption lookup only (GitHubClient.list_open_prs(limit=100), github.py:1080); REPLAN_REEXECUTE already uses the strict listing. Search by head:autoforge/<n> / linked:<n> or paginate; recovery should find the PR rather than block
  • #21 [AF-SEC-002] Redaction misses fine-grained PATs, Basic auth, JWTs and credentials in URLs — redaction.py covers Authorization: Bearer|Token, classic gh*_ tokens and sk-/sk-ant- keys; none of the four named patterns is present yet. Do after #81 / #6 so the new patterns reach every boundary at once
  • #6 Redact state-derived output in status --jsonblock_reason is printed unredacted at cli.py:338, :553, :615 and :661, and status --json prints state.to_dict() whole (cli.py:568). Land with #67 (same output path)
  • #65 Subset YAML parser errors do not name the config file — _parse_yaml_subset raises ConfigurationError directly and load_config_file re-raises it unchanged, so the cannot parse config <path>: prefix that wraps tomllib / json / PyYAML errors never applies; the two backends also word the root-type error differently. Raise ValueError from the subset parser (as the #64 duplicate-key error already does) or prefix on re-raise. Cosmetic, follow-up to #59 / PR #64
  • #75 Bring make check closer to the hosted CI workflow — make check runs the same four commands as CI on one interpreter (pinned by test_ci_runs_the_same_commands_as_the_local_make_targets), but CI also installs with uv sync --locked and runs pytest on 3.11 and 3.12 while .python-version is 3.14. Add a lock-check target (uv lock --check) to check, fix the Makefile comment, optionally an opt-in check-matrix target reading the versions from one place, and extend the drift guard for any new mirrored target. Follow-up to #73 / PR #74
  • #22 [AF-DOC-001] README and docstrings overstate current guarantees — re-audit after #54; the #11 sentence is now true, the "all transition logic lives in transitions.py" sentence is still false until #2. The instruction files were re-cut in PR #74; README "Development" was corrected there, the rest of README was not re-audited
  • #2 Wire in or remove decide_next_phase (dead transition logic at transitions.py:173; referenced only from tests/test_transitions.py; cannot express REVIEW → REPLAN_REEXECUTE — see #37 N4)
  • #7 Minor hygiene — re-scoped: lock-file PID growth (the lock now replaces rather than appends), unknown state keys (State.from_dict refuses them since PR #44) and directory fsync are done. Remaining: REQUIRED_PROFILES duplicated in engine.py:229 and doctor.py:52, and the unused AgentInvocation dataclass at executor.py:149
  • #30 quarantine_state_file: fall back when hard links are unavailable; document the link+unlink crash window
  • #40 The yaml extra's PyYAML branch is exercised by no environment — the branch that decides routing, loop bounds and safety.allow_merge for a pip install autoforge[yaml] operator has zero coverage; add a differential test over the example config or remove the extra. Same question as #75 (what CI actually exercises)

Exit criteria per level

Level Requires
3 — unattended single issue, human merge Phase 1 follow-up #71 + Phase 2 bugs and bounds (#57, #55, #56, #53, #77) + Phase 3 (#81, #17, #15, #14, #10, #5)
4 — unattended single issue incl. merge Level 3 + #68 (bind the clean review to the PR identity before MERGE) — Phase 0 is otherwise complete (#18, #41, #43, #42 landed; #39 declined)
5 — unattended multi-issue Level 4 + Phase 4 (#13, #4, #19)

#69, #67, #78, #80 and the rest of Phase 2 (#51, #54, #52) and Phase 5 improve cost, documentation, operator visibility and hardening but gate no level. LOCAL mode (PR #44) is a separate product surface with its own promise — one working tree, no GitHub from the controller — and is not scored on this scale.

Only the section between the ai-controller-roadmap markers is intended to be machine-maintained. Text outside the markers is maintained by hand.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the execution roadmap and dependency chains in this issue, then choose a specific open child issue rather than working on the EPIC itself. Read that child issue's named files, tests, or pull requests first; the EPIC is complete only when its tracked engineering items and stated phase criteria are resolved.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
ai-infra-agents, backend, devtools
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
10/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.