Round-2 sweep after re-auditing all 15 reported issues against the merged dev:
- #885 generalized: the original fix only renamed self-improving-agent's
status/review, but three more plugins shipped skills whose bare names
shadow Claude Code built-ins. Renamed with the same convention:
playwright-pro init/review -> pw-init/pw-review, agenthub init/status ->
hub-init/hub-status, autoresearch-agent status/resume -> ar-status/
ar-resume. All command references (/pw: /hub: /ar:), docs, audit records,
harness manifests, and mirror trees/indexes updated; the flat mirror
namespace no longer collides on 'status'. New scripts/check_skill_names.py
gate (wired into ci-quality-gate.yml as blocking) fails CI on any future
bare reserved name; rule added to SKILL-AUTHORING-STANDARD.md.
- #969 follow-through: five more scripts print box-drawing characters that
cannot exist in cp1252 (api_scorecard, api_linter,
breaking_change_detector, humanizer_scorer, content_scorer) — same
guarded UTF-8 reconfigure applied; all smoke-tested under a forced
legacy encoding.
Verified: check_skill_names (incl. negative test), check_plugin_json,
check_paths, derive_counters, check_dual_publish, smoke_scripts (634/634),
0 broken mirror symlinks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qgc6RYXWJPr5oW9DHU7zR4
Review follow-up on PR #972: the Windows Notes section now sits between
Verification & Testing and Troubleshooting, matching the Table of Contents
order, and the four scripts patched for #969 are back to exactly two blank
lines after the reconfigure block.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qgc6RYXWJPr5oW9DHU7zR4
Review follow-up on PR #972: docs/plugins/index.md still described the old
"two approved extension fields in plugin.json" policy reversed by #954 —
rewritten to point at .claude-plugin/authoring-notes.json and the CI
hard-fail. 32 generated docs pages still linked the gitignored megaprompts/
tree via absolute GitHub URLs (404s); converted to the same annotated
plain-text form used in the source files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qgc6RYXWJPr5oW9DHU7zR4
Public audit record audit/pr-stream-2026-08/: every open PR against dev
(#788-#967) deep-read and re-executed against the repo's own gates.
Verdicts: 8 MERGE, 8 MERGE-WITH-CHANGES, 6 CLOSE, 3 maintainer-draft
finish plans. Names the four blocking maintainer decisions (extension-key
policy #966-vs-#940, agent skills: preloading, DESIGN-only folders,
release framing), a six-phase global merge order, and per-PR executable
verification plans.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Bzm6Pafyxja6g4jUDPcei
- #954: strip non-spec source/attribution keys from all 39 plugin.json
manifests so Claude Code's validator accepts them; metadata preserved in
new .claude-plugin/authoring-notes.json sidecars; check_plugin_json.py now
hard-fails manifests carrying those keys and sanity-checks the sidecar;
CLAUDE.md ClawHub schema section updated to the new rule.
- #949: move the c-level-agents plugin out of c-level-advisor/ to a
top-level directory so the two marketplace sources no longer overlap;
updated marketplace.json source, homepage, descriptions, all
cross-references, docs, harness manifest, mirror-tree symlinks/indexes,
and rebased the moved files' relative links; domain counters trued up
(18 -> 19 domains).
- #933: replace dead links to the gitignored maintainer-local megaprompts/
tree with annotated plain-text references (44 files: SKILL.md, READMEs,
agents, commands).
- #931: DynamoDB on-demand pricing updated to post-Nov-2024 rates
($0.625/M writes, $0.125/M strongly consistent reads).
- #969: skill_security_auditor.py and the three dossier scripts reconfigure
stdout/stderr to UTF-8 (errors=replace) so legacy Windows codepages no
longer crash at print time; PYTHONUTF8=1 documented.
- #968: Windows Notes section in INSTALLATION.md + README pointer for the
core.symlinks mirror-tree checkout caveat.
- #924/#885 residuals: hook commands quote "${CLAUDE_PLUGIN_ROOT}" paths in
all plugin hooks.json/settings.json (space-safe roots); removed the stale
pre-rename status/review mirror symlinks and index entries left over from
the memory-status/memory-review rename.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qgc6RYXWJPr5oW9DHU7zR4
GitHub Copilot CLI reads .claude-plugin/marketplace.json but enforces a
1024-character cap on each plugin's description. Four entries exceed it, so
Copilot rejects the entire catalog with:
Invalid marketplace.json: plugins.N.description: String must contain at most
1024 character(s)
Shortens engineering-advanced-skills (1132 -> 986), research-ops-skills
(1593 -> 957), markdown-html-skills (1240 -> 914) and memory-engineering
(1044 -> 964), trimming only redundant parenthetical detail. Skill inventories,
version history, hard rules, tool/reference counts and attributions are kept.
No other field is touched.
Verified with Copilot CLI: marketplace adds successfully and all 90 plugins are
listed.
Independent 10-agent verification workflow: 9/9 spec parts PASS, overall PASS,
zero differences from spec. Records the verdict table, design decisions, and
reproducible verification steps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FwXG6TqCXKZQvF4iD69cv
Adds the agent-launcher/ top-level domain — a plugin re-implementation of
Anthropic's launch-your-agent reference skill (Apache-2.0; independent, not a
fork) for building Claude Managed Agents (CMA) in the user's own account.
Every session starts with a goal (./my-agent/goal.json, surfaced by an opt-in
AGENT_LAUNCHER_SESSION=1 SessionStart hook + /cs:goal); loop_compiler.py
compiles that goal into a bounded grade->iterate loop (CMA user.define_outcome
self-grading, max_iterations 1..20), a recurring POSIX-cron scheduled-deployment
loop, or a single-pass interview->stage->launch workflow.
- 6 skills: agent-launcher-orchestrator (context: fork goal router) + interview
+ stage-launch + grade-iterate + run-without-you + wrap-up
- 18 stdlib-only deterministic scaffolder tools (NO network/API calls; live
launches emitted as BYOK curl that never prints the key); all pass --help/--sample
- 4 agents (orchestrator + interviewer + grader + deployer), 8 /cs:* commands
- opt-in SessionStart/SessionEnd hooks (exit 0 on any error), 5 shared
references, 4 assets (build-sheet schema + overview/next-directions templates
+ example)
- validators enforce CMA limits (<=20 skills/session, <=8 memory stores,
depth-1 multiagent, max_iterations <=20, <=1000 deployments/org)
- registered in marketplace.json; headline counters trued up via
derive_counters.py --check (skills 362->368, domains 18->19, tools 644->664,
refs 741->746, agents 102->106, commands 116->124, plugins 88->89)
Distinct from engineering/agent-harness (generic bounded loop over any domain)
and engineering/write-a-skill (authors Claude Code skills, not CMAs).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FwXG6TqCXKZQvF4iD69cv
Round-38 review noticed that the committed contract files already encode
an answer to some of 9's open decisions -- hooks.json wires
UserPromptSubmit even though 9.5's option (c) is to delete that hook.
The doc knew: 9.5 already says "if (c) wins, hooks.json must shrink too."
But that only helps a reader holding both files. Read on its own a
hooks.json says "these three hooks exist", which is precisely the wrong
impression, and a contract file is exactly the artifact someone reads on
its own before implementing. Same lesson as round 36's contested-tag gap,
one level up: a cross-reference is not a contract, and that applies
between files as much as between sections.
hooks.json's own description now marks the entry PROVISIONAL, names the
open decision, gives the reason (the budget is dominated by interpreter
cold-start, not by the script's work), and says plainly that listing the
hook is a contract for the shape it would take IF it survives -- not
evidence the decision was made. SessionStart and SessionEnd are marked
not provisional so the warning stays scoped.
Verified: hooks.json parses; 69 checks, 0 failures; derive_counters.py
--check unchanged (363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-37 review. The substantive finding: 9.5's options (a) fits /
(b) raise the budget / (c) drop the hook all treat spawn cost as a
constant to tolerate -- while last round's measurement showed spawn cost
is the ONLY cost that matters (scan 2-3ms, interpreter ~20ms). None of
the three attacks the measured bottleneck. That follows directly from a
number this doc already carried, and I should have drawn it rather than
waiting for a reviewer to.
Added (d): a warm resident process holding the store in memory, with the
hook reduced to a socket round-trip. Named as a real option because it is
the only one that removes the dominant term, explicitly NOT as the
recommendation -- its costs land on this design's own properties. A
"never blocks, exit 0" hook gains a liveness dependency and must keep the
cold path anyway, so complexity is added to rather than swapped for what
(a)-(c) need; lifecycle and stale-socket handling are a second heuristic
alongside 5.4's; a resident process holding memory contents in RAM is a
different security surface; and stdlib-scripts-that-exit is this repo's
shape. Sequenced behind the busy-machine measurement: if 100ms holds
under load, a daemon buys latency nobody needed.
Also trimmed the three passages the review named as archive-not-spec.
One of them ("the sentence went stale twice (53 -> 57 -> 67)") was itself
stale at 69, which is the argument for cutting it rather than updating it.
Verified: 69 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-36 review, three real findings.
1. Session-id uniqueness was the one load-bearing claim in 3.1.1 asserted
rather than evidenced, while everything around it cites line numbers or
arithmetic. Measured it: transcript filenames on a live install are
RFC-4122 UUIDs (version 5, RFC-4122 variant), shown with the commands
that produced them. One install is enough to establish shape, not to
promise the scheme is stable -- so the more useful half of the fix is
the fallback the reviewer noted was missing. The glob now has defined
behaviour for all three outcomes, and the >= 2 row is the one that
matters: without it a naive implementation takes the first match and
attributes a claim to the wrong session, which is a *wrong* citation
rather than a missing one, and 6 rule 6 cares about that distinction.
2. 4.2 states the contested-rendering rule tier-agnostically, but 5.2 is
the contract user_prompt_submit.py actually gets built from and never
mentioned it. An implementer working strictly from 5.2 ships a recall
path that surfaces a contested claim as plain fact. Stated in both
places now -- cross-references are not a contract.
3. L1 had a stored cap (500 atoms); L2/L3 had only injection budgets, so
a marker block could grow without bound while every session saw a
silently truncated view -- the 1 failure this design exists to prevent,
one layer down. L2 caps at 60 atoms with overflow demoted to L1
(recoverable, re-promotable); L3 caps at 30 and refuses further
promotions instead of deleting, since "never auto-demoted" means the
cap blocks inflow rather than choosing what to lose.
Verified: 69 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-35 review called the 100ms recall budget the highest-risk
unverified assumption, and estimated interpreter cold start at 20-40ms.
9.5 has prescribed the measurement since it was written -- "time a no-op
python3 -c pass plus a 500-atom scoring pass at p50/p95" -- so I ran it
rather than citing an estimate.
n=40, Linux container, otherwise idle:
python3 -c pass p50 12.4 p95 30.8 max 36.0
spawn + 500 atoms + score + top-5 p50 23.2 p95 30.1 max 50.6
...of which in-script work p50 2.1 p95 3.0
This reframes the risk rather than settling it. The scoring pass is
~2-3ms, so the 500-atom cap is not the binding constraint and never was
-- cold start is essentially the whole cost, which makes the budget a
process-spawn question rather than an algorithmic one. 100ms holds with
~3x headroom here; the 50.6ms max shows a real tail that would widen
under load. Recorded as a floor, not the answer: 9.5 asks for a busy
machine and this was an idle container.
Also cut two asides the review named as review-thread artifact rather
than spec: the 3.1 paragraph on why a rationale cannot be a $comment
inside a fixture (5 lines -> 3, keeping the reason), and the validator's
8-line comment on not whitelisting $comment (-> 4, keeping the warning a
future editor needs).
Verified: 69 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-34 review, two actionable items.
5.1 had no internal-budget discussion while 5.2 argues at length that its
1s hook timeout is a backstop rather than the target -- an asymmetry a
reader can only resolve by guessing. Stated why it is deliberate: this
hook runs once per session rather than once per prompt, so a slow run
costs one startup instead of compounding; and its work is bounded by the
2KB/4KB byte caps rather than by a scan that grows with history, where
recall scores up to 500 atoms. Also noted that 9.5's cold-start finding
lands here too if it comes back bad.
Addressed the .py.txt parking as a *precedent* rather than defending this
instance. What makes it legitimate is not intent, which is unfalsifiable,
but that the file is not a tool: no plugin ships it, no skill owns it,
nothing invokes it, and counting it would make python_tools less accurate
rather than more. The abuse it could be mistaken for -- a real tool
renamed to keep a headline number down -- is separable by one question a
reviewer can ask: is anything supposed to run this? Pointed at 11's
audit/ option as the way to avoid the pattern entirely.
Not changed: hooks.json naming three scripts that do not exist. Checked
whether the hypothetical lint exists -- nothing in scripts/ or
.github/workflows/ references hooks.json at all -- so the file's own
"CONTRACT ONLY" description is the whole mitigation needed today.
Verified: 69 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-33 review. 4.1's L1->L2 gate has two clauses -- >= 3 sessions AND
>= 2 distinct calendar days (UTC) -- and family 4 only checked the count.
The days clause is the half that stops one long working day from minting
an L2 claim, which is the exact case 4.1 calls out for the `stated` fast
path, and it had neither a check nor a fixture exercising it.
Added it for L2 atoms whose confidence is not `verified` (4.1 names
`verified` as the only exempt path). Worth stating why the test is
sound rather than approximate: first_seen and last_seen bound every
observation, so date(first) != date(last) is equivalent to ">= 2 distinct
days", not a proxy for it. Same date means every observation fell inside
it; different dates means at least two were touched.
Verified by shrinking the L2 fixture's window to one day:
FAILED: atm_961f033d spans >= 2 distinct calendar days
FAILED: atm_961f033d/L2 promoted_at falls inside its own observation window
The second is family 7 catching a side effect of the same injection,
which is the intended overlap.
Also fixed the placement section's framing. It claimed "neither option is
endorsed here" and then introduced its bullets with "both cut against
staying here" -- a lean, contradicting the sentence above it, and the
third reviewer in a row to read it as one. Now two symmetric cost lists
with nothing weighed.
Verified: 68 -> 69 checks, 0 failures; derive_counters.py --check
unchanged (363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-32 review. 4.1.1 step 3 enumerated nine of the schema's thirteen
required fields, so a memory_promote.py written literally against it
would emit a schema-invalid atom. The reviewer also caught that 4.1.3's
confidence-merge rule is scoped to "(5.3)" -- the same-tier SessionEnd
merge -- so it does not cover this cross-project one by implication.
Added a table for the four: `claim` from the earliest contributor (the
group normalizes identically; earliest is for determinism, since
normalization is lossy on case and punctuation), `confidence` = max()
across all contributors (4.1.3's principle, restated rather than
assumed), `redacted` = true if any is (over-claiming costs nothing,
under-claiming loses the signal), and `kind` must agree or the group is
ineligible -- `kind` is not in the hash key, so two projects classifying
the same sentence differently means the "same claim" premise is what is
shaky. Refusing keeps this section's one-directional property: L3
under-fires, it never mis-fires.
This step has now shipped three omissions (`source`,
`promoted_from_projects`, and these four), each caught by a human
reading the list against the schema. Added a check for the class: every
schema-required field must appear in 4.1.1. Re-injecting the omission
fails on ['kind', 'redacted'].
Two notes on that check, both found by verifying it rather than trusting
it. It first reported first_seen/last_seen/first_source as missing --
false, because norm_prose strips `_` as an italic marker, so the needles
had to be normalized the same way. And it is a substring test over the
section, so `confidence` and `claim` satisfy it via incidental mentions
elsewhere; that limit is now stated in the code rather than implied.
Verified: 67 -> 68 checks, 0 failures; derive_counters.py --check
unchanged (363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-31 review spotted "other-project" sitting inert in the validator's
allow-list. It is inert for a reason worth fixing rather than tidying:
the check only scanned the path-embedded `-home-user-<slug>` form, so
`project` and `promoted_from_projects` -- where names appear bare -- were
never checked at all. That is the whole coverage gap, not a dead entry.
The consequence was the exact leak 6.5 exists to prevent. The rule came
from a fixture that named a private repo; a fixture naming one in
`project` rather than in a path would have passed clean. Verified by
injecting one:
FAILED: no unknown project names in fixtures, in paths or in fields
['acme-internal-private']
Now gathers names from both sources before comparing. Check count
unchanged at 67.
Verified: 67 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Two of three notes from the eighth PR review round; the third needed no change.
1. `--max-rounds` was per-invocation, so `open` and a later `close` could
disagree about the cap and G5 escalation depended on how the command
happened to be typed. `open` now records the agreed cap in gate state and
status/collect/close inherit it. Passing the flag again is an explicit
renegotiation and prints the change rather than silently overriding.
Before: `open --max-rounds 2` then `status` -> "max_rounds": 5
After: same sequence -> "max_rounds": 2
2. The protocol-relative and own-asset URL allowances were documented in a
comment but nothing checked them, unlike the void-element and template-token
regressions which each got a fixture. `--sample` fixtures now carry
must-keep / must-drop URL assertions alongside the block count, and
`SAMPLE_HTML` exercises all three cases (`//host/x`, `https:` image,
`javascript:`). Verified the assertion bites: adding "javascript" to the
scheme allowlist turns --sample red with `FAIL - kept javascript:`, exit 2.
3. No change for cross-origin `<svg><use href="https://...">`. `href` is
already in URL_ATTRS and scheme-checked; `https:` is allowed there by the
same deliberate rule that lets a reviewed page's own `<img src>` load. It is
not a gap in the allowlist, it is the allowlist working as designed.
Gates: derive_counters --check pass, check_plugin_json --all pass, all three
scripts --help/--sample exit 0, write-a-skill checklist PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01233Eggb2cjSYf96X6C3pCm
Round-30 review: ~70 lines of meta-discussion about where this file
should live stood between a reader and the tiering design. Moved to a
new 11 at the end, leaving a three-line pointer at the top. Old 11
(Attribution) renumbered to 12; the heading-order linter confirms the
sequence is still consistent.
The content is kept rather than dropped -- it is the record of an open
maintainer decision that three reviewers have now asked about -- but it
is not part of the design and should not be read first.
Verified: 67 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-29 review. Family 6 compared 1's cited byte count against the
CURRENT size of the repo-root CLAUDE.md -- a file this repo appends a
release note to on nearly every point release. The next unrelated PR
touching it would have turned this suite red for a reason with nothing
to do with this folder. 10.1's own thesis is that a checker must own its
ground truth; this one borrowed a moving one.
Snapshotting the byte count as a constant in the checker was the
reviewer's other suggestion and is not better: it puts the same number in
a second place that can drift from the first, with nothing able to say
which is wrong.
Replaced with two properties of the sentence itself, both of which the
doc controls: the claim must name `wc -c` and carry a real byte figure,
and its two units must agree (84 KB == 85,875 bytes). That still catches
the defect this check was written for -- 1 once read "~40 KB",
eyeballed, off by more than 2x -- verified by re-injecting it:
A (eyeballed, no byte figure): FAILED names `wc -c` and cites a byte figure
B (42 KB vs 85,875 bytes): FAILED KB and byte figures agree
Check count is unchanged at 67 (two removed, two added), so the
self-referential count assertion still holds. Also fixed a docstring in
_find() that still described the repo-root lookup this removes.
Verified: 67 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-28 review. Three changes, two held.
5.1 now carries the consequence 9.6 leaves live: L2 and L3 are injected
together and 4.2.1's detector cannot reach L3, so nothing upstream
guarantees they agree. Stated as a constraint on the hook -- never emit
two colliding claims as plain assertions -- rather than a mechanism, so
it holds under all three of 9.6's candidates. The reviewer is right that
retrofitting conflict-marking after session_start.py ships costs more
than honouring it in the first version; that does not require settling
9.6 itself, only refusing to ship the unmarked case.
AGENT_MEMORY_RECALL -> AGENT_MEMORY_USERPROMPTSUBMIT, in DESIGN.md and
hooks.json. The old justification (shorter; matches section 3's
vocabulary) traded away a property worth more: with all three vars
mirroring their hook name, a user who knows Claude Code's hook names can
derive all three without reading this doc. Three vars under two
conventions also invites the typo report the reviewer predicts.
Added the exact CI workflow snippet to 10.1. Still not wiring it into
ci-quality-gate.yml -- that runs on every PR in the repo, for a folder
9.3 permits deleting after a two-week trial -- but saying yes now costs
one paste rather than a design conversation.
Held: the placement decision, and the suggestion to split mechanical
rationale into references/. The second turns out to be blocked by the
first, which was worth measuring: a references/*.md under
engineering/agent-memory/ moves the references counter 746 -> 747, while
the same file under audit/ is pruned and free. The status header now
tables that alongside the parking hack and the double-relocation cost --
three open items resolving from one decision, which is the argument for
answering it before the implementation PR.
Verified: 67 checks, 0 failures; hooks.json parses; derive_counters.py
--check unchanged (363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Three findings from the seventh PR review round.
1. `marketplace.json` still said "zero network requests" flat. An earlier
commit corrected exactly that wording in README.md, SKILL.md and
plugin.json — a reviewed HTML artifact's own https: assets do load, and
deliberately so — but missed marketplace.json. Now matches the others.
2. `GATED_ELSEWHERE` lived in human_gate.py as free-text prefixes matched
against messages generated in feedback_parser.py: an implicit cross-file
contract nothing enforced. The prefixes now live beside the
`problems.append()` calls that emit them and are read from the loaded
parser module, with the old literal kept only as a fallback for an older
parser.
Reproduced the drift on pre-fix code by rewording the G3 message:
G3 round 1 has no named reviewer
G7 round 1 integrity: unknown severity/kind 'BLOKCER'
G7 round 1 integrity: the sidecar names no reviewer - ... <- duplicate
Same reword post-fix produces only the first two lines.
3. `ATTR_RE` truncated a quoted header attribute at the first space:
`target="q3 plan.md"` parsed as `q3`. Quoted values now parse, and the
docstring states the quoting rule and that `target` is a display hint —
quote verification runs against the `--target` path.
Gates: derive_counters --check pass, check_plugin_json --all 90/90 OK,
marketplace.json parses, all three scripts --help/--sample exit 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01233Eggb2cjSYf96X6C3pCm
Round-27 review: DESIGN.md had accumulated its own review history inline
("round 3's fixture fix", "round 7 shipped two ids", "an earlier draft
violated..."), which reads as a PR changelog in a document an
implementer reads once. Fair, and I had been adding to it every round --
the previous commit put "until round 26" into 10.1.
Rewrote every such site to state the rule and the failure it prevents,
without the narration of who found it when. The rationale is what stops
a rule being re-broken; the round number is not. Two examples:
- 3.1's lifecycle callout now names the drift a "one lifecycle" claim
invites (two atoms that each validate alone and contradict each other
side by side) rather than reporting that a draft once did it.
- 10.1's property 1 now reads as a prohibition -- "it must not get this
property by exec-ing the fence" -- which is what a future editor needs,
instead of a note about when the exec was removed.
Same pass over the validator's comments, keeping the warnings (a future
editor of that file does need to know why $comment is not whitelisted)
and dropping the round counters.
Also stopped the status header reading as a lean toward audit/. Two
consecutive reviewers took "a better fit than this note originally
claimed" as an endorsement; it was meant as a correction to the note.
Now states plainly that neither option is endorsed and the decision is
the maintainer's, with the costs of each listed below it.
No rules, thresholds or contracts changed. Verified: 67 checks, 0
failures; derive_counters.py --check unchanged (363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-26 review asked for a trust-boundary comment on published_normalize()'s
exec of a fenced block extracted from DESIGN.md. Removed the exec instead,
because the same review's other finding turns the comment into an
insufficient answer.
Findings 1 and 2 interact. Wiring the validator into CI -- correctly
observed to be possible today, since a temp .py copy is never seen by
derive_counters.py -- would have escalated the exec from "a maintainer
runs this on a branch they trust" to "any PR author executes arbitrary
code," because ci-quality-gate.yml triggers on pull_request and that
includes forks. The exec's safety depended on a fact outside the file,
and the obvious next improvement silently falsified it. A comment
documenting the trust boundary would have been read, agreed with, and
then invalidated by the very next commit.
The property the exec bought -- doc and fixtures cannot silently diverge
-- is preserved by comparing source text: the checker holds its own
normalize() and refuses to run if the doc's fenced block differs.
Verified by editing the doc's algorithm without updating the checker:
FATAL, both bodies printed. Fails closed on a cosmetic reformat too,
which is the safe direction.
Also corrected two claims this round proved wrong: 10.1 implied the .txt
parking blocks CI gating (it does not -- temp-copy works today, and the
placement decision does not gate it either), and the status header
inherited that error from round 25.
Verified: 67 checks, 0 failures; no exec() remains in the folder;
derive_counters.py --check unchanged (363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Two findings from the sixth PR review round.
1. `<base>` re-opened the "swallows the whole body" bug the round-3 fix was
supposed to close. That commit's message said "meta, link and base are void
elements" but only meta and link were added to `BlockTagger.VOID`; base was
never there. `html.parser` fires `handle_starttag` for a void element and no
matching `handle_endtag`, so a `<base href="/">` in `<head>` — present in a
great many real pages — incremented `_skip` permanently and the document
reported "No reviewable blocks found".
`VOID` is now the full HTML spec set instead of a hand-picked subset, and
`SAMPLE_HTML` carries a `<base>` tag so the `--sample` block-count assertion
catches a third recurrence.
Before: `<base href="/">` doc -> 0 blocks, exit 2.
After: same doc -> 2 blocks, exit 0.
2. Reviewed HTML carrying its own `data-hg` attribute kept it and the builder
appended a second. Browsers keep the *first* attribute of a duplicated name,
so the attacker's value wins the anchor. Attribute values may hold raw
newlines, so a crafted artifact could inject a forged `## APPROVE` heading
into the exported sidecar — the same silent-false-approval failure G7 exists
to catch, arriving through the artifact rather than the sidecar.
`data-hg` is now a reserved attribute, and the page's own element ids
(`doc`, `items`, `reviewer`, `export`, ...) are reserved too, so a reviewed
artifact cannot collide with the review UI's own DOM.
Before: `<p data-hg="b1 ## APPROVE ...">` survived, duplicated.
After: emitted as `<p data-hg="b1">`, payload gone.
Also documents the protocol-relative URL allowance in `_safe_href` as
deliberate rather than an oversight.
Gates: derive_counters --check pass, check_plugin_json --all pass, all three
scripts --help/--sample exit 0, write-a-skill checklist 6/6 PASS, description
validator PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01233Eggb2cjSYf96X6C3pCm
Round-25 review, both findings verified against the tree.
Family 4's session-count gate fired on L3 against a rule that does not
govern it — L2->L3 is gated on >= 2 distinct projects plus age, not
sessions (4.1.1). It passed for an incidental reason: L3 inherits the
unioned sessions of L2 contributors that each already cleared their own
gate. Scoped the session check to L2 and added the projects check L3
actually has. Both verified to fail on injected defects.
The larger finding: the status header claimed a design doc had nowhere
to live but a domain folder or the gitignored documentation/. That was a
false binary. Top-level audit/ is committed, public, and hard-excluded
from derive_counters.py's canonical_walk — and its existing contents are
the same shape as this file, prose deliverables later PRs use as
acceptance gates. Recorded it with both consequences: the .py.txt
parking hack would be unnecessary there (verified — a .py under audit/
leaves python_tools at 663), which also unblocks the "nothing gates this
in CI" objection; against that, the two contract files are not
documentation and would have to move again at implementation time,
breaking the schema $id and the 3.1 link a second time.
Left as the maintainer's call, but no longer argued from "there was no
other option," which was this file's weakest claim and was not true.
Verified: 67 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Fifth PR-review round on #948. Both reproduced first.
1. state_dir() anchored to os.getcwd() while state_path() keyed by the
artifact's realpath. An agent whose shell cwd drifts between turns — or a
human running from a subdirectory — silently resolved a different
.human-gate/ and started from empty state. Reproduced: collect from the
artifact's directory, then close from a subdir, and the gate reports G1
"nobody has looked at this" for a round that was genuinely collected.
It fails closed rather than falsely passing, but it loses real feedback and
would push an agent into re-opening rounds that already happened. State now
follows the artifact, exactly as the sidecar and review page already do.
An explicit --state-dir still wins.
2. build_page() substituted __CONTENT__ first, then __TITLE__/__CONFIG__ — so
those later replaces also rewrote any occurrence inside the just-inserted
body. Reviewing a document that mentions the tokens (this skill's own docs
being the obvious case) injected the entire JSON config into the visible
page, not just a garbled title. Worse than the report suggested.
All three slots now fill in one re.sub pass, so no substituted value can be
re-substituted — which also covers the reverse direction, where block text
inside the config JSON contains __CONTENT__. The Markdown --sample fixture
now carries the token text, so the case is guarded rather than reasoned
about.
Minor: `--waive` with nothing to waive now prints "nothing to waive" instead of
silently discarding the flag.
Re-verified: derive_counters --check, check_plugin_json --all, checklist 6/6
PASS, description validator PASS, all three scripts --help/--sample green,
--sample asserts both fixtures' block counts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01233Eggb2cjSYf96X6C3pCm