Skip to content

fix(unic-archon-dlc): explicit staging and host-agnostic repo pinning in the Archon Boxes - #307

Closed
orioltf wants to merge 6 commits into
developfrom
archon/task-feature-unic-archon-dlc-289-stage-explicitly-pin-r
Closed

fix(unic-archon-dlc): explicit staging and host-agnostic repo pinning in the Archon Boxes#307
orioltf wants to merge 6 commits into
developfrom
archon/task-feature-unic-archon-dlc-289-stage-explicitly-pin-r

Conversation

@orioltf

@orioltf orioltf commented Aug 5, 2026

Copy link
Copy Markdown
Member

Fixes #289.

Why

Archon 0.7.0 removed git add -A from its own bundled command defaults and pinned every gh call to the origin repository (coleam00/Archon#2361, issue #2229). Both defects were live in this plugin's Boxes.

Blind staging. /explore's preserve-spike ran git add -A; /build's open-pr said "stage everything changed by the build". A Box runs in an isolated worktree that also carries the operator's copied .archon/ directory, scratch files from earlier nodes, and a build-state.json the loop rewrites on every one of up to 60 iterations — so "everything changed" is not the change, and a blind add lands scratch and PR-body artefacts in the PR.

Unpinned PR commands. No gh or az invocation passed a repository. In a fork clone the CLI resolves to the upstream parent, so a PR opened, a review posted, or a defect filed landed in the wrong repository. /pr-review is the sharpest case: it holds the only review-posting authority in the lifecycle, so an inferred repository meant a whole review — summary comment and every inline thread — on a stranger's PR.

What changed

Area Change
unic-dlc-explore.yaml preserve-spike stages named paths, then proves it with git status --porcelain; spike-ticket pins the tracker call
unic-dlc-build.yaml open-pr stages an explicit list (source/tests, PRD.md, issues.json, report.md, build-state.json, each new ADR by name) and pins the PR; both loop phases stage by name and refuse build-state.json
unic-dlc-qa.yaml verify-pr-base, merge and UAT finding-capture pin the repository
unic-dlc-pr-review.yaml prep and post pin every read and every write
commands/specs.md · commands/tickets.md both gate paths stage named paths and pin gh pr create; specs.md stops staging the whole docs/adr/ directory
lib/config-schema.mjs new optional project.repo_ref, defaulting to null
all four Box YAMLs new guard-no-repo-ref cancel node

build-state.json is now committed once, at open-pr. It is the proof of ADR-0012's anti-cheat claim — red_exit, red_unexpected_pass, the per-slice phase order — and until now that proof died with the worktree when /cleanup pruned it.

Both rules are stated inline in each prompt, not only as doctrine in AGENTS.md: a Box node is self-contained and imports nothing from the Plugin (ADR-0023 §5), so a doctrine document is invisible to a running node.

Two decisions worth reviewing

project.repo_ref stays optional. It is not in MANDATORY_PATHS and /setup does not demand it. Promoting it belongs with the 0.7.0 adoption (AC 7 puts that out of scope), and a mandatory leaf would invalidate every existing Consumer config on upgrade. Absence is handled by a guard-no-repo-ref node that cancels rather than fails — an expected precondition failure (ADR-0011). The two command Boxes have a live human, so they warn and ask rather than cancel.

The Azure DevOps flag follows the subcommand. az repos pr create and az repos pr list take --repository; az repos pr show and az repos pr update identify the PR by --id. gh api has no --repo flag at all, so the reference is written into the API path instead of left to gh's {owner}/{repo} placeholders, which resolve from the checkout. Each prompt names the form its own call needs; no host is hardcoded (ADR-0016).

Verification

Check Result
pnpm --filter unic-archon-dlc test 223 pass, 0 fail
pnpm --filter unic-archon-dlc typecheck exit 0
pnpm --filter unic-archon-dlc verify:changelog ok — version (new) → 0.15.1
pnpm ci:check exit 0 (5 pre-existing Biome infos in unic-pr-review, unrelated)

New test/box-staging-and-repo-pinning.test.mjs holds every Box to both rules. The blind-add check matches the verb as an invocation (at line start), not as a substring — every prompt has to name git add -A in order to forbid it, so a substring check would fail on exactly the rule that makes the file correct.

🤖 Generated with Claude Code

orioltf and others added 2 commits August 5, 2026 20:05
… in the Archon Boxes

Two defects Archon 0.7.0 fixed in its own bundled defaults were live here.

Blind staging: /explore's preserve-spike ran `git add -A` and /build's open-pr
said "stage everything changed by the build". A Box runs in an isolated worktree
that also carries the operator's copied `.archon/`, scratch files from earlier
nodes, and a build-state.json the loop rewrites up to 60 times — so "everything
changed" is not the change.

Unpinned PR commands: no `gh`/`az` call passed a repository, so in a fork clone
the CLI resolved to the upstream parent and a PR opened, a review posted or a
defect filed landed in the wrong repository.

Changes:
- Stage named paths in preserve-spike, open-pr, both loop phases, and both gate
  paths of commands/specs.md and commands/tickets.md; prove it with
  `git status --porcelain` before committing
- Commit build-state.json once, at open-pr — never in a loop iteration; it is
  ADR-0012's anti-cheat proof and died with the pruned worktree
- Carry the deny list inline in every committing prompt: pr-body.md, *.tmp.md,
  *.scratch.md, $ARTIFACTS_DIR, .archon/
- Add optional `project.repo_ref`; every PR-touching node reads it via bootstrap
  and passes it host-agnostically (gh --repo, az repos --repository)
- Add a guard-no-repo-ref cancel node to all four Box YAMLs (ADR-0011: expected
  precondition failures cancel, they do not fail)
- Stop staging the docs/adr/ directory in commands/specs.md — name each new ADR
- Add test/box-staging-and-repo-pinning.test.mjs and repo_ref schema tests

Fixes #289

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Promotes the #289 changelog entries — explicit staging in every committing node
and host-agnostic repo pinning via the new optional project.repo_ref key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@orioltf
orioltf changed the base branch from develop to main August 5, 2026 18:09
@orioltf
orioltf changed the base branch from main to develop August 5, 2026 18:10
@orioltf

orioltf commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

🔍 Comprehensive PR Review

PR: #307
Reviewed by: 4 specialized agents (code-review, error-handling, test-coverage, comment-quality)
Date: 2026-08-05


Summary

Well-targeted fix with a strong new test file (box-staging-and-repo-pinning.test.mjs) — line-start BLIND_ADD regex, node-scoped assertions, host-agnostic --repo/--repository checks across all four Boxes. Two HIGH findings survive review: a DAG-ordering gap in unic-dlc-pr-review.yaml where prep's prompt asserts a guarantee the graph doesn't encode, and a coverage gap on the cancel-vs-warn distinction that this PR's own scope flagged as needing verification.

Verdict: REQUEST_CHANGES

Severity Count
🔴 CRITICAL 0
🟠 HIGH 2
🟡 MEDIUM 2
🟢 LOW 2

🟠 High Issues

guard-no-repo-ref and prep are DAG siblings in unic-dlc-pr-review.yaml — the "guaranteed non-empty" claim is unbacked

📍 .archon/workflows/unic-dlc-pr-review.yaml:105-129

Both nodes declare depends_on: [bootstrap] — same tier. Elsewhere in these YAMLs, same-tier depends_on means concurrent execution. prep's prompt says "REPO_REF is guaranteed non-empty here: guard-no-repo-ref cancelled the run otherwise" — unbacked by the graph. Every other Box's first repo_ref consumer sits several nodes downstream of its guard, so this race is only real here.

View fix
  - id: prep
    depends_on: [bootstrap, guard-no-repo-ref]

Matches the existing pattern for real joins (synthesize: depends_on: [research-stack, research-features, research-architecture, research-pitfalls], unic-dlc-explore.yaml:269-270). Costs nothing at runtime.


The cancel-vs-warn distinction this PR's own scope calls out is untested

📍 commands/specs.md:735-742, commands/tickets.md:817-824

scope.md's review focus area 4 requires verifying that command docs warn-and-ask on a null repo_ref (live human) while workflow YAMLs cancel (ADR-0011). The warn-and-ask block is implemented correctly in both docs, but nothing tests for it — the existing COMMANDS loop wouldn't fail if the block were silently deleted or reworded into a no-op.

View fix
for (const command of COMMANDS) {
	test(`commands/${command}.md warns (not cancels) on a null repo_ref, before opening a PR`, () => {
		const contents = readCommand(command)
		const warnIdx = contents.indexOf('project.repo_ref is not set in .archon/unic-dlc.config.yaml')
		assert.notEqual(warnIdx, -1, `commands/${command}.md must warn when REPO_REF is null, not silently proceed`)
		const prCreateIdx = contents.indexOf('gh pr create --repo "')
		assert.ok(warnIdx < prCreateIdx, `commands/${command}.md must print the warning before gh pr create`)
	})
}

🟡 Medium Issues (Needs Decision)

Repo-pinning assertions run at whole-file granularity, not per-node

📍 test/box-staging-and-repo-pinning.test.mjs:1041-1076

A file with 6+ pinning call sites passes as long as any one still has --repo "/--repository " — a regression dropping pinning from merge in unic-dlc-qa.yaml would ship green if verify-pr-base two nodes earlier still has it.

Options: Fix now (enumerate node IDs, scope via nodeSource()) | Create follow-up issue | Skip


BLIND_ADD doc-comment names the wrong distinguishing feature of its own regex

📍 test/box-staging-and-repo-pinning.test.mjs:972-980

Comment claims the regex distinguishes invocation from prose "by the backtick" — it actually anchors on line-start position and never inspects backticks. CHANGELOG.md:677 describes the same mechanism correctly, so the two now disagree.

Options: Fix now (one-sentence reword) | Create follow-up issue | Skip


🟢 Low Issues

View 2 low-priority observations
Issue Location Suggestion
build.yaml's bootstrap fallback phrasing diverges from the other 3 Boxes (functionally equivalent) unic-dlc-build.yaml:27-29 No fix required
pr-review.yaml's post node describes GitHub review-comment endpoints loosely (pre-existing, not introduced here) unic-dlc-pr-review.yaml:481-482 No fix required for this PR

✅ What's Good

  • guard-no-repo-ref consistently uses cancel: (not a failing check) across all four Boxes, correctly following ADR-0011.
  • az repos pr show/update correctly use --id while pr create/pr list correctly use --repository — the two subcommand families are genuinely asymmetric and the PR gets this right throughout.
  • lib/config-schema.mjs's handling of repo_ref is exemplary: defaults to null, deliberately excluded from MANDATORY_PATHS with a comment explaining why, survives deepMerge, and migrateLegacy treats it like its sibling keys.
  • docs/adr/ directory-add elimination in commands/specs.md is a good catch beyond the PR's literal git add -A framing.
  • CHANGELOG cites the specific upstream Archon issues (coleam00/Archon#2361, #2229) that motivated the fix.

📋 Suggested Follow-up Issues


Next Steps

  1. Fix both HIGH issues before merge.
  2. Decide on the two MEDIUM issues (both low-effort to fix now).
  3. LOW issues need no action.

Reviewed by Archon comprehensive-pr-review workflow
Artifacts: /Users/oriol.torrent/.archon/workspaces/unic/unic-agents-plugins/artifacts/runs/b1f2bd288cb04c6a14e11d33acfa2345/review/

Fixed:
- pr-review.yaml: prep now depends_on [bootstrap, guard-no-repo-ref] — the
  DAG previously ran them as same-tier siblings, leaving prep's own
  "guaranteed non-empty" claim unbacked by an edge (HIGH, error-handling)
- box-staging-and-repo-pinning.test.mjs: nodeSource() anchored the id split
  on a bare substring, so a prefix id (merge vs merge-gate) returned the
  wrong node's body — fixed while adding the per-node pinning tests below
- box-staging-and-repo-pinning.test.mjs: BLIND_ADD doc-comment named the
  wrong distinguishing feature of its own regex (backtick vs line-start
  position) — reworded to match the regex and the CHANGELOG (MEDIUM,
  comment-quality)

Tests added:
- node-scoped repo-pinning assertions for every PR-touching node, closing
  the whole-file-granularity gap that would still pass a regression
  dropping the pin from one specific node (HIGH/MEDIUM, test-coverage)
- warn-before-gh-pr-create ordering test for commands/specs.md and
  commands/tickets.md, covering the cancel-vs-warn distinction scope.md
  flagged as needing verification (HIGH, test-coverage)

Skipped: none — the two LOW findings (build.yaml phrasing divergence,
pr-review.yaml's pre-existing loose endpoint description) were marked "no
fix required" by the reviewing agents themselves.
@orioltf

orioltf commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

⚡ Self-Fix Report (Aggressive)

Status: COMPLETE
Pushed: ✅ Changes pushed to archon/task-feature-unic-archon-dlc-289-stage-explicitly-pin-r (4f06095)
Philosophy: Fix everything unless clearly a new concern


Fixes Applied (4 total)

Severity Count
🔴 CRITICAL 0
🟠 HIGH 2
🟡 MEDIUM 2
🟢 LOW 0
View all fixes
  • guard-no-repo-ref/prep DAG siblings — unbacked "guaranteed non-empty" claim (unic-dlc-pr-review.yaml:129) — prep now depends_on: [bootstrap, guard-no-repo-ref], matching the existing synthesize join pattern in unic-dlc-explore.yaml.
  • Cancel-vs-warn distinction untested (test/box-staging-and-repo-pinning.test.mjs) — added a test per command doc asserting the null-repo_ref warning prints before gh pr create.
  • Repo-pinning assertions ran at whole-file granularity (test/box-staging-and-repo-pinning.test.mjs) — added node-scoped pinning tests for every PR-touching node. Surfaced (and fixed) a real bug in the file's own nodeSource() helper: it matched - id: merge as a substring inside - id: merge-gate's line, returning the wrong node's body.
  • BLIND_ADD doc-comment named the wrong distinguishing feature (test/box-staging-and-repo-pinning.test.mjs:59-66) — reworded to describe the actual mechanism (line-start position), matching the CHANGELOG's description.

Tests Added

  • 7 node-scoped pins its own gh/az call to REPO_REF tests (one per PR-touching node)
  • 2 warns (not cancels) on a null repo_ref, before opening a PR tests (specs.md, tickets.md)

box-staging-and-repo-pinning.test.mjs: 20/20 passing (was 12).


Skipped (2)

Finding Reason
build.yaml's bootstrap fallback phrasing diverges from the other 3 Boxes Reviewer's own call: "none required"
pr-review.yaml's post node describes GitHub endpoints loosely Reviewer's own call: pre-existing, out of scope

Suggested Follow-up Issues

(none — the one suggested follow-up was fixed directly in this pass)


Validation

✅ Type check | ✅ Lint/format (5 pre-existing infos, unrelated files) | ✅ Tests (232/232 passed)


Self-fix by Archon · aggressive mode · fixes pushed to archon/task-feature-unic-archon-dlc-289-stage-explicitly-pin-r

orioltf and others added 2 commits August 5, 2026 20:27
…ing/pinning test

Windows checkouts convert LF to CRLF, so `- id: <name>\n` in nodeSource's
split regex never matched (the character after the id was \r, not \n),
failing every downstream node lookup and cascading into 10 test failures
on windows-latest CI.
…all_done

The `depends_on: [bootstrap, guard-no-repo-ref]` edge added for the prior
review round broke the happy path. `guard-no-repo-ref` carries a `when:`, so
when `project.repo_ref` IS set the guard is skipped, and a skipped dependency
propagates its skipped state under Archon's default `all_success` join. `prep`
would have been skipped for every correctly-configured Consumer, taking
`review` → `synthesize` → `reconcile` → `post` with it and turning
`/pr-review` into a no-op.

`post`, `verify-pr-base` and `merge` already carry `trigger_rule: all_done`
over their own gate edges for exactly this reason.

A new test asserts the join for any node that depends on the guard, so a Box
cannot take the edge without the join again. Verified red before green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@orioltf

orioltf commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

🔍 Comprehensive PR Review

PR: #307fix(unic-archon-dlc): explicit staging and host-agnostic repo pinning in the Archon Boxes
Reviewed at: 6257f55 (PR head) · base develop · 9/9 CI green
Reviewed by: 5 specialised agents — code-review, error-handling, test-coverage, comment-quality, docs-impact
Date: 2026-08-05


Summary

The staging half of #289 is done and provable: zero line-anchored blind adds survive anywhere in the plugin tree, every committing node carries its deny list inline per ADR-0023 §5, and build-state.json is staged once at open-pr.

The repo-pinning half is not ready. Three defects each break a whole class of Consumer:

  1. open-pr stages and proves but never commits or pushes, so AC 3's anti-cheat proof never reaches the PR.
  2. guard-no-repo-ref cancels all four Boxes for every existing Consumer, and the remedy it names (/setup) cannot write the key.
  3. The documented ado form "PROJECT/REPO" cannot be passed to az repos --repository, which takes a repository name or ID with --project supplied separately.

The regression suite is the second concern. A mutation pass injected nine defects against the head and five survived a fully green 21/21 run — including the exact F1 DAG defect this PR just fixed, rewritten as a YAML block list. A malformed unic-dlc-qa.yaml passed 233/233.

Verdict: REQUEST_CHANGES

Severity Count
🔴 CRITICAL 0
🟠 HIGH 8
🟡 MEDIUM 12
🟢 LOW 11

31 consolidated from 44 raw findings. Three agents returned REQUEST_CHANGES; docs-impact returned UPDATES_REQUIRED. No agent approved.


🧪 Mutation evidence

Injected one at a time into a clean checkout of 6257f55. "SURVIVED" = the defect shipped past a green suite.

# Mutation Result Finding
M1 preserve-spike stages with git add --all SURVIVED (21/21) H6
M2 preserve-spike commits with git commit -am SURVIVED (21/21) H6
M3 /build bootstrap drops repo_ref_present from required SURVIVED (21/21) H8
M4 open-pr pins gh pr create --repo "<SLUG>" — right flag, wrong value SURVIVED (21/21) M5
M5 prep rewritten with block-list depends_on, trigger_rule dropped (F1 back) SURVIVED (21/21) H7
M7 build-pr-gate loses its whole inline deny list SURVIVED (21/21) M3
M8 unic-dlc-qa.yaml made unparseable YAML SURVIVED (233/233) M4
defaultConfig() drops project.repo_ref caught
guard-no-repo-ref node deleted from a workflow caught

⚖️ The one decision

H1 and H3 are the same question asked twice, and both blocking. project.repo_ref is a new key that duplicates tracker.coords, is never collected by /setup, and has an ado encoding no az flag accepts. Two coherent resolutions:

Option Approach Fixes Cost
Drop the key Delete project.repo_ref. Each node composes its pin from tracker.coords: --repo "<owner>/<repo>" for github, --repository "<repo>" --project "<project>" --organization "<org>" for ado H1, H3, M1, M8, and the Breaking — (none) claim Touches every pinned node; the "one opaque verbatim string" story goes; large diff for a p2 defect fix
Keep the key, redefine ado repo_ref for ado means the bare repository name; --project / --organization keep coming from tracker.coords, as every ado branch already does. Teach /setup and README.md. Reclassify the CHANGELOG entry as Breaking with an upgrade note H1, H3, M1 tracker.coords.project becomes load-bearing for ado (one sentence); the duplication survives as tracked debt

Recommendation: keep the key and redefine ado's form. It preserves the invariant the whole PR is built on — one opaque string, the flag is the only host-specific thing — instead of carving a per-host exception into seven prompts, and it is documentation plus /setup rather than a rewrite of every pinned node. File the tracker.coords duplication as a follow-up so the debt stays visible.

Everything else below is independent of that choice.


🟠 HIGH Issues

H1 — The guard cancels every Box for every existing Consumer, and the remedy it names cannot fix it

📍 .archon/workflows/unic-dlc-build.yaml:58-66 + 3 sibling guards · commands/setup.md:131 · README.md:161-164 · CHANGELOG.md:681
code-review F1 · docs-impact F3 + F1needs the decision above

No Consumer config on disk carries project.repo_ref — it did not exist before this PR. /setup is the config's sole writer (ADR-0019, repeated at README.md:159) and its Step 4 field list still reads project.name, project.branching, project.pr_strategy. So /setup writes repo_ref: null and never asks. Every cancel message says "Add it under project: (or re-run /unic-archon-dlc:setup)" — and the second remedy loops straight back to the same cancel.

On upgrade to 0.15.1, /build, /qa, /pr-review and /explore all stop at their first guard for every Consumer. The only working repair is a hand edit of .archon/unic-dlc.config.yaml, which nothing documents. MANDATORY_PATHS excludes the key so a config "still validates", but the guard makes it mandatory in practice — so the exclusion buys nothing and hides the breakage from validateConfig. Shipped under ### Breaking — (none).

Fix, if the key stays

Add project.repo_ref to commands/setup.md Step 4, suggesting a value parsed from GIT_REMOTE and asking the human to confirm it — in a fork clone the remote is the right answer, and inferring is what this key exists to stop. Add the README configuration row. Move the CHANGELOG entry to ### Breaking with an upgrade note.

- **project**`project.name`, `project.branching` (`gitflow | github-flow`), `project.pr_strategy`
  (`merge | squash | rebase`), `project.repo_ref` (host-agnostic repository the Boxes pin PR commands
  to: `OWNER/REPO` or `HOST/OWNER/REPO` for github, the repository name for ado). Suggest a value
  parsed from `GIT_REMOTE` and ask the human to confirm it. _(mandatory: branching, pr_strategy)_

H2 — open-pr stages, proves, then opens a PR — it never commits or pushes

📍 .archon/workflows/unic-dlc-build.yaml:471-541 (node open-pr)
error-handling F1

Step 1 builds an explicit stage list and ends with "PROVE it before committing". Step 2 opens the PR. There is no git commit and no git push anywhere in the node. AC 3's headline claim — "build-state.json is committed HERE and ONLY here" — has no instruction behind it.

The loop's RED and GREEN phases commit their own files, so source and tests do reach the branch. The five paths this node alone stages — PRD.md, issues.json, report.md, build-state.json, each new ADR — stay staged and uncommitted, then die with the worktree when /cleanup prunes it.

gh pr create exits 0 on a branch whose last commit is the loop's final GREEN commit. The PR opens, the URL prints, the node reports success. Nothing distinguishes this from a correct run. With no upstream, gh pr create cannot push non-interactively either, and the node has no git push instruction.

The reviewer therefore has no evidence the TDD order was honoured — build-state.json is ADR-0012's anti-cheat proof (red_exit, red_unexpected_pass, per-slice phase order) — while the prompt, AGENTS.md, the CHANGELOG and the test's own comment all state that it does. In AFK mode there is no human to notice.

View fix

preserve-spike (unic-dlc-explore.yaml:446-480) already has the right shape: stage by name → prove → explicit numbered commit step.

# after the PROVE step, before "2. Open a PR"
         COMMIT what you staged:
           git commit -m "build(<SLUG>): session artifacts and drafted ADRs"
         Then confirm the commit landed and carries build-state.json:
           git show --stat --name-only HEAD
         If <ARTIFACTS_DIR>/<SLUG>/build-state.json is absent from that output, STOP and report it —
         it is ADR-0012's anti-cheat proof and the PR is not reviewable without it. Push the branch
         before opening the PR: `git push -u origin HEAD`.

H3 — The ado "PROJECT/REPO" form cannot be passed to az repos --repository

📍 19 sites across all 4 YAMLs, both command docs, lib/config-schema.mjs:25-29, AGENTS.md:72, CHANGELOG.md:20
comment-quality F1 · code-review F2 · error-handling F3 · test-coverage F7 · scope F2needs the decision above

Every site states the same contract twice: the reference is "PROJECT/REPO" for ado, and it is emitted verbatim, "with no normalising, splitting or host-qualifying". The consuming instruction then passes that exact string to --repository. Verified against the installed CLI:

--project -p     : Name or ID of the project. …
--repository -r  : Name or ID of the repository to create the pull request in.

An ado Consumer following this documentation composes az repos pr create --repository "MyProject/my-repo" --project MyProject, which cannot resolve. /qa's merge and /pr-review's post fail at the CLI, and the "never split" clause stops the agent recovering by inference.

The github half is right. The claim is also test-encodedassert.match(contents, /--repository "/) and its message lock the wrong shape in, so a fix and the defect are indistinguishable to CI. github Consumers are unaffected, so the defect is invisible to this repo's own use.


H4 — No failure path for a pinned call that errors; the cheapest retry is the defect

📍 unic-dlc-build.yaml:534-541 · unic-dlc-qa.yaml:295-310, :346-360 · unic-dlc-pr-review.yaml:137-145, :446-456 · unic-dlc-explore.yaml:388-400
error-handling F3

Every PR-touching node is told to pin the repository and given the guarantee "REPO_REF is guaranteed non-empty here". No node is told what to do when the pinned call errors. Combined with H3 that matters immediately: an LLM node whose command errors retries, and the shortest retry that succeeds is to drop the flag, because splitting the value is explicitly forbidden. The run then completes, prints a PR URL, and reports success — against the inferred repository.

Every ado Consumer hits this on their first /build, /qa or /pr-review, and the most likely recovery restores #289 with the guard still green. That is the worst combination for trust in the fix, and it is what makes deferring H3 unacceptable without this clause.

View fix — mechanical, independent of the H3 format question
         If a pinned call fails, STOP and report the exact command and its error. Do NOT retry the
         call without the pin and do NOT split, reshape or host-qualify REPO_REF to make it pass: an
         unpinned retry writes to whatever repository the checkout infers, which is the defect this
         pin exists to prevent (#289). A wrong `project.repo_ref` is the operator's to fix.

H5 — tickets.md publishes every issue before the warning that governs it

📍 commands/tickets.md:277-291 (Step 9) versus :302-318 (Step 10)
error-handling F2

Step 9 publishes one tracker issue per slice. The pin rule that covers it — "The same rule covers Step 9's issue publishing" — and the null-REPO_REF warning both sit in Step 10, which runs after Step 9 has finished. Step 9's own text names gh / az / jira and never mentions REPO_REF.

On a Consumer with no project.repo_ref, every issue is filed against whatever repository the checkout infers, and only then is the human asked whether they wanted that. In a fork clone that is the upstream parent — #289's defect, in the one place this PR left it reachable.

gh issue create exits 0 and prints a URL; nothing compares it to the intended repository. The instruction's own "before doing anything else" is unreachable from where it sits. If the human then answers "set the key and start again", Step 9's issues already exist in the wrong tracker and no step says to report or clean them up. The new regression test compares the warning's index to gh pr create only, so Step 9 is invisible to it.

View fix

Move the null-REPO_REF check to the end of Step 1, where the config is read and the check is genuinely "before doing anything else", and name the flag inline in Step 9 as well (AC 8's own inline-in-each-prompt doctrine). Apply the same move to commands/specs.md so the two do not diverge.

<!-- end of Step 1 -->
If `REPO_REF` is `null` or empty, print the warning below and ask the human to set the key or confirm
the inferred repository, BEFORE Step 9 publishes anything. Every tracker write in this command is
pinned to `REPO_REF`.

<!-- Step 9, inline with the publish instruction -->
Pin every call: `gh issue create --repo "<REPO_REF>" …` for GitHub; for ado, the flag the subcommand
takes plus the `--organization` / `--project` coords from `TRACKER.coords`.

H6 — The blind-staging barrier tests three spellings out of six

📍 test/box-staging-and-repo-pinning.test.mjs:68
test-coverage F3 (M1, M2 survived) · code-review F5 · error-handling F6 · comment-quality F4

const BLIND_ADD = /^[ \t]*git add\s+(?:-A|-u|\.)(?:\s|$)/m

This single constant is the barrier behind AC 1, AC 2 and AC 5 — six of the 21 tests use it. It matches three spellings. git add --all (the documented long form of -A), git add :/, git add *, and git commit -a / -am all stage the same unwanted content and all pass. git commit -am never runs git add at all, so it defeats AC 1–4 completely.

Mutation-proven: M1 rewrote preserve-spike to git add --all, M2 to git commit -am. Both shipped past 21/21 green. --all is exactly what a model writes when told not to write -A.

The docstring compounds it: it grounds the rule on "prose always leads with something else", but the node text this same PR replaced was - \git add` the test file(s) and commit: — a list item. A blind add in a compound command (git checkout -b … && git add -A`) also passes.

View fix
const BLIND_ADD = /^[ \t]*git add\s+(?:-A|--all|-u|\.|:\/|\*)(?:\s|$)/m
/** `git commit -a` stages every tracked modification without ever calling `git add`. */
const BLIND_COMMIT = /^[ \t]*git commit\s+(?:-[A-Za-z]*a|--all)\b/m

Thread both through the same per-workflow and per-command loops. Name git commit -a in each staging rule so the prompt forbids it as explicitly as git add -A. Then narrow the docstring to the rule it enforces and name the residual gap.


H7 — The F1 regression test is vacuous in three of four Boxes, and blind to block-list depends_on

📍 test/box-staging-and-repo-pinning.test.mjs:217-238
test-coverage F2 (M5 survived)

The test guarding the fix shipped in 6257f55 has two holes:

  1. Its depends_on reader is /^ {4}depends_on: \[([^\]]*)\]/m — inline flow sequence only. An equally valid YAML block list matches nothing, so the node is silently skipped, not failed.
  2. Across the four workflows the loop finds exactly one qualifying node: prep in unic-dlc-pr-review.yaml. Measured — build 11 nodes / 0 dependants, pr-review 9 / 1, qa 10 / 0, explore 12 / 0. Three of the four assertions are zero-iteration no-ops reporting as passes.

M5 restored the exact F1 defect in block-list form with trigger_rule dropped. 21/21 green. Under the default all_success join the skipped guard propagates to prep, and /pr-review becomes a silent no-op for every correctly-configured Consumer — a run that reports success and reviews nothing.

View fix

depends_on is the one thing in these files that is data rather than prose, so the file's own justification for string checks does not cover it. The yaml package is already a dependency (test/config-schema.test.mjs:8).

import { parse as parseYaml } from 'yaml'

let checked = 0
for (const workflow of WORKFLOWS) {
	for (const node of parseYaml(readWorkflow(workflow)).nodes ?? []) {
		if (![node.depends_on ?? []].flat().includes('guard-no-repo-ref')) continue
		checked++
		assert.equal(node.trigger_rule, 'all_done', `${workflow}.yaml's ${node.id} node …`)
	}
}
assert.ok(checked >= 1, 'no guard-no-repo-ref dependant found — the depends_on reader is broken')

H8 — Nothing binds the guard's when: to the bootstrap output contract

📍 .archon/workflows/unic-dlc-build.yaml:70-75 (producer) · test/box-staging-and-repo-pinning.test.mjs:190-215 (consumer)
test-coverage F1 (M3 survived) · error-handling F4

The test asserts the guard's when: verbatim, including $bootstrap.output.repo_ref_present == 'false'. Nothing asserts the other half: that the bootstrap node still declares repo_ref_present, still lists it in required, and still types it as the STRING enum the comparison depends on.

M3 removed it from required and the suite stayed green. A bootstrap that stops emitting the field — or retypes it boolean, against which == 'false' never matches — leaves the guard permanently false. It never cancels, open-pr proceeds, and gh pr create resolves the repository from the checkout: in a fork clone, the upstream parent. #289, reintroduced with the guard sitting in the file looking correct.

Related: the guard reads a self-reported flag the same LLM node derives from a value it also emits, and nothing cross-checks them. repo_ref is { type: string } with no minLength, so repo_ref: "" with repo_ref_present: "true" validates, passes the guard, and reaches every downstream node as --repo "".

View fix

Two cheap assertions — walk every when: for $bootstrap.output.<field> and assert each field is in bootstrap's required; assert repo_ref_present is declared with the two string literals — plus one condition:

  - id: guard-no-repo-ref
    when: "$bootstrap.output.status == 'ready' && ($bootstrap.output.repo_ref_present == 'false' || $bootstrap.output.repo_ref == '')"

Preferred over adding minLength: 1 to repo_ref, which collides with the deliberate contract that bootstrap emits repo_ref: "" in every non-ready branch so the object validates before a guard can cancel.


🟡 MEDIUM Issues (needs decision)

Ten of the twelve are recommended fix now at LOW effort. Options for each: fix now · create issue · skip.

M1gh api repos/<REPO_REF>/… 404s on the documented HOST/OWNER/REPO form · unic-dlc-pr-review.yaml:460,470,487

post writes the reference into the API path because gh api has no --repo flag — correct for OWNER/REPO, but the contract also allows HOST/OWNER/REPO, which yields repos/github.example.com/acme/app/issues/comments/<id>. The host belongs in --hostname. GitHub Enterprise Consumers are exactly the ones who need the host-qualified form and exactly the ones whose summary-comment update silently 404s. Separately, build.yaml:531-532 tells the node to put --repo "on every other gh pr / gh api call", contradicting post's own correct note.

Recommendation: fix now (state the split for gh api only), and correct build.yaml:531-532 in the same pass. Resolves for free if the decision goes to "drop the key".

M2/explore tells the ado path to file a work item with az repos --repository · unic-dlc-explore.yaml:401-402

spike-ticket files a tracker issue. On ado that is az boards work-item create --org … --project …, which has no --repository flag and belongs to a different command family. /qa's finding-capture node gets it right (unic-dlc-qa.yaml:256-258). The wrong shape was likely forced by the whole-file assertion that every workflow must contain --repository ", which pushes the flag into a file whose ado path has no use for it.

Recommendation: fix now — copy /qa's wording, make the assertion node-scoped. An ado Consumer's spike ticket, the durable output the gate exists to preserve, is filed by a command that does not exist.

M3 — AC 4 says "every committing node"; the test pins two of four · test/box-staging-and-repo-pinning.test.mjs:81-103 (M7 survived)

Hardcoded to preserve-spike and open-pr. Two further nodes commit: run-build (up to 60 times) and build-pr-gate (the on_reject fix-and-push path). Both carry a correct deny list today; neither is pinned. M7 gutted build-pr-gate's list and the suite stayed green. That reject path commits under human review pressure, where scratch files are most likely present and least likely noticed.

Recommendation: fix now — derive from every node whose body contains git commit, with a floor assertion (checked >= 4). AC 4 is quantified over every committing node, so the test should be too.

M4 — No test asserts a workflow is loadable YAML · whole test files (M8 survived 233/233)

Every workflow assertion in the plugin reads the file as a string. M8 appended a malformed node to unic-dlc-qa.yaml — unclosed flow sequence, unterminated quote — and the full suite reported 233/233 pass. This PR hand-edits all four YAMLs, +323 net, including new when: expressions and trigger_rule keys. A syntax error survives CI and surfaces as an Archon load failure in a Consumer's AFK run, with no local signal.

Recommendation: fix now — one parseYaml per file plus an edge-resolution check (every depends_on entry names a declared node). Four lines once the parse gives you the nodes, and this PR touches edges.

M5 — The pin assertions check the flag, never the value · test/box-staging-and-repo-pinning.test.mjs:155-188 (M4 survived)

/--repo(?:sitory)? "/ is flag presence only. M4 rewrote open-pr's call to gh pr create --repo "<SLUG>" and all 21 tests passed. <SLUG> is the branch slug: the call either errors on an unresolvable OWNER/REPO or, worse, resolves against a same-named repository. AC 6 asks the node to resolve project.repo_ref; the test only asks it to mention a flag.

Recommendation: fix now — require the argument to be <REPO_REF> or $bootstrap.output.repo_ref, and assert the node binds the placeholder in the same prompt. One assertion each.

M6az repos pr thread is not an az subcommand · unic-dlc-pr-review.yaml:472,488reclassified LOW → MEDIUM

az repos pr offers abandon | checkout | complete | create | list | policy | reviewer | set-vote | show | update | work-item. There is no thread. ADO comment threads go through az devops invoke --area git --resource threads, which the sibling unic-pr-review plugin already proves here (agents/ado-fetcher.md:28, CHANGELOG.md:236).

Pre-existing, but this PR edits both lines (it adds --id "$PR_ID" to each), and post is the only review-writing authority in the lifecycle — on ado it cannot post at all.

Recommendation: fix now — the lines are already being edited, and the correct form exists in this monorepo.

M7/qa's guard comment claims an ordering the DAG does not give; three Boxes keep an unbacked guarantee · unic-dlc-qa.yaml:554-568, build.yaml:536, qa.yaml:308, explore.yaml:404

The /qa guard comment says it "cancels before the e2e suite runs". e2e declares depends_on: [bootstrap] — the same tier as the guard — and both when: conditions are true on status == 'ready', so Archon dispatches them together. A cancel does stop in-flight nodes, so the run ends cancelled; but the e2e suite starts and is killed mid-flight, which can leave a running dev server, a locked browser profile or a partial coverage report behind. Only /build's comment is accurate, because run-build sits a tier further down.

Separately, "REPO_REF is guaranteed non-empty here: guard-no-repo-ref cancelled the run otherwise" appears in five nodes. CHANGELOG.md:29 calls that exact wording unbacked in /pr-review's prep and fixes it there; three Boxes keep the sentence with neither an edge nor a when: on repo_ref_present. Practically true today, structurally unexpressed — and a reader cannot tell which Box backs it.

Recommendation: fix /qa's e2e structurally (edge + all_done) — it is the only tier-2 sibling that costs real time and can leave state behind — and correct the other three comments in the same pass. slopcheck and the research-* nodes are read-only. Once H7's test parses YAML, a fifth all_done edge is covered for free.

M8repo_ref is checked for presence, never for shape · lib/config-schema.mjs:24-31,:82-86

validateConfig checks MANDATORY_PATHS only, which deliberately excludes the key; the guard checks presence only. A pasted browser URL (https://github.com/unic/x), a bare owner (unic), a trailing slash, or the four-segment ADO web path (org/project/_git/repo) all pass /setup, pass validateConfig, satisfy repo_ref_present == 'true', and fail for the first time at open-prafter up to 60 loop iterations. The guard's own comment gives that cost as its reason to exist, so the expensive-discovery hole stays open for the likelier operator error: a typo, not an omission.

Recommendation: fix now — validate loosely in validateConfig when the key is present (reject whitespace, a URL scheme, fewer than two segments) and restate the rule inline in each bootstrap prompt, since a Box node imports nothing from the plugin (ADR-0023 §5). Keep it loose. Optional is not the same as unvalidated.

M9 — The CHANGELOG cites synthesize as the join-pattern precedent; it is the opposite pattern · CHANGELOG.md:29

The entry justifies prep's new edge as matching "the join pattern the codebase already uses (synthesize in /explore)". synthesize depends on four research nodes, none carrying a when:, and declares no trigger_rule — the default all_success is correct there precisely because nothing upstream can be skipped. Copying that shape onto a when:-gated guard is what produced the happy-path skip that CHANGELOG.md:31, the very next line, documents and fixes. The real precedent is post, verify-pr-base and merge.

Recommendation: fix now — fold both entries into one describing the final state, citing the correct precedent. 0.15.1 has not shipped. /build, /qa and /explore all still lack a guard edge; the next person to add one follows this citation.

M10/build's guard comment names one affected node; the PR's own test names two · unic-dlc-build.yaml:90-93

The comment says "open-pr is the only node that would notice". This PR pins two /build nodes and registers both: REPO_PINNED_NODES['unic-dlc-build'] = ['implement-review-precheck', 'open-pr'], and CHANGELOG.md:26 lists both. An unpinned implement-review-precheck reads a stranger's slice issues before open-pr ever runs.

Recommendation: fix now — name both nodes and keep the cost argument. Both sit after the loop, so "up to 60 iterations of wasted work" survives unchanged. This is the file's own explanation of why the guard exists, and understating the affected set is how a guard gets argued away later.

M11 — The README node pipelines and the four per-Box command references describe a DAG that no longer exists · README.md:62-65, .archon/commands/unic-dlc-*.mdreclassified HIGH → MEDIUM

The § Archon workflow pipelines table renders the full node sequence per Box and already names the sibling guard-not-ready; after this PR all four rows are wrong. Each .archon/commands/unic-dlc-*.md carries a config table and a "What this workflow does" list whose step 1 enumerates the keys bootstrap reads — both now incomplete in all four files, and these ship inside the plugin's .archon/ tree, so they are what a Consumer reads after installation. /pr-review's doc is the worst case: line 68 asserts "there is no fail-closed merge guard here", and a reader now meets a guard that cancels the whole Box.

Reclassified because it is doc drift with no operator dead-end — the dead-end (the missing README config row) is folded into H1, where it blocks.

Recommendation: fix now — add the guard to the four README rows plus one legend line for the guard-join rule, and add the key row and the guard to all four per-Box docs.

M12 — The warn-versus-cancel split is recorded only in the CHANGELOG · README.md, commands/specs.md, commands/tickets.md

One config key now produces two behaviours by container type: a command warns and continues, a workflow cancels. The PR states the divergence in the CHANGELOG and pins it with a test, but no operator-facing doc says it. A Consumer who sets no repo_ref finds /specs working and /build refusing, with nothing explaining why — and the next person editing either surface will "fix the inconsistency" in the wrong direction. A command has a live human to ask; a node does not (ADR-0017).

Recommendation: fix now — one clause in the README project.repo_ref row and one sentence in the AGENTS.md invariant this PR added.


🟢 LOW Issues

View 11 low-priority findings
# Issue Location Suggestion
L1 The command docs check null only; repo_ref: "" or " " passes the warning and fails at the CLI commands/specs.md:266, commands/tickets.md:310 Reword to "null or empty"; normalise in the loader: (g(…) ?? '').toString().trim() || null
L2 "Confirm the inferred repository" has no command to run; both improvisations are wrong commands/specs.md:266-296, commands/tickets.md:310-339 Print gh repo view --json nameWithOwner first, then run with no --repo; never --repo "null"
L3 open-pr's stage-list source command has no failure path, and re-derives already-committed files unic-dlc-build.yaml:490-503 Name the unstaged git status --porcelain entries as the source; use origin/<expected_base>; never stage an empty list silently
L4 gh --repo "<ref>" is not a runnable shape — --repo is a subcommand flag unic-dlc-build.yaml:364 gh issue view --repo "<ref>"
L5 <ARTIFACTS_DIR> and $ARTIFACTS_DIR differ by one character and mean opposite things unic-dlc-build.yaml:494-507 Rename the config-derived constant to SESSION_DIR (already introduced at :481, never used)
L6 "Anchors on the id as a whole line" describes an anchor the regex does not have …test.mjs:45-56, CHANGELOG.md:30 Say "terminated by a newline"
L7 AGENTS.md's "every PR-touching node pins" overstates; commands/cleanup.md:193 is unpinned and untested AGENTS.md:72, commands/cleanup.md:193 Scope to the six covered Boxes, name /cleanup as the open one, file a follow-up
L8 The trigger_rule rationale sits outside the node's comment box and cites two nodes from another file unic-dlc-pr-review.yaml:128-132 Move inside the box; qualify verify-pr-base / merge as unic-dlc-qa.yaml
L9 "Both gate paths" is asserted file-globally, so the stage-only path can lose its staging rule …test.mjs:241-272 Slice at the stage-only heading and assert per section, as the sibling test already does
L10 The fixed nodeSource was not backported; two divergent copies now exist …test.mjs:54-57 vs archon-box-methods.test.mjs:70-72 Extract into test/helpers/workflows.mjs — three call sites, and M4's parse helper wants the same home
L11 CONTEXT.md gains no vocabulary entry for repository pinning CONTEXT.md One entry following the file's existing shape, with an _Avoid_: line

✅ What's good

  • The staging fix is complete and provable. Zero line-anchored blind adds remain anywhere in the plugin tree; every remaining mention is prose that forbids the verb, and BLIND_ADD is deliberately position-based so the forbidding prose does not trip it.
  • build-state.json is handled exactly as ADR-0012 needs — and the loop test asserts both phases say they do not commit it, rather than asserting an absence a blind add would satisfy. The comment says why. That instinct is exactly what H6 asks for on the verb set.
  • F1 was found by review, fixed, and shipped with a regression test verified red before green. The all_done fix is right and it generalised. H7 narrows its reach; it does not dispute the practice.
  • cancel: over a failing bash: check in all four guards, so a missing key reads as cancelled and does not trigger auto-resume. The test asserts the node key itself, so a rewrite to a failing check breaks the suite. The right reading of ADR-0011.
  • Every cancel message is actionable: the missing key, both legal shapes, the concrete consequence ("in a fork clone is the upstream parent"), the remedy, how to restart, and each Box's own stake ("/qa is the box that MERGES"). The test enforces the key name and the remedy command — a much better bar than "a message exists".
  • The bootstrap fallback contract is explicit and complete in all four Boxes, so a clean cancel cannot degrade into a schema error.
  • warns (not cancels) on a null repo_ref checks ordering, not presence. It would catch a reordering a presence check could not.
  • Node-scoped pinning assertions replace the old whole-file check, closing the exact hole where one node loses its flag while its file still passes.
  • The config loader's named throw, with a comment explaining that the default join(undefined, …) message "says nothing about what to do next". Error handling written for the person reading the output in six months.
  • Every new rule states its why, at the site that needs it. The deny lists say $ARTIFACTS_DIR "resolves OUTSIDE the repo tree and is NOT the config's artifacts_dir" — the exact confusion the rule prevents. Deliberate duplication with a written reason, justified by ADR-0023 §5.
  • gh api's missing --repo flag is caught and explained rather than papered over with a symmetric-looking instruction.
  • MANDATORY_PATHS' JSDoc explains an absence — why the key is deliberately not in the list, and what would have to change. The comments that stop a well-meaning "fix" later.
  • nodeSource's prefix-collision fix is a real bug found while writing the tests, with the merge / merge-gate case recorded at the helper.
  • CRLF normalisation on read keeps the string assertions honest on Windows, where CI runs.
  • The change correctly avoids a new ADR — it implements ADR-0011, 0012, 0016 and 0023 rather than deciding anything new.
  • Housekeeping is clean: 0.15.1 in all three manifests, ## [0.15.1] — 2026-08-05, conventional commits with package scope, base develop, MERGEABLE / CLEAN, 9/9 CI green, no new runtime deps, no LICENSE touched.

📋 Suggested follow-up issues

Title Priority Related
project.repo_ref duplicates tracker.coords — collapse to one primitive P2 H3
Pin /cleanup and /triage PR commands to project.repo_ref, with test coverage P2 L7
Extract the shared workflow test helpers into test/helpers/workflows.mjs P3 L10, M4
Record build-state.json's per-phase committed file list, and stage open-pr from it P3 L3
Add a repo pinning vocabulary entry to the plugin CONTEXT.md P3 L11

Next Steps

  1. Decide the project.repo_ref question. H1 and H3 both unblock from it; M1 and M8 shift with it.
  2. Auto-fix the six mechanical HIGH findings: H2, H4, H5, H6, H7, H8.
  3. Re-run the mutation set. M1, M2, M3, M4, M5, M7 and M8 must all go red. That is the acceptance test for H6, H7, H8, M3, M4 and M5 — nothing else proves those fixes landed.
  4. Review the twelve MEDIUM findings and decide: fix now, create an issue, or skip. Ten are recommended fix-now at LOW effort.
  5. Re-run pnpm --filter unic-archon-dlc test, pnpm typecheck, pnpm ci:check, and confirm CI stays 9/9 before merge.

Reviewed by Archon comprehensive-pr-review workflow
Artifacts: artifacts/runs/beb9b30606f43d287c6af1020ef87086/review/

…review

All eight HIGH findings from the five-agent review. H1 and H3 were one design
question — whether `project.repo_ref` survives — resolved under the review's
stated recommendation: keep the key, define ado's form as the bare repository
name. Reversible; the alternative (derive from `tracker.coords`) is filed as a
follow-up.

- H1 `/setup` now asks for `project.repo_ref`, suggesting a value parsed from
  GIT_REMOTE. README documents the key. The guard cancelled all four Boxes for
  every pre-0.15.1 Consumer while the remedy it named could not write the key,
  so the CHANGELOG entry moves to `### Breaking` with an upgrade step.
- H2 `open-pr` commits and pushes what it stages, and proves build-state.json
  reached the commit. It staged, proved, then opened a PR — `gh pr create`
  exits 0 on the loop's last commit, so ADR-0012's proof died with the worktree
  while the node reported success.
- H3 ado's `repo_ref` is the bare repository NAME, not "PROJECT/REPO":
  `az repos --repository` takes a name or ID with `--project` separate, and
  every prompt forbids splitting the value. Corrected at all 11 sites.
- H4 A pinned call that errors STOPS and reports. The shortest retry an
  autonomous node finds is to drop the flag, which restores #289 with the guard
  still green.
- H5 The null-REPO_REF check moves to Step 1 in both command docs, where the
  config is read. It sat in Step 10, after Step 9 had already filed one issue
  per slice against the inferred repository. Step 9 names the pin inline, and
  "confirm the inferred repository" now has a command to run.
- H6 Staging rules and the BLIND_ADD barrier cover all six spellings.
  `git commit -am` stages every tracked modification without calling `git add`.
- H7 The all_done regression test parses `depends_on` instead of string-matching
  the inline form only, and asserts non-vacuity — it found one node across four
  Boxes, so three of its four assertions were silent no-ops.
- H8 A new test binds each bootstrap's output contract to the guard that reads
  it, and the guard also fires on a present-but-empty value.

Incidental, in blocks rewritten for the above: the `gh api` host-segment split
(HOST/OWNER/REPO 404s in the path), the `--repo`-on-`gh api` contradiction, the
`gh --repo` top-level-flag shape, the `synthesize` join precedent, the
nodeSource anchor claim, and AGENTS.md's overstated invariant.

Mutation-verified: M1, M2, M3, M5 and M8 all previously survived a green 21/21
run and now fail. M4 and M7 still survive — they map to MEDIUM findings left for
the maintainer.

1817 tests pass; typecheck, ci:check and verify:changelog clean.

Review artifacts: artifacts/runs/beb9b30606f43d287c6af1020ef87086/review/
@orioltf

orioltf commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

⚡ Auto-Fix Report

Status: COMPLETE — all 8 HIGH fixed (0 CRITICAL existed)
Pushed: ✅ 6257f55..c0fddbc on archon/task-feature-unic-archon-dlc-289-stage-explicitly-pin-r
Scope: 13 files, +279 / −107


Fixes Applied

Severity Fixed Skipped
🔴 CRITICAL
🟠 HIGH 8 0

What was fixed

  • H1 — the guard cancelled every existing Consumer (commands/setup.md, README.md, CHANGELOG.md) — /setup now asks for project.repo_ref, suggesting a value parsed from GIT_REMOTE for the human to confirm. README documents the key. The entry moved from ### Breaking — (none) to a real ### Breaking entry with the upgrade step.
  • H2 — open-pr now commits and pushes what it stages (unic-dlc-build.yaml) — new step 2 commits, runs git show --stat --name-only HEAD, and stops if build-state.json is missing from the commit, then pushes before opening the PR.
  • H3 — ado's repo_ref is the bare repository NAME, not "PROJECT/REPO" — corrected at all 11 sites. --project / --organization keep coming from tracker.coords, as every ado branch already did.
  • H4 — a pinned call that errors STOPS (7 blocks, all 4 YAMLs) — never retried unpinned, never reshaped to make it pass.
  • H5 — the null-REPO_REF check moved to Step 1 (commands/tickets.md, commands/specs.md) — before Step 9 files anything. Step 9 names the pin inline; "confirm the inferred repository" now has a command to run.
  • H6 — the blind-staging barrier covers all six spellingsBLIND_ADD widened, new BLIND_COMMIT for commit -a/-am, and all six staging rules name them.
  • H7 — the all_done regression test parses depends_on instead of matching the inline form only, with a non-vacuity floor. It was finding one node across four Boxes; three assertions were silent no-ops.
  • H8 — a new test binds each bootstrap's output contract to the guard that reads it, and all four guards now also fire on a present-but-empty value.

Tests

test/box-staging-and-repo-pinning.test.mjs: 1 new test, 3 rewritten, BLIND_COMMIT added to 4.
test/config-schema.test.mjs: blessed ado form corrected.
Plugin suite 234/234 (was 233).


🧪 Mutation verification — the real acceptance test

A green suite proves nothing here; these are the mutations that previously shipped past 21/21.

# Mutation Before After
M1 preserve-spike stages with git add --all SURVIVED FAILS
M2 preserve-spike commits with git commit -am SURVIVED FAILS
M3 /build bootstrap drops repo_ref_present from required SURVIVED FAILS
M3b repo_ref_present retyped boolean (found while verifying) would have survived FAILS
M5 prep block-list depends_on, trigger_rule dropped — the F1 defect SURVIVED FAILS
M8 unic-dlc-qa.yaml made unparseable SURVIVED (233/233) FAILS
M4 open-pr pins --repo "<SLUG>" SURVIVED ⚠️ still survives — MEDIUM, out of scope
M7 build-pr-gate loses its deny list SURVIVED ⚠️ still survives — MEDIUM, out of scope

M3's first fix did not work — my field-extraction regex stopped at the first $bootstrap.output.<field> per when:, so the compound guard condition's second read went unchecked. Caught by running the mutation rather than trusting the green suite.


⚠️ One assumption you should confirm

H1 and H3 were one design question, not two: does project.repo_ref survive? The review offered two coherent resolutions. Rather than block with nothing delivered, I took the consolidated review's recommendation — keep the key, redefine ado's form as the bare repository name. It preserves the "one opaque string, the flag is the only host-specific thing" invariant and is documentation plus /setup, not a rewrite of every pinned node.

What it leaves open: code-review's primitive-duplication finding stands — repo_ref still duplicates tracker.coords, and every ado node reads both in the same sentence. Filed as a P2 follow-up.

To take the other option instead (delete the key; compose the pin from tracker.coords): revert only the H1/H3 parts of c0fddbccommands/setup.md, the README row, the four bootstrap contract paragraphs, lib/config-schema.mjs, test/config-schema.test.mjs. H2, H4, H5, H6, H7 and H8 are independent and stay.


Fixed incidentally

Each sat inside a block rewritten for a HIGH fix; leaving a known-wrong line there would have been worse. Not claimed as scope:

gh api's HOST-segment split (M1) · the --repo-on-gh api contradiction · gh --repo top-level flag shape (L4) · the synthesize join precedent in the CHANGELOG (M9) · the nodeSource anchor claim (L6) · AGENTS.md's overstated invariant and the /cleanup gap (L7) · implement-review-precheck's ado work-item read.


🟡 MEDIUM Issues — still your decision

# Issue Options
M2 /explore files an ado work item with az repos --repository Fix now / Create issue / Skip
M3 Deny-list test pins 2 of 4 committing nodes — M7 still survives Fix now / Create issue / Skip
M5 Pin assertions check the flag, not the value — M4 still survives Fix now / Create issue / Skip
M6 az repos pr thread is not an az subcommand Fix now / Create issue / Skip
M7 /qa's guard comment claims an ordering the DAG does not give Fix now / Create issue / Skip
M8 repo_ref checked for presence, never shape — a typo costs a full run Fix now / Create issue / Skip
M10 /build's guard comment names one node; the test names two Fix now / Create issue / Skip
M11 README pipelines + 4 per-Box docs describe a DAG that no longer exists Fix now / Create issue / Skip

M6 is the one I would take next — two lines of prose, the correct form is already proven in unic-pr-review, and until it lands /pr-review's post cannot post on ado at all.

M4 (gh api path) and M9 (synthesize citation) are done. M12 is partly covered by the new README row. Of the 11 LOW findings, L1, L2, L4, L6 and L7 are done; six remain open.


📋 Suggested Follow-up Issues

  1. project.repo_ref duplicates tracker.coords — collapse to one primitive (P2) — the open half of the H3 assumption
  2. az repos pr thread is not a real subcommand (P2) — ADO threads need az devops invoke
  3. Pin /cleanup and /triage PR commands, with test coverage (P2) — the gap AGENTS.md now names
  4. Derive the deny-list and pin-value test node sets instead of hardcoding them (P2) — kills M4 + M7
  5. Validate project.repo_ref's shape in validateConfig and each bootstrap prompt (P3)
  6. Extract the shared workflow test helpers into test/helpers/workflows.mjs (P3)
  7. Bring README node pipelines and the four per-Box docs back in step with the YAMLs (P3)

Validation

Check Command Result
Plugin tests pnpm --filter unic-archon-dlc test ✅ 234/234, exit 0
Monorepo tests pnpm test ✅ 1817/1817, 0 fail, exit 0
Type check pnpm typecheck ✅ exit 0
Lint + format pnpm ci:check ✅ exit 0
Changelog verify:changelog ✅ exit 0

The 5 Biome infos in ci:check are pre-existing in pr-review / unic-pr-review and none are in changed files. This repo has no build step — tsc --checkJs --noEmit via pnpm typecheck is the whole compilation story.


Auto-fixed by Archon comprehensive-pr-review workflow
Fixes pushed to archon/task-feature-unic-archon-dlc-289-stage-explicitly-pin-r as c0fddbc
Full report: artifacts/runs/beb9b30606f43d287c6af1020ef87086/review/fix-report.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(unic-archon-dlc): explicit staging and host-agnostic repo pinning in the Archon Boxes

1 participant