Skip to content

chore(harness): adopt copier template v0.7.0 - #13

Open
egparedes wants to merge 3 commits into
ao/ulv-3/template-v0.6.0from
ao/ulv-3/template-v0.7.0
Open

egparedes wants to merge 3 commits into
ao/ulv-3/template-v0.6.0from
ao/ulv-3/template-v0.7.0

Conversation

@egparedes

Copy link
Copy Markdown
Collaborator

Stacked on #11 — targets ao/ulv-3/template-v0.6.0, so this diff is the
v0.6.0 → v0.7.0 delta only. Merge #11 first.

Why this stacks on #11 rather than restarting from main

v0.7.0 requires the docs/ → development/ relocation #11 performed: it is
an upstream breaking change, and the new _skip_if_exists list protects
development/*.md paths only. Starting from main would mean redoing 77 files
of relocation to reach the same place. Exactly one commit of #11 is superseded
(ea24433, the hook repair), and that is a reconcile, not a reason to discard
the rest — see "Rejected hunks" below.

What v0.7.0 is

Two upstream ADRs:

  • ADR 0013 — hook payload parsing. The Claude Code hooks had an
    undocumented hard jq dependency that degraded differently and silently at
    each of three call sites. New .agents/hooks/hook-input.sh parses with jq,
    falls back to python3 (probed by running it — stock macOS ships a stub
    that passes command -v), and returns distinct exit codes (3 = no parser,
    4 = bad payload) so each hook picks a posture: PreToolUse denies with the
    install remedy, Stop and PostToolUse skip with a visible non-zero exit.
  • ADR 0012 — simplification wave. 24 questions → 13; the four role playbook
    skills merge into their subagent files; the SPIKE-/EXPLORER-/
    PLAN-REVISION markers collapse into one HANDBACK(kind)/RESULT(kind)
    convention; the example work unit, the verify skill, the three .agents/
    sub-READMEs, and the cursor/mcp modules stop being generated.

Eleven answers drop out of .copier-answers.yml; nothing re-prompts.

What survived untouched

.mcp.json, the Playwright block in .opencode/opencode.jsonc, all five real
work units, and scripts/fmt-file.sh — the last because v0.7.0 added
scripts/verify.sh and scripts/fmt-file.sh to _skip_if_exists, so the
working formatter from #11 is now protected rather than at risk.

Rejected hunks and how each was resolved

copier update --conflict rej produced five. Two were resolved in favour of
upstream outright:

Reject Resolution
.claude/settings.json Upstream wins. #11's own jq handling exited 0 on a jq-less host, letting every Bash command through unchecked; upstream denies with a remedy. Strictly better.
architect.md, product-owner.md Dropped. The round caps #11 added by hand are now upstream text.

Three local hunks are re-applied on top:

  • block-destructive.sh keeps this repo's matcher. Upstream's single
    substring grep denies any command that merely quotes a pattern — enough
    to block an rg search of the file itself, and it blocked two legitimate
    tool calls during the v0.6.0 adoption. The header now records the divergence
    and says to keep rejecting that hunk; behaviour stays pinned by 23 tests.
    This is the one deliberate, recurring cost in this PR: one .rej per
    template release on a 60-line file.
  • PreToolUse stays anchored to ^Bash$. Unanchored it also matches
    BashOutput/KillShell, whose payloads carry no .tool_input.command — and
    upstream's new fail-closed empty-command branch would deny them. Verified:
    a BashOutput-shaped payload returns rc=2 through the unanchored wiring.
  • The OpenCode formatter keeps bash, not sh. fmt-file.sh uses
    set -o pipefail and [[ ]]; under dash it aborts on line 1 and formats
    nothing, silently, because a formatter's exit code is not surfaced.

AGENTS.md also keeps its concrete license and squash-merge lines, which
v0.7.0 replaces with a _Fill in: marker and strategy-generic prose.

Preserved-doc drift

_skip_if_exists is matched at any depth, so README.md covers every README
in the tree. That left .agents/README.md pointing at three sub-READMEs and
four playbook skills the update had just deleted, and harness-usage.md still
teaching SPIKE-REQUEST: and the verify skill. All four affected files were
unmodified renders of the previous version, so they are replaced with the
v0.7.0 renders and the local content re-applied — the Browser automation (MCP)
section returns to harness-usage.md, and tool-bootstrap.md's two
_Fill in: markers are answered from pyproject.toml and the CI matrix.

development/style.md is deliberately not synced (see register row
2026-07-template-v0.7.0.3).

Verification

  • make verify green: 376 tests, lint, format-check, docs build.
  • tests/test_harness_config.py: 33 → 45 tests. The hook-wiring guards moved
    from asserting on inline jq to asserting the hook-input.sh contract and
    each hook's exit-3 posture; new TestHookInput covers the reader, including
    that the python3 fallback is byte-identical to jq — a backend that
    disagrees makes the guard machine-dependent.
  • End-to-end through the real PreToolUse command string, with and without
    jq on PATH: benign allowed, destructive denied, quoted mention allowed.

Follow-up worth considering (not in this PR)

Three deny mechanisms still overlap and only one is the improved matcher:
.claude/settings.json's permissions.deny prefix rules and
.opencode/opencode.jsonc's substring globs remain the old style, so they
over-block relative to the hook. Either loosen them to match or upstream the
matcher to the template — the latter would also end the recurring reject.

🤖 Generated with Claude Code

egparedes and others added 3 commits July 28, 2026 12:47
Template v0.7.0 lands two upstream decisions. ADR 0013 replaces the hooks'
undocumented hard `jq` dependency with `.agents/hooks/hook-input.sh`, a
payload reader that falls back to `python3` and reports distinct exit codes
(3 no parser, 4 bad payload) so each hook picks its own failure posture:
PreToolUse denies with the install remedy, Stop and PostToolUse skip with a
visible non-zero exit. ADR 0012 cuts the question set from 24 to 13, merges
the four role playbook skills into their subagent files, collapses the
SPIKE/EXPLORER/PLAN-REVISION markers into one HANDBACK(kind)/RESULT(kind)
convention, and stops generating the example work unit and the verify skill.

Eleven answers drop out of `.copier-answers.yml`; nothing re-prompts.
`.mcp.json`, the Playwright block in `.opencode/opencode.jsonc`, and all five
real work units are untouched — `scripts/verify.sh` and `scripts/fmt-file.sh`
joined `_skip_if_exists`, so the working formatter survives the update.

Four of the five rejected hunks are resolved in favour of upstream. The
repo's own jq handling goes: on a jq-less host its PreToolUse path exited 0,
letting every command through unchecked, where upstream denies. The round
caps added by hand to `architect.md` and `product-owner.md` are now upstream
text. Three local hunks are re-applied:

- `block-destructive.sh` keeps this repo's matcher. Upstream's single
  substring grep denies any command that merely quotes a pattern, which
  blocked two legitimate tool calls during the v0.6.0 adoption. The header
  now says so, and `copier update` should keep rejecting that hunk.
- `PreToolUse` stays anchored to `^Bash$`. Unanchored, it also matches
  BashOutput and KillShell, whose payloads carry no `.tool_input.command` —
  and upstream's fail-closed empty-command branch would deny them.
- The OpenCode formatter keeps `bash` (not `sh`): `fmt-file.sh` uses
  `set -o pipefail` and `[[ ]]`, so dash aborts on line 1 and silently
  formats nothing.

`AGENTS.md` keeps the concrete license and squash-merge lines that v0.7.0
replaces with a `_Fill in:` marker and strategy-generic prose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`_skip_if_exists` protects these files by design, so `copier update` left
them describing a harness that no longer exists: `.agents/README.md` pointed
at three sub-READMEs and four playbook skills the update deleted, and
`harness-usage.md` still taught the SPIKE-REQUEST marker and the verify
skill. Copier's pattern list is matched at any depth, so `README.md` covers
every README in the tree, not just the root one.

All four were unmodified renders of the previous template version, so they
are replaced with the v0.7.0 renders and the local content re-applied: the
Browser automation (MCP) section returns to `harness-usage.md`, and
`tool-bootstrap.md`'s two `_Fill in:` markers are answered from
`pyproject.toml` and the CI matrix rather than left as scaffold.

`development/style.md` is deliberately not synced: v0.7.0 replaces its
concrete squash-merge rule with strategy-generic prose, and this repo
squash-merges. Three register rows record that call and the two other
judgements this update needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The hook-wiring guards asserted on inline `jq` calls that v0.7.0 removed, so
they described plumbing the repo no longer has. They now assert what the new
wiring depends on: that all three input-consuming hooks read through
`hook-input.sh`, and that each branches on its exit 3 with the posture its
role requires — PreToolUse denies, PostToolUse and Stop skip visibly. A hook
that ignores exit 3 inherits whatever the surrounding shell does with an
empty read, which is exactly how the guard once allowed everything.

`TestHookInput` covers the reader itself, including the case no unit test
would otherwise reach: that the `python3` fallback returns byte-identical
output to `jq`. A backend that disagrees makes the guard's behaviour depend
on which machine it runs on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
egparedes added a commit that referenced this pull request Aug 3, 2026
…-3 stack

Evaluated the unowned competing stack (PRs #11/#13, branches
ao/ulv-3/template-v0.6.0 and ao/ulv-3/template-v0.7.0) and ported its
unique work:

- tests/test_harness_config.py: hook-wiring pins, the hook-input.sh
  backend contract, block-destructive coverage, skill guard (all pass
  against this branch's implementations unchanged)
- .agents/hooks/block-destructive.sh: quote-aware, command-position
  matcher with a divergence header for future copier updates
- scripts/fmt-file.sh: real ruff --force-exclude implementation
  replacing the template no-op; opencode.jsonc runs it under bash
- .claude/settings.json: PreToolUse matcher anchored to ^Bash$
- development/glossary.md: seeded domain vocabulary
- development/adr/0010 + decision-register rows: relocation decision
  recorded (adapted to this PR's actual migration mechanics and to the
  retained dependency-needs-ADR rule); register marker scoped to
  report.md lines
- src/ulv/model.py docstring no longer points into the unpublished
  development/ tree; README, tool-bootstrap, PR template refreshed;
  initial-project-description.md to development/

Rejected: their reverts of this branch's review fixes (hook-input.sh
hardening, Stop-hook doc honesty, role-file contradiction fixes,
verify skill) and their adoption of the softer dependency-ADR bar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant