Skip to content

docs: document shell test-helper duplication and exit-code divergence as deliberate - #853

Merged
kyle-sexton merged 13 commits into
mainfrom
docs/820-shell-helper-dedup-or-document
Jul 25, 2026
Merged

docs: document shell test-helper duplication and exit-code divergence as deliberate#853
kyle-sexton merged 13 commits into
mainfrom
docs/820-shell-helper-dedup-or-document

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the design fork raised in #820: whether the shell assert-helper duplicated across
5 plugins (and the divergent per-script exit-code taxonomies alongside it) should be
consolidated into a shared mechanism, or documented as deliberate. This PR documents.

Fix

  • Adds docs/conventions/shell-test-helpers/README.md as the owner doc explaining why the
    duplication and divergence stay as-is, and registers it in docs/PLUGIN-PHILOSOPHY.md's
    convention registry table.
  • Adds a one-line pointer comment at each copy site back to the owner doc:
    guardrails/hooks/guardrails-test-helpers.sh, claude-ops/hooks/claude-ops-test-helpers.sh,
    source-control/scripts/test-helpers.sh, repo-hygiene/skills/clean/scripts/lib/test-helpers.sh,
    work-items/tools/work-item-tracker/tests/lib.sh.
  • Adds the same kind of pointer to scripts/check-skill-portability.test.sh, which opted out
    for an unrelated reason (it's repo tooling, not a plugin, so no plugin assertion library
    applies) — noted so the fork's second observation isn't left unexplained.
  • Bumps plugin.json + adds a CHANGELOG.md entry for every plugin whose helper file gained
    the pointer comment. Each has been re-derived from current main many times as sibling PRs
    merged or rebased mid-flight (see Related for the current picture):
    • repo-hygiene: 0.4.4→0.4.6
    • source-control: 0.15.7→0.16.1
    • claude-ops: 0.17.1→0.17.5
    • work-items: 0.18.1→0.20.1
    • guardrails: 0.9.5→0.9.6 (no open-PR collision at any point so far)

No behavior change anywhere — comments and docs only.

Decision

Chose Option B: document the per-plugin duplication and exit-code divergence as deliberate.
No shared helper introduced.

Investigation before deciding:

  • This repo already has one sanctioned cross-plugin shared-source mechanism: a canonical file
    under lib/ (e.g. lib/hook-utils.sh), copied — not imported — into each carrying plugin by
    a dedicated scripts/sync-*.sh, tracked in scripts/cross-plugin-source-registry.txt, and
    drift-checked by scripts/check-cross-plugin-source-drift.sh --check.
  • That mechanism is scoped to clusters meant to stay byte-identical. Running
    check-cross-plugin-source-drift.sh discover confirms it never even flags the five
    assert-helper files as a cluster candidate — they live at different paths per plugin and
    are not byte-identical, so they fall outside that mechanism's scope entirely.
  • Reading all five files: they are already three genuinely different shapes, not one library
    that drifted — a hook-contract shape (guardrails/claude-ops: ok/bad, PASS/FAIL,
    make_sink/wait_for_sink), a skill-script shape (source-control/repo-hygiene: pass/fail,
    FAILED/CASE_NUM, file-existence assertions), and a vendored-seam shape (work-items: same
    primitives, but owned by the seam itself so it stays correct wherever the seam is resolved
    from, independent of this repo's tooling).
  • Consolidating would mean designing a fourth, unified assertion API and rewriting every
    existing *.test.sh onto it — a bigger, riskier change than the coupling it would remove,
    and it would cross the plugin-independence boundary docs/PLUGIN-PHILOSOPHY.md's design
    boundary section already draws (no plugin imports files from a sibling plugin).
  • Exit-code taxonomies (remove-path.sh 0/1/2/3/4, git-tree-reset-batch.sh 0/1/2 forwarding
    a child's 5/7, check-skill-portability.sh 0/1/2) encode genuinely different per-script
    contracts, not arbitrary numbering — each script already documents its own Exit: line, and
    a shared usage/exit helper would either flatten those contracts or grow per-caller branching.
  • Deferred, not rejected: guardrails-test-helpers.sh and claude-ops-test-helpers.sh are the
    one pair that already share a shape closely. If they converge to byte-identical, vendoring
    just that pair through the existing lib/ + sync-*.sh + registry mechanism is the smaller,
    precedented move — recorded as the trigger in the owner doc rather than acted on now.

Verification

  • shellcheck clean on all 6 edited shell files.
  • Full check-skill-portability.test.sh suite: 16/16 pass.
  • check-cross-plugin-source-drift.sh --check: no unregistered or drifted clusters.
  • check-changelog-parity.sh --check: passes with every version bump.
  • Ran every *.test.sh that sources an edited helper (repo-hygiene/clean, guardrails hooks,
    claude-ops hooks, work-items adapters/lib) — all green, confirming the comment-only edits
    changed no behavior.
  • markdownlint-cli2 and lychee clean on the new and modified docs.

Related

Closes #820

Work-class: C2 (mechanical) — attended triage 2026-07-23, operator-ratified. 🤖

… as deliberate

Resolves the fork in #820: the per-plugin shell assert-helper copies (guardrails,
claude-ops, source-control, repo-hygiene, work-items) and their divergent per-script
exit-code taxonomies stay as-is rather than being consolidated into a shared
mechanism. The existing lib/ + sync-*.sh vendoring precedent (hook-utils.sh) only
covers clusters meant to stay byte-identical; these five copies are three genuinely
different shapes already, so forcing them onto one shared library would mean a
bigger, riskier redesign than the coupling it removes — and would cross the
plugin-independence boundary the plugin philosophy already draws.

Adds docs/conventions/shell-test-helpers/README.md as the owner doc, registers it
in PLUGIN-PHILOSOPHY.md's convention registry, and adds a one-line pointer at each
copy site (plus scripts/check-skill-portability.test.sh, which opted out for an
unrelated reason: it is repo tooling, not a plugin).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
@kyle-sexton kyle-sexton added the automated Opened by automation. label Jul 21, 2026
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 11s —— View job


PR #853 Review

  • Gather context and understand the PR
  • Review changed files
  • Check documentation accuracy and consistency
  • Verify version bumps and CHANGELOG entries
  • Post review findings

Summary

This is a clean docs-only PR. No behavior changes anywhere — all 141 additions are comments and documentation. The architecture decision is well-reasoned and the execution is careful. A few minor observations below, none blocking.


Documentation (docs/conventions/shell-test-helpers/README.md)

Accurate and complete. The three-shape taxonomy (hook-contract, skill-script, vendored-seam) maps correctly to the five files. The "Deferred, not rejected" trigger condition is precise and actionable. The "Why not the existing vendoring mechanism" section correctly explains why check-cross-plugin-source-drift.sh discover would never flag these as an unregistered cluster.

One minor clarity gap: the Conformance section states "each copy site above carries a one-line pointer back to this doc," but scripts/check-skill-portability.test.sh also received a pointer yet isn't listed as a copy site anywhere in the doc. The earlier prose does explain its opt-out reason, but a reader scanning Conformance only would wonder why there's a pointer at that file if it isn't named. A one-sentence rider like "scripts/check-skill-portability.test.sh carries a pointer for the opt-out reason documented above, but is not a copy site" would close the loop. Not blocking — the information is present, just split across sections.

Permalink: docs/conventions/shell-test-helpers/README.md#L65-L72


Pointer comments in helper files

All six files received the same consistent wording. The pointer in the five plugin helpers:

# Duplicated across plugins by design, not drift — see
# docs/conventions/shell-test-helpers/README.md at the repo root.

The pointer in scripts/check-skill-portability.test.sh correctly names the opt-out reason ("repo tooling, not a plugin") before forwarding to the owner doc.

All correct. The # shellcheck disable=SC2016 pragma in check-skill-portability.test.sh remains directly before set -uo pipefail after the insertion — the new comment block doesn't disturb the directive's effectiveness.


Version bumps

All five bumps are patch (x.y.z → x.y.z+1), correct for docs-only changes:

Plugin Before After
repo-hygiene 0.4.4 0.4.5
source-control 0.15.7 0.15.8
guardrails 0.9.5 0.9.6
claude-ops 0.17.1 0.17.2
work-items 0.18.1 0.18.2

The merge-order race condition flagged in the PR description (competing bumps in #826, #839, #840, #844) is real, but it's correctly flagged and requires a rebase after those land — not a defect in this PR.


CHANGELOG entries

All five entries follow the established format: ## [x.y.z]### Documentation → one-paragraph description ending with "No behavior change." Consistent across all five plugins. No issues.


Relative path correctness

  • README links (../../../plugins/...) are correct from docs/conventions/shell-test-helpers/ (three levels up to repo root).
  • docs/PLUGIN-PHILOSOPHY.md link (conventions/shell-test-helpers/README.md) is correct relative to docs/.
  • Shell file pointer comments use repo-root-anchored prose ("at the repo root"), appropriate for shell comments where relative paths would be meaningless.

Verdict

Approve. No correctness issues, no security concerns, no behavior changes. The one minor observation (Conformance section doesn't explicitly name check-skill-portability.test.sh) is a documentation clarity nit, not a defect. The architecture reasoning in the README is sound and gives future contributors a clear trigger for when to revisit the guardrails/claude-ops pair consolidation.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 7s —— View job


Security Review

  • Gather context
  • Fetch PR diff and read changed files
  • Audit shell script modifications for injection or unsafe patterns
  • Audit new markdown doc and JSON changes
  • Post findings

No security issues found.

This PR is verifiably documentation and comments only. Here is what each category of change does and why none carries security risk:

Shell scripts (6 files modified): Each change adds exactly 3–4 lines of shell comments (#-prefixed). Comments are never executed by the shell. The added text is static prose referencing a relative internal path; it is not passed to any command, eval, source, or file operation anywhere in the changed lines or surrounding context.

New markdown doc (docs/conventions/shell-test-helpers/README.md): Pure prose and relative markdown links. All links point inward to paths within this repo (no external URLs). No executable content, no credentials, no template expressions.

PLUGIN-PHILOSOPHY.md: One additional table row with a relative link. Same assessment as above.

plugin.json files (5 files): Each changes only the version field by one patch increment. No new permissions, dependencies, mcpServers, remote URLs, or other security-relevant fields are touched.

CHANGELOG.md files (5 files): Plain text changelog entries. No executable content.

The attack surface of this diff is zero: no executable code paths are added or modified, no external resources are introduced, no trust boundaries are crossed, and no secrets or credentials appear anywhere in the changes.

…de-ops, work-items

Re-checked open PRs touching these plugins' plugin.json right before the version-bump
collision protocol requires it: source-control carries #839 (0.15.8) and #840 (0.15.9),
claude-ops carries #844 (0.17.2), work-items carries #826 (0.18.2) — all still open.
Bumps this PR's claims one past each plugin's current highest open-PR claim
(source-control 0.15.10, claude-ops 0.17.3, work-items 0.18.3) so this PR does not
collide at merge time regardless of which sibling lands first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
@kyle-sexton kyle-sexton added the do-not-merge Hard merge gate: do not merge while applied. label Jul 21, 2026
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 47s —— View job


PR #853 Review

  • Gather context and understand the PR
  • Review PR diff (updated branch — 2 commits)
  • Check version bump accuracy (fix commit)
  • Verify documentation consistency
  • Post review findings

What changed since the last review

The branch gained one new commit — ae5c5aa fix: bump past highest open-PR version claim for source-control, claude-ops, work-items — which re-checked competing open PRs right before merge and bumped this PR's version claims past each plugin's highest claimed version:

Plugin Prior claim Competing PRs New claim
source-control 0.15.8 #839 → 0.15.8, #840 → 0.15.9 0.15.10
claude-ops 0.17.2 #844 → 0.17.2 0.17.3
work-items 0.18.2 #826 → 0.18.2 0.18.3
guardrails 0.9.6 (none) 0.9.6 (unchanged)
repo-hygiene 0.4.5 (none) 0.4.5 (unchanged)

The math is correct in each case: max(competing claims) + 1 patch → 0.15.10 for source-control (skipping 0.15.9 because #840 holds it), 0.17.3 for claude-ops, 0.18.3 for work-items. The CHANGELOG headers were updated in sync with the plugin.json bumps.


Documentation (docs/conventions/shell-test-helpers/README.md)

Unchanged since prior review. Still accurate and complete. The three-shape taxonomy, "Deferred, not rejected" trigger, and "Why not the existing vendoring mechanism" sections all hold. The minor Conformance-section gap noted in the previous review (the check-skill-portability.test.sh pointer isn't named in the Conformance list even though it received one) is still present and still minor — the information is present in the preceding prose.


CHANGELOG version gaps

The fix commit produces non-sequential CHANGELOG entries for three plugins:

These gaps are the intended consequence of the collision-avoidance strategy and will close when the sibling PRs land. If a sibling PR is ever closed without merging, there will be a permanent gap in the sequence. This is an accepted cost of the "bump past highest" approach and isn't a defect — worth being aware of when reviewing those sibling PRs later, but nothing to fix here.


Shell comment pointers

All six edited shell files were reviewed at HEAD. Pointer wording is consistent across the five plugin helpers:

# Duplicated across plugins by design, not drift — see
# docs/conventions/shell-test-helpers/README.md at the repo root.

The check-skill-portability.test.sh pointer correctly names the opt-out reason before forwarding to the owner doc. The fix commit touched none of the shell files — comments are correct at both commits.


Verdict

Approve. The fix commit is correct. Version arithmetic matches the stated competing-PR state, CHANGELOG headers track plugin.json, and no other files were affected. The base documentation change (commit 056cb9c) was already reviewed and found clean. No correctness issues, no security concerns, no behavior changes anywhere in the diff.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 19s —— View job


Security Review — PR #853

  • Gather context
  • Fetch PR diff and read changed files
  • Audit shell script modifications
  • Audit new markdown doc and JSON changes
  • Post findings

No security issues found.

This PR is verifiably comments and documentation only. The full diff was audited category by category:

Shell scripts (6 files modified): Each change adds 3–4 lines of #-prefixed comments only — static prose pointing to the owner doc. Comments are never executed. None of the added lines are passed to any command, eval, source, or substitution in the changed or surrounding context.

New markdown doc (docs/conventions/shell-test-helpers/README.md): 72 lines of prose and relative markdown links. All links point to paths within this repo (no external URLs). No executable content, no secrets, no template expressions.

docs/PLUGIN-PHILOSOPHY.md: One additional table row with a relative internal link. Same assessment.

plugin.json files (5 files): Each modifies only the version field. No new permissions, dependencies, mcpServers, remote URLs, or other security-relevant fields are touched. Note: the actual version increments (claude-ops 0.17.1→0.17.3, source-control 0.15.7→0.15.10, work-items 0.18.1→0.18.3) are larger than the PR description's patch-increment claim; this is explained by the "bump past highest open-PR version claim" fix commit and carries no security implication.

CHANGELOG.md files (5 files): Plain-text changelog entries with no executable content.

The effective attack surface of this diff is zero: no executable code paths added or modified, no external resources introduced, no trust boundaries crossed, no secrets or credentials present anywhere in the changes.

Resolves conflicts on source-control and work-items plugin.json/CHANGELOG.md:
main advanced source-control to 0.15.8 (#839, merged) and work-items to 0.18.2
(#826, merged) since this branch was last rebased. Also re-checked live open
PRs at merge time and found work-items now carries a new open claim, #857 at
0.19.0 (Jira adapter) — re-bumped this branch's work-items claim from 0.18.3
to 0.19.1 to stay ahead of it. source-control's 0.15.10 and claude-ops's
0.17.3 remain valid (still one past #840/#860's 0.15.9 and #844/#860's 0.17.2
open claims, respectively).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 16s —— View job


PR #853 Review (post-sync)

  • Gather context
  • Check what changed since last review (branch synchronize)
  • Review current diff
  • Post findings

What's new since the last review

The branch gained one commit — 11ac0f0 Merge branch 'main' into docs/820-shell-helper-dedup-or-document. No new content commits; the substantive work (056cb9c docs commit and ae5c5aa fix commit) is unchanged.

The merge resolved conflicts on source-control and work-items plugin.json/CHANGELOG.md. Two things changed as a result:

1. #826 (work-items 0.18.2) and #839 (source-control 0.15.8) have since merged. Main now carries those as its base. The merge re-anchored this branch's claims against the new base correctly.

2. New open PR #857 (Jira adapter, claims work-items 0.19.0) appeared. This PR's work-items claim was re-bumped from 0.18.3 → 0.19.1 to stay ahead of it. The CHANGELOG entry documents the jump explicitly.


Version arithmetic at HEAD

Plugin main base Highest open claim This PR claims Math
source-control 0.15.8 #840, #860 → 0.15.9 0.15.10 max + 1 ✓
claude-ops 0.17.1 #844, #860 → 0.17.2 0.17.3 max + 1 ✓
work-items 0.18.2 #857 → 0.19.0 0.19.1 max + 1 ✓
guardrails 0.9.5 (none) 0.9.6 base + 1 ✓
repo-hygiene 0.4.4 (none) 0.4.5 base + 1 ✓

All correct.


One thing worth noting: work-items lands as a minor version

The work-items claim is now 0.19.1 — a minor version release — for what is semantically a patch-level docs-only change. This is the unavoidable consequence of the "bump past highest open claim" strategy when the blocking PR (#857) happens to occupy a minor slot. The CHANGELOG entry calls it out explicitly:

"Version re-bumped past #857's open 0.19.0 claim (Jira adapter) to avoid a collision."

This is not a defect — the strategy is correctly applied and self-documented — but it means work-items consumers will see 0.18.x → 0.19.1 with no features between those versions. Accepted cost of the approach, consistent with how the prior bumps were handled.


PR description do-not-merge conditions are now partially stale

The PR body lists the holds as:

The conditions in the body don't match the current state. This won't block the merge — the do-not-merge label is the actual gate — but whoever removes the label should verify against current open PRs rather than the body. Worth a quick note before unlabeling.


Documentation and shell files

Unchanged since ae5c5aa. The review from that commit still holds: core docs accurate, pointer comments consistent across all six files, relative paths correct, the minor Conformance-section gap (check-skill-portability.test.sh not explicitly named in the Conformance list) remains a documentation nit, not a defect.


Verdict

Approve. The merge is clean. Version math is correct. The work-items minor-version jump is documented and expected. The only action item before removing do-not-merge is verifying the current open-PR state (not the body) — #840, #857, #860, and #844 should all be confirmed merged or withdrawn before unlabeling.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 33s —— View job


Security Review — PR #853

  • Gather context
  • Fetch PR diff and read changed files
  • Audit shell script modifications
  • Audit new markdown doc and JSON changes
  • Post findings

No security issues found.

This PR is verifiably documentation and comments only. Full audit by category:

Shell scripts (6 files modified): Each change adds 3–4 lines of #-prefixed comments — static prose pointing to an internal doc path. Shell comments are never executed. The added lines contain no command substitutions, variable expansions, heredoc terminators, escape sequences, or control characters that could influence shell parsing. Verified: all new text sits between a # and the next newline in each file. The # shellcheck disable=SC2016 directive in check-skill-portability.test.sh is correctly preserved immediately before set -uo pipefail after the insertion.

New markdown doc (docs/conventions/shell-test-helpers/README.md): 72 lines of prose and relative markdown links. Scanned all links: every one resolves to a path within this repo — no external URLs anywhere in the file. No executable content, no credentials, no template expressions, no CDN or remote resource references.

docs/PLUGIN-PHILOSOPHY.md: One additional table row with a single relative internal link. Same assessment.

plugin.json files (5 files): Each modifies only the version string field. No new or modified permissions, dependencies, mcpServers, hooks, allowedTools, remote URLs, or other capability-granting fields are present. Full manifest for work-items (the only one with an unusual version jump to 0.19.1) was read and confirmed clean — userConfig fields are unchanged, no new egress surface.

CHANGELOG.md files (5 files): Plain-text changelog entries. No executable content.

GitHub Actions: No .github/workflows/ files are modified in this PR. No workflow permission changes, no pull_request_target/workflow_run patterns introduced, no script injection vectors.

The effective attack surface of this diff is zero.

repo-hygiene had no open-PR collision at this PR's original open, but #860
(chore/skill-shell-declaration) opened since and claims 0.4.5 exactly, matching
this branch's prior claim. Bump to 0.4.6 to stay ahead of it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 48s —— View job


Security Review — PR #853

  • Gather context
  • Fetch PR diff and read changed files
  • Audit shell script modifications for injection or unsafe patterns
  • Audit new markdown doc and JSON changes
  • Post findings

No security issues found.

This PR is verifiably documentation and comments only. Full audit by category:

Shell scripts (6 files modified): Each change adds 3–4 lines of #-prefixed comments — static prose pointing to an internal doc path. Shell comments are never executed. The added text contains no command substitutions, variable expansions, heredoc terminators, escape sequences, or control characters that could influence shell parsing. The # shellcheck disable=SC2016 directive in check-skill-portability.test.sh is correctly preserved immediately before set -uo pipefail after the insertion.

New markdown doc (docs/conventions/shell-test-helpers/README.md): 72 lines of prose and relative markdown links. All links resolve to paths within this repo — no external URLs anywhere in the file. No executable content, no credentials, no template expressions, no CDN or remote resource references.

docs/PLUGIN-PHILOSOPHY.md: One additional table row with a single relative internal link. Same assessment.

plugin.json files (5 files): Each modifies only the version string field. No new or modified permissions, dependencies, mcpServers, hooks, allowedTools, remote URLs, or other capability-granting fields. Full manifests were reviewed — no egress surface changes.

CHANGELOG.md files (5 files): Plain-text changelog entries. No executable content.

GitHub Actions: No .github/workflows/ files modified. No pull_request_target/workflow_run patterns, no script injection through the github context, no permission-widening changes, no supply-chain risk from loosened or unpinned action pins.

The effective attack surface of this diff is zero.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 25s —— View job


PR #853 Review (post-sync)

  • Gather context
  • Check what changed since last review (841a1bf)
  • Review current diff
  • Post findings

What's new since the last review

One new commit: 841a1bf fix: bump repo-hygiene past a newly-opened collision (#860)

PR #860 (chore/skill-shell-declaration) opened after the prior sync and claimed repo-hygiene 0.4.5 — exactly matching this branch's prior claim. The fix bumps the repo-hygiene version to 0.4.6 and updates the CHANGELOG header and trailing note to name the collision.

Only two files changed: plugins/repo-hygiene/.claude-plugin/plugin.json and plugins/repo-hygiene/CHANGELOG.md. No documentation, shell files, or other plugin manifests were touched.


Version arithmetic at HEAD

Plugin main base Highest open claim This PR claims Math
repo-hygiene 0.4.4 #860 → 0.4.5 0.4.6 max + 1 ✓
guardrails 0.9.5 (none) 0.9.6 base + 1 ✓
source-control 0.15.8 #840, #860 → 0.15.9 0.15.10 max + 1 ✓
claude-ops 0.17.1 #844 → 0.17.2 0.17.3 max + 1 ✓
work-items 0.18.2 #857 → 0.19.0 0.19.1 max + 1 ✓

All correct.


CHANGELOG update

The prior ## [0.4.5] header became ## [0.4.6] and the entry body gained the note:

"Version bumped past #860's open 0.4.5 claim to avoid a collision."

Consistent with how the same note was added for source-control, claude-ops, and work-items in the earlier fix commit. Pattern is uniform across all five plugins.


Documentation and shell files

Unchanged since ae5c5aa. Prior review findings still hold — the three-shape taxonomy, pointer comments, relative path correctness, and the minor Conformance-section nit (that check-skill-portability.test.sh is noted in prose but not listed in the Conformance table) are all as previously reviewed.


Do-not-merge holds (updated)

#860 is now a blocker for both source-control (claims 0.15.9) and repo-hygiene (claims 0.4.5). Before removing the label, confirm merged or withdrawn:


Verdict

Approve. The fix commit is correct and minimal — two files, right version numbers, CHANGELOG updated in sync with the stated reason. All prior review findings unchanged. No correctness issues, no security concerns, no behavior changes anywhere in the diff.

Re-syncs onto main after #860 (fleet-wide `shell: bash` declaration sweep)
merged, which bumped source-control to 0.15.9, claude-ops to 0.17.2, and
repo-hygiene to 0.4.5. Resolved conflicts on those three plugins' plugin.json
and CHANGELOG.md; re-derived each held version against the new main plus a
fresh live-open-PR check rather than assuming the prior numbers still held:

- source-control 0.15.10: still one past main's 0.15.9 and #840's live 0.15.9
  claim.
- claude-ops 0.17.3: still one past main's 0.17.2 and #844's live 0.17.2
  claim.
- repo-hygiene 0.4.6: still one past main's new 0.4.5 (#860, now merged); no
  open-PR collision remains for this plugin.
- work-items 0.19.1 and guardrails 0.9.6 were untouched by this merge (no
  conflict) and remain correct against #857/#861 (both still 0.19.0, open)
  and no open collision, respectively.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 20s —— View job


PR #853 Review (post-sync)

  • Gather context
  • Check what changed since last review (841a1bf)
  • Review current diff and version arithmetic
  • Post findings

What's new since the last review

One new commit: e96e9a6 Merge branch 'main' into docs/820-shell-helper-dedup-or-document

The merge pulled in #860 (chore: declare shell: bash on every skill with dynamic-context injections), which bumped source-control → 0.15.9, claude-ops → 0.17.2, and repo-hygiene → 0.4.5 on main. Conflicts were resolved on those three plugins' plugin.json and CHANGELOG.md.


Version arithmetic at HEAD

Plugin Main (7688ae7) Open holds This PR claims Math
source-control 0.15.9 #840 → 0.15.9 0.15.10 max + 1 ✓
claude-ops 0.17.2 #844 → 0.17.2 0.17.3 max + 1 ✓
repo-hygiene 0.4.5 (none remaining) 0.4.6 main + 1 ✓
work-items 0.18.2 #857 → 0.19.0 0.19.1 max + 1 ✓
guardrails 0.9.5 (none) 0.9.6 main + 1 ✓

All correct. The merge commit message documents the re-derivation reasoning against the new main baseline — the logic holds up.


One minor observation: claude-ops CHANGELOG attribution

The [0.17.3] entry says "Version bumped past #844's open 0.17.2 claim to avoid a collision." This is slightly imprecise: #860 (now merged) is what established 0.17.2 on main, not just a competing claim. The version choice (0.17.3) is correct regardless — the number passes the max+1 arithmetic — but the note credits only the still-open #844 when the merged #860 is equally the reason that slot is taken. Not blocking, and the version number is what actually matters at merge time.


Do-not-merge hold picture (updated after this sync)

With #860 now merged, one hold has cleared:

Plugin Prior holds After this sync
source-control #840, #860 #840 only (0.15.9)
claude-ops #844, #860 #844 only (0.17.2)
repo-hygiene #860 none — cleared
work-items #857 #857 (0.19.0) — still open
guardrails none none

Before removing do-not-merge, confirm #840, #844, and #857 are merged or withdrawn. repo-hygiene no longer has a live competing claim.


Documentation and shell files

Unchanged since ae5c5aa. All prior review findings hold: three-shape taxonomy accurate, pointer comments consistent across all six shell files, relative paths correct, the minor Conformance-section nit (check-skill-portability.test.sh pointer noted in prose but not in the Conformance list) is still present and still non-blocking.


Verdict

Approve. The merge is clean. Version arithmetic is correct against the new main baseline established by #860. The only pending do-not-merge conditions are the three still-open sibling PRs (#840, #844, #857) — verify those are settled before unlabeling.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 7s —— View job


Security Review — PR #853

  • Gather PR diff
  • Read changed shell files
  • Audit markdown, JSON, and docs changes
  • Post findings

No security issues found.

All 18 changed files were read and audited. This PR adds comments and documentation only — no new executable code paths anywhere in the diff. Category-by-category breakdown:

Shell scripts (6 files modified): Each change adds 3–4 lines of #-prefixed comments. Shell comments are never executed. The added text is static prose pointing to an internal doc path; it contains no command substitutions, variable expansions, heredoc terminators, escape sequences, or control characters that could influence shell parsing. The pre-existing make_sink bodies in guardrails-test-helpers.sh (which writes arbitrary caller-supplied content as a temporary executable) and claude-ops-test-helpers.sh (which uses %q to shell-quote a path into a generated cat > redirect) are unchanged — this PR's changes to those files are the pointer comment block only. The # shellcheck disable=SC2016 directive in check-skill-portability.test.sh remains directly before set -uo pipefail after the insertion.

New markdown doc (docs/conventions/shell-test-helpers/README.md): 72 lines of prose and relative markdown links. All links resolve to paths within this repo — no external URLs in the file. No executable content, no credentials, no template expressions.

docs/PLUGIN-PHILOSOPHY.md: One additional table row with a single relative internal link. Same assessment.

plugin.json files (5 files): Each modifies only the version string field. No new or modified permissions, dependencies, mcpServers, hooks, allowedTools, remote URLs, or other capability-granting fields. All five manifests were read; work-items (the one with the largest version jump to 0.19.1) shows no egress surface changes — userConfig fields are unchanged.

CHANGELOG.md files (5 files): Plain-text changelog entries. No executable content.

GitHub Actions: No .github/workflows/ files are modified in this PR. No pull_request_target/workflow_run patterns, no script injection through the github context, no permission-widening changes, no supply-chain risk from loosened or unpinned action pins.

The effective attack surface of this diff is zero.

kyle-sexton and others added 3 commits July 21, 2026 13:33
#840 rebased since this branch's last check and re-derived its own claim
from 0.15.9 to 0.15.10, so this PR's prior 0.15.10 now collides exactly.
Bump to 0.15.11 (one past #840's current live claim). claude-ops (0.17.3),
work-items (0.19.1), repo-hygiene (0.4.6), and guardrails (0.9.6) were
independently re-verified against current main and all live open PRs and
remain correct — no change needed for those four.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
#857 (Jira Cloud adapter) merged into main since this branch's last rebase,
bumping work-items to 0.19.0. Resolved the resulting conflict, keeping this
branch's 0.19.1 (already one past both the new main and #861's still-open
0.19.0 claim, so no further bump needed). Re-verified all 5 plugins fresh
against current main and every live open PR (#840 0.15.10, #844 0.17.2, #861
0.19.0): source-control 0.15.11, claude-ops 0.17.3, repo-hygiene 0.4.6, and
guardrails 0.9.6 all remain correct with no change required.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
An unrelated claude-ops docs PR (#877, closing #864) merged since this
branch's last rebase and landed at exactly 0.17.3 — the same version this
branch was claiming. Re-bumped claude-ops to 0.17.4 to clear the exact
collision (plugin.json auto-merged to an identical-looking "0.17.3" string
without flagging a conflict, since both sides happened to write the same
literal text for unrelated reasons; caught by diffing main's version history
directly, not by trusting a clean auto-merge). CHANGELOG.md split into two
sections (0.17.3 for #877's actual change, 0.17.4 for this PR's).

Also swept for new open PRs against all 5 plugins post-merge: #870
(feat/800-audit-instructions) newly touches repo-hygiene (claims 0.4.5) and
claude-ops (claims 0.17.2) — both below this branch's current claims
(0.4.6 / 0.17.4), no collision. source-control (0.15.11), work-items
(0.19.1), and guardrails (0.9.6) re-confirmed against current main and every
live open PR (#840 0.15.10, #844 0.17.2, #861 0.19.0) — unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 12s —— View job


PR #853 Review (post-sync)

  • Gather context
  • Check what changed since last review (e96e9a6)
  • Review current diff and version arithmetic
  • Post findings

What's new since the last review

Two new commits since e96e9a6:


Notable: silent auto-merge collision on claude-ops

#877 merged onto main at exactly 0.17.3 — the same version this branch was claiming. Git auto-merged the plugin.json cleanly (both sides wrote the identical string for unrelated reasons, so no conflict was flagged). The commit message documents that the collision was caught by diffing main's version history directly rather than trusting the clean merge.

The fix is correct: claude-ops bumped to 0.17.4, and the CHANGELOG was split into two entries — [0.17.3] for #877's actual change (now already on main, preserved verbatim in the CHANGELOG body) and [0.17.4] for this PR's pointer comment. This is the cleanest possible handling of this scenario — no version gap, each change attributed to the right entry.

This catch was non-trivial: without the manual diff, a clean merge would have silently shipped two unrelated changes under a single version number. Worth flagging as a process observation, not a defect: the "bump past highest claim" strategy is necessary but not sufficient when a sibling lands at exactly your claimed version — the merge itself won't warn you.


Version arithmetic at HEAD

Plugin main Highest open claim This PR Math
claude-ops 0.17.3 (post-#877) #844 → 0.17.2 (below main) 0.17.4 main+1 ✓
source-control 0.15.9 #840 → 0.15.10 0.15.11 max+1 ✓
work-items 0.19.0 (post-#857) #861 → 0.19.0 (matches main) 0.19.1 max+1 ✓
repo-hygiene 0.4.5 #870 → 0.4.5 (matches main) 0.4.6 max+1 ✓
guardrails 0.9.5 none 0.9.6 main+1 ✓

All correct.


CHANGELOG structure


Do-not-merge holds (updated)

Plugin Live holds
claude-ops #844 (0.17.2 — needs to rebase above main's 0.17.3 before merging)
source-control #840 (0.15.10)
work-items #861 (0.19.0 — matches main; must resolve before this PR lands at 0.19.1)
repo-hygiene #870 (0.4.5 — matches main; same situation)
guardrails none

Before removing do-not-merge: confirm #840, #844, #861, and #870 are each merged or withdrawn (not just that they haven't conflicted in git — the version slots must also be settled).


Documentation and shell files

Unchanged since ae5c5aa. All prior findings hold: three-shape taxonomy accurate, pointer comments consistent across all six shell files, relative paths correct, minor Conformance-section nit unchanged.


Verdict

Approve. The two new commits are correct and handle a genuinely tricky edge case (silent same-string auto-merge) well. Version arithmetic is clean across all five plugins. The CHANGELOG split for claude-ops is the best outcome possible given the collision. No correctness issues, no security concerns, no behavior changes anywhere in the diff.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 10s —— View job


Security Review — PR #853 (post-sync)

  • Gather context
  • Fetch PR diff and read changed files
  • Audit shell script modifications
  • Audit new markdown doc and JSON changes
  • Post findings

No security issues found.

All 18 changed files were read and audited against the HEAD commit (93294e6). Full breakdown by category:

Shell scripts (6 files modified): Each change adds 3–4 lines of #-prefixed comments — static prose pointing to an internal doc path. Shell comments are never executed. The added text contains no command substitutions, variable expansions, heredoc terminators, escape sequences, or control characters that could influence shell parsing. The pre-existing # shellcheck disable=SC2016 directive in check-skill-portability.test.sh remains correctly positioned immediately before set -uo pipefail after the insertion.

New markdown doc (docs/conventions/shell-test-helpers/README.md): 72 lines of prose and relative markdown links. Every link resolves to a path within this repo — no external URLs appear anywhere in the file. No executable content, no credentials, no template expressions, no CDN or remote resource references.

docs/PLUGIN-PHILOSOPHY.md: One additional table row with a single relative internal link. Same assessment.

plugin.json files (5 files): Each modifies only the version string field. The version numbers differ from what prior reviews reported — source-control now claims 0.15.11 (skipping 0.15.10) and claude-ops claims 0.17.4 (past the previously-reviewed 0.17.3) — both explained by additional sibling PRs merging between sync cycles. No new or modified permissions, dependencies, mcpServers, hooks, allowedTools, remote URLs, or other capability-granting fields. All five manifests were read in full; no egress surface changes.

CHANGELOG.md files (5 files): Plain-text changelog entries. No executable content.

GitHub Actions: No .github/workflows/ files are modified in this PR. No pull_request_target/workflow_run patterns, no script injection through the github context, no permission-widening changes, no supply-chain risk from loosened or unpinned action pins.

The effective attack surface of this diff is zero.

kyle-sexton added a commit that referenced this pull request Jul 21, 2026
Resolves the claude-ops CHANGELOG.md conflict by keeping #860's
0.17.2 entry as-is and moving this PR's fix under a new 0.17.4
heading. Re-bumps plugin.json past both main's 0.17.2 (landed via
#860) and PR #853's pending 0.17.3 claim.
claude-ops: #844 rebased and re-derived its claim from 0.17.2 to 0.17.4,
colliding exactly with this branch's prior 0.17.4. Bump to 0.17.5.

source-control: a new PR, #882 (fix/511-babysit-self-identity-decouple),
claims 0.16.0 — a minor bump above this branch's prior 0.15.11 and above
#840's 0.15.10. Bump to 0.16.1 to stay ahead of both.

repo-hygiene (0.4.6), guardrails (0.9.6), and work-items (0.19.1) were
re-verified fresh against current main and every live open PR
(main: repo-hygiene 0.4.5, guardrails 0.9.5, work-items 0.19.0; #861 0.19.0)
and remain correct, no change needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 39s —— View job


PR #853 Review (post-sync)

  • Gather context
  • Check what changed since last review (93294e6)
  • Review current diff and version arithmetic
  • Post findings

What's new since the last review

One new commit: 7fd0258 fix: re-bump claude-ops and source-control past new live collisions

Two plugins were affected — claude-ops and source-control. Four files changed (two plugin.json, two CHANGELOG.md). No other files touched.

claude-ops: #844 re-derived after #877 merged and moved its version claim from 0.17.2 to 0.17.4. That collided with this branch's prior 0.17.4 claim, so this PR bumped to 0.17.5. The CHANGELOG attribution was updated to note #844's current claim, not the stale pre-rebase one.

source-control: A newly opened #882 claims 0.16.0, which jumped past this branch's prior 0.15.11 claim. This PR responded by claiming 0.16.1 — the first value above the new highest claimant.


Version arithmetic at HEAD

Plugin main Highest open claim This PR Math
claude-ops 0.17.3 #844 → 0.17.4 0.17.5 max + 1 ✓
source-control 0.15.9 #882 → 0.16.0, #840 → 0.15.10 0.16.1 max(0.16.0) + 1 ✓
work-items 0.19.0 #861 → 0.19.0 0.19.1 max + 1 ✓
repo-hygiene 0.4.5 (none noted) 0.4.6 main + 1 ✓
guardrails 0.9.5 (none) 0.9.6 main + 1 ✓

All correct.


Notable: second minor-version jump in the series

source-control now claims 0.16.1 for a docs-only patch change — a minor version bump forced by #882's 0.16.0 claim. This is the same pattern as work-items earlier in this PR's history (0.18.x → 0.19.x when #857 held a minor slot). The CHANGELOG entry documents it explicitly. Accepted cost of the collision-avoidance strategy; not a defect.

The source-control CHANGELOG now skips from [0.16.1] directly to [0.15.9] — a gap of three slots (0.15.10, 0.15.11, 0.16.0), the largest gap in the series so far. These will be filled by the sibling PRs when they land. The entry body explains the jump, consistent with the established pattern.


CHANGELOG attribution accuracy

  • claude-ops [0.17.5]: Credits #844's current 0.17.4 claim (re-derived after #877 merged). Correct — the prior version note was stale; this update accurately reflects the live reason.
  • source-control [0.16.1]: Credits #882's open 0.16.0 claim and notes it supersedes #840's 0.15.10. Correct — both blockers named, arithmetic justified.

Do-not-merge holds (updated)

Plugin Live holds
source-control #840 (0.15.10) and #882 (0.16.0)
claude-ops #844 (0.17.4)
work-items #861 (0.19.0)
repo-hygiene none (last verified at prior sync; re-check)
guardrails none

Before removing do-not-merge, confirm #840, #844, #861, and #882 are each merged or withdrawn — and verify repo-hygiene hasn't picked up a new collision, given how fast this set is moving.


Documentation and shell files

Unchanged since ae5c5aa. All prior review findings hold: three-shape taxonomy accurate, pointer comments consistent across all six shell files, relative paths correct, the minor Conformance-section nit (check-skill-portability.test.sh pointer is noted in prose but not listed in the Conformance table) remains non-blocking.


Verdict

Approve. The fix commit is correct and minimal — four files, version math checks out, CHANGELOG attribution updated to match the live collision state. The source-control minor-version jump is documented and expected. No correctness issues, no security concerns, no behavior changes anywhere in the diff.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 23s —— View job


Security Review — PR #853

  • Gather context
  • Fetch PR diff and read changed files
  • Audit shell script modifications for injection or unsafe patterns
  • Audit new markdown doc and JSON changes
  • Post findings

No security issues found.

All 18 changed files were read and audited against HEAD (7fd0258). The latest commit (fix: re-bump claude-ops and source-control past new live collisions) is version-number-only changes. Full breakdown by category:

Shell scripts (6 files modified): Each change adds 3–4 lines of #-prefixed comments — static prose pointing to an internal doc path. Shell comments are never executed. The added text contains no command substitutions, variable expansions, heredoc terminators, escape sequences, or control characters that could influence shell parsing. The pre-existing make_sink body in guardrails-test-helpers.sh (line 68–78, which writes arbitrary caller-supplied content as a temporary executable) and in claude-ops-test-helpers.sh (line 51–61, which uses %q to shell-quote a path into a generated cat > redirect) are unchanged — this PR's changes to those files are the pointer comment block only. The # shellcheck disable=SC2016 directive in check-skill-portability.test.sh is correctly preserved immediately before set -uo pipefail after the insertion.

New markdown doc (docs/conventions/shell-test-helpers/README.md): 72 lines of prose and relative markdown links. All links resolve to paths within this repo — no external URLs anywhere in the file. No executable content, no credentials, no template expressions, no CDN or remote resource references.

docs/PLUGIN-PHILOSOPHY.md: One additional table row with a single relative internal link. Same assessment.

plugin.json files (5 files): Each modifies only the version string field — claude-ops now at 0.17.5, source-control at 0.16.1 after the latest re-bump. No new or modified permissions, dependencies, mcpServers, hooks, allowedTools, remote URLs, or other capability-granting fields. Both manifests were read in full; no egress surface changes.

CHANGELOG.md files (5 files): Plain-text changelog entries. No executable content.

GitHub Actions: No .github/workflows/ files are modified in this PR. No pull_request_target/workflow_run patterns, no script injection through the github context, no permission-widening changes, no supply-chain risk from loosened or unpinned action pins.

The effective attack surface of this diff is zero.

#844 (claude-ops) merged into main at exactly 0.17.4, which this branch's
prior 0.17.5 was already one past — kept as-is, no re-bump needed for the
number itself, just resolved the resulting plugin.json/CHANGELOG.md conflict.

Full fresh collision sweep after the merge found claude-ops now has NO open-PR
collision at all. It also found #861 (work-items) re-derived its own claim
from 0.19.0 to 0.20.0 since the last check (following #857's Jira-adapter
minor bump into main) — colliding with this branch's prior 0.19.1. Re-bumped
work-items to 0.20.1.

source-control (0.16.1, held behind #882's 0.16.0 and #840's 0.15.10),
repo-hygiene (0.4.6, no collision), and guardrails (0.9.6, no collision)
re-verified against current main and all live open PRs — unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
kyle-sexton added a commit that referenced this pull request Jul 22, 2026
…th live labels (#922)

## Summary

`plugins/work-items/skills/triage/SKILL.md` had two docs-match-reality
gaps against shipped state:

- The "Attention view (no number)" bucket list named only the
status-axis raw marker (`status:needs-triage`), leaving a repo that
files raw intake on the priority axis (`priority:needs-triage`, per PR
#802's already-merged dual-axis Scope wording) invisible to the no-arg
attention view.
- `status:needs-decision` is a live, actively-used label (e.g. issue
#505, per CHANGELOG `[0.16.1]`/`#562`), and the closing invariant
already referenced it as a routing outcome that clears the raw marker —
but the state machine itself never introduced it as a side exit, unlike
`needs-info`, human-gated, and close.

## Fix

- Attention-view bucket 2 now reads "Raw marker — `status:needs-triage`
/ `priority:needs-triage`, whichever axis the repo files it under",
mirroring the Scope section's exact dual-axis wording (SKILL.md line
35).
- The "Side exits from any state" sentence and the state-machine ASCII
diagram now name `status:needs-decision` ("awaiting a human or
maintainer judgment call" — the label's live GitHub description)
alongside the existing side exits.
- Version-bumped `work-items` 0.21.0 → 0.21.1 (patch, matching the
`#562` precedent for the same doc-only shape) with a matching CHANGELOG
entry.

Scope note: the raw-state table row (line 48, `unlabeled or
status:needs-triage`) is intentionally left single-axis — broader
raw-marker axis canonicalization across that table, evals, and reference
docs is issue #818's job, not this one. Also intentionally out of scope:
adding `needs-decision` to step 2's "Target state" list or step 5's
outcome table — doing so would require defining when triage applies it
vs. the human-gated role, which the label's live usage does not settle
(it appears both with and without `needs-human` across open issues) and
which this issue scoped as pure docs-match-reality, not a design
decision.

## Verification

- Confirmed PR #802's merged Scope-section wording verbatim (`gh pr view
802`): "carrying the raw marker (`status:needs-triage` /
`priority:needs-triage`, whichever axis the repo files it under)" — the
attention-view bucket now mirrors this exactly.
- Confirmed `status:needs-decision` is a live label via `gh issue list
--label "status: needs-decision"` (21+ open/closed issues, e.g. #881,
#718, #664, #551) and confirmed its GitHub description ("Awaiting a
human or maintainer judgment call") used verbatim in the new SKILL.md
text.
- Confirmed issue #505's prior routing to `status:needs-decision` via
the plugin's own CHANGELOG `[0.16.1]` entry (`#562`), which already
treated `needs-decision` as a routing outcome in the closing invariant —
this PR closes the gap between that reference and the state machine's
own introduction of the label.
- Traced the ASCII diagram's pipe/branch alignment by hand to confirm
the new `status:needs-decision` line reads correctly as a side exit at
the same indentation level as `close`.

Closes #817

## Related

- #817 (this fix)
- #802 (source of the dual-axis raw-marker wording mirrored here)
- #505 (live example of `status:needs-decision` usage; see also
CHANGELOG `#562`)
- #818 — separate, unopened-PR item covering raw-marker axis
canonicalization across the state table, evals, and reference docs;
deliberately not touched here to avoid overlap. Re-checked immediately
before opening this PR: no PR exists yet for #818.
- #853 — open draft PR (labeled `do-not-merge`, stale at plugin version
0.19.0) also touches `plugins/work-items/.claude-plugin/plugin.json` and
`CHANGELOG.md` for an unrelated shell-test-helper doc change. Re-checked
immediately before opening this PR: no version-bump collision — #853 is
stale relative to current `main` (0.21.0) and will need to rebase
regardless of this PR.

Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 23, 2026
…triage docs + evals (#923)

## Summary

The live raw-intake marker is applied on whichever axis a consuming repo
files it under — `priority: needs-triage` (the current live label,
applied by `.github/workflows/issue-triage-label.yml`) or `status:
needs-triage`. `SKILL.md`'s "Scope: raw intake only" section already
documents this dual-axis treatment (landed in #802), but several other
spots in the same skill, its evals, and reference docs still described
or asserted the marker as status-axis-only — a priority-axis label
documented as status-only.

## Fix

Brought every remaining occurrence in line with #802's already-merged
dual-axis wording (`status:needs-triage` / `priority:needs-triage`,
whichever axis the repo files it under):

- `plugins/work-items/skills/triage/SKILL.md` — Triage-states table
(`raw` row) and Attention-view bucket 2, both previously
`status:needs-triage` only.
- `plugins/work-items/reference/dogfood-filing.md` — step 4 ("Label
`needs-triage`, then hand off") previously instructed filers to always
`Apply status:needs-triage`; now applies the raw marker on whichever
axis the repo files it under, resolved from the live label set.
- `plugins/work-items/reference/label-taxonomy.md` and
`plugins/work-items/reference/tracker-seam.md` — the axis-grammar tables
listed `needs-triage` as a Status-axis example only; added a note that
it is dual-axis with a pointer to SKILL.md's "Scope: raw intake only".
- `plugins/work-items/skills/triage/evals/evals.json` — evals 4 and 5
asserted "clears `status:needs-triage`" without acknowledging the
marker's dual-axis nature; reworded to "clears the raw-intake marker
(here `status:needs-triage`)" so the assertion doesn't imply that's the
only valid axis.
- Version bump `work-items` `0.21.0` → `0.21.1` + matching
`CHANGELOG.md` entry.

No routing logic changed — this is a doc/eval consistency fix matching
an already-ratified decision.

## Verification

- `skill-quality:check` static contract gate (18 checks) over the
`triage` skill: **PASS — 0 errors, 1 warning** (pre-existing "no Gotchas
surface" warning, unrelated to this change). All 9 base-ref trigger
phrases preserved; markdownlint clean.
- `check-jsonschema` against the bundled `evals.schema.json`: `ok --
validation done`.
- Manually cross-checked every edit against PR #802's diff to confirm
the dual-axis phrasing (`status:needs-triage` / `priority:needs-triage`,
whichever axis the repo files it under) is reused verbatim, not
reinvented.
- Confirmed the live label is priority-axis via
`.github/workflows/issue-triage-label.yml` ("Applies the `priority:
needs-triage` floor label...").

Closes #818

## Related

- #818 — this fix. The issue body already carries a ratified/defaulted
decision (dual-axis treatment, maintainer-vetoable alternative was
single-canonical-axis migration — not implemented here per the issue's
explicit instruction), satisfying rule 4d's citation requirement.
- #802 — the ratifying source for the dual-axis wording; this PR's
phrasing is copied verbatim from its already-merged "Scope: raw intake
only" section.
- **Version-bump collision with PR #922 (issue #817):** #922 also bumps
`work-items` `0.21.0` → `0.21.1` and touches `SKILL.md`'s
state-machine/attention-view sections — the same lines this PR's
dual-axis fix touches, since the marker lives in those sections. Both
PRs are currently open against the same `0.21.0` base. Opening this PR
as **draft + `do-not-merge`** until the version numbers and any
overlapping `SKILL.md` lines are reconciled by whoever merges second
(normal git-merge conflict resolution, per the collision-serialization
protocol).
- PR #853 (issue #820, unrelated) also touches `work-items/plugin.json`
and `CHANGELOG.md` with a stale bump (`0.19.0` → `0.20.1`, already
behind current `origin/main`) — noted for visibility, not a live
collision with this change.

Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
@kyle-sexton kyle-sexton removed the do-not-merge Hard merge gate: do not merge while applied. label Jul 23, 2026
@kyle-sexton
kyle-sexton marked this pull request as ready for review July 23, 2026 07:38
kyle-sexton added a commit that referenced this pull request Jul 23, 2026
… FLEET_STATE_HOOK_UTILS source footgun (#907)

## Summary

`fleet-state.sh` sourced `hook-utils.sh` from a caller-supplied
`FLEET_STATE_HOOK_UTILS` path, guarded only by an existence check
(`[[ -f "$HOOK_UTILS" ]]`). Any process able to set that env var — a
project
`.claude/settings.json` env block, an inherited shell environment,
another hook
that exports it — could redirect `source` at an arbitrary file, executed
with
the script's ambient permissions. The guard checked only that the path
existed,
never that it was the trusted default.

This removes the override entirely and loads `hook-utils.sh`
unconditionally
from the script-relative plugin root.

## Fix

`hook-utils.sh` is a fixed sibling shipped inside the plugin, always at
`<plugin-root>/hooks/hook-utils.sh` relative to the script. The fix
resolves it
from the script's own location (`PLUGIN_ROOT_DEFAULT`, derived from
`BASH_SOURCE`), which no environment variable can influence:

```bash
HOOK_UTILS="$PLUGIN_ROOT_DEFAULT/hooks/hook-utils.sh"
```

`CLAUDE_PLUGIN_ROOT` is no longer consulted for this sibling file (it
equals the
script-relative root in production, and tests set it to a *fake* value
to
exercise marketplace self-resolution). It remains in use for marketplace
self-resolution further down the script — that concern legitimately
needs the
install-metadata env var; locating a shipped sibling file does not.

### Options considered

The issue proposed two remediations; I document both and why Option A
wins:

- **Option 1 — gate the override behind a test-context flag.** No such
flag
  exists in this repo's convention; the test harness sets the individual
`FLEET_STATE_*` override vars directly. Adding one would be a new
abstraction
  invented solely to keep an override alive.
- **Option 2 — validate the sourced path against an expected location
before
sourcing.** Once the trusted baseline is the script-relative default, an
override is honored *only* when it canonically equals that default. Both
  existing test call sites already resolved to exactly that file, and an
attacker path is rejected — so both branches source the identical file
and the
  override cannot even inject a mock. The seam is provably dead code.
- **Option A — remove the override (chosen).** Since a validated
override could
only ever equal the trusted default, the override carries no capability
worth
keeping. Removing it is the root-cause fix: it fully closes the
arbitrary-file
`source` with no new abstraction and no dead seam. The three
now-redundant
`FLEET_STATE_HOOK_UTILS=` lines in the test harness (which already
pointed at
  the real file) and the doc-comment entry were removed with it.

No behavior change in production: `PLUGIN_ROOT_DEFAULT` and
`CLAUDE_PLUGIN_ROOT`
resolve to the same directory there, so the same real `hook-utils.sh`
loads.

## Verification

**Existing test suite (`fleet-state.test.sh`) — full pass on the
committed code:**

```
23 cases, 0 failed
```

(Later re-runs on this Windows host intermittently exceed the command
timeout on
the `git init` and binary-copy cases from AV/disk contention; that is
environmental, not a test failure — the suite completes green on the
exact
committed tree.)

**Exploit path closed — malicious override in a non-test context:**

```
=== BEFORE (origin/main) — malicious override in a NON-test context ===
PWNED: attacker code executed via FLEET_STATE_HOOK_UTILS
=== AFTER (fixed) — same malicious override ===
(no PWNED — override ignored, exploit closed)
```

The repro sets `FLEET_STATE_HOOK_UTILS` to a script that echoes a
marker, then
runs `fleet-state.sh --marketplace nope` with no test env. Before: the
attacker
file is sourced. After: the override is ignored and the trusted sibling
loads.

Closes #797

## Related

- #797 — this issue.
- #795 (`fix/254-fleet-state-review-gaps`) — the security-review pass
that
surfaced this as a pre-existing, out-of-scope finding and deferred it
here.
- #853 (`docs/820-shell-helper-dedup-or-document`) — open draft
(do-not-merge)
  that also bumps `plugins/claude-ops/.claude-plugin/plugin.json` +
`CHANGELOG.md` (no code overlap with this PR). This PR is provisionally
parked
behind #853's unresolved cross-plugin version/CHANGELOG bump: a
merge-drive
  must re-diff the version claim before either PR flips ready. Carrying
  `do-not-merge` until then.

Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o

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

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@kyle-sexton kyle-sexton added the agent-ready Fully specified and briefed; eligible for autonomous pickup from the frontier. label Jul 23, 2026
# Conflicts:
#	docs/PLUGIN-PHILOSOPHY.md
#	plugins/claude-ops/.claude-plugin/plugin.json
#	plugins/claude-ops/CHANGELOG.md
#	plugins/guardrails/.claude-plugin/plugin.json
#	plugins/guardrails/CHANGELOG.md
#	plugins/repo-hygiene/.claude-plugin/plugin.json
#	plugins/repo-hygiene/CHANGELOG.md
#	plugins/source-control/.claude-plugin/plugin.json
#	plugins/source-control/CHANGELOG.md
#	plugins/work-items/.claude-plugin/plugin.json
#	plugins/work-items/CHANGELOG.md
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 7s —— View job


Security Review — PR #853

  • Fetch PR diff and enumerate changed files
  • Read changed shell scripts in full
  • Audit new markdown doc and JSON changes
  • Post findings

No security issues found.

All 18 changed files were read and audited against HEAD (776dda6). This PR is documentation and comments only. Full category breakdown:

Shell scripts (6 files modified): Each change inserts 3–4 lines of #-prefixed comments pointing to an internal doc path. Shell comments are never executed. The added text contains no command substitutions, variable expansions, heredoc terminators, escape sequences, or control characters that could influence shell parsing.

The two files with the most security-sensitive pre-existing bodies were read in full to confirm the change boundary:

  • guardrails-test-helpers.sh — The make_sink body (lines 73–83) writes caller-supplied shell content as a temporary executable under $TEST_TMPDIR. That body is unchanged by this PR; the only addition is the two-line pointer comment at lines 7–8.
  • claude-ops-test-helpers.sh — The make_sink body (lines 51–61) uses %q-formatted printf to shell-quote a capture-file path into a generated cat > redirect. Also unchanged; only the pointer comment at lines 7–8 is new.

The # shellcheck disable=SC2016 directive in check-skill-portability.test.sh remains correctly positioned on line 12, immediately before set -uo pipefail on line 13 — the new comment block at lines 9–11 does not disturb the directive's position or effectiveness.

New markdown doc (docs/conventions/shell-test-helpers/README.md): 72 lines of prose and relative markdown links. All links resolve to paths within this repo — no external URLs in the file. No executable content, no credentials, no template expressions, no CDN or remote resource references.

docs/PLUGIN-PHILOSOPHY.md: One additional table row with a single relative internal link. Same assessment.

plugin.json files (5 files): Each modifies only the version string field. No new or modified permissions, dependencies, mcpServers, hooks, allowedTools, remote URLs, or other capability-granting fields. All five manifests were read in full. The version numbers at HEAD (guardrails 0.14.2→0.14.3, claude-ops 0.19.1→0.19.2, repo-hygiene 0.7.0→0.7.1, source-control 0.26.0→0.26.1, work-items 0.24.0→0.24.1) differ from the PR description's stale snapshot — expected after the many rebases documented in the PR body — and carry no security implication.

CHANGELOG.md files (5 files): Plain-text changelog entries. No executable content.

GitHub Actions: No .github/workflows/ files are modified in this PR. No pull_request_target/workflow_run patterns, no script injection through the github context, no permission-widening changes, no supply-chain risk from loosened or unpinned action pins.

The effective attack surface of this diff is zero.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 56s —— View job


PR #853 Review (post-sync)

  • Gather context
  • Check what changed since last review (24a1605)
  • Review current diff and version arithmetic
  • Post findings

What's new since the last review

One new commit: 776dda6 Merge remote-tracking branch 'origin/main' into HEAD

This is the most consequential sync this branch has had. origin/main advanced by a large batch — including all of the sibling PRs that were previously blocking this one (#840, #844, #857, #861, #870, #882, #895, #898, and more). Conflicts were resolved on all five plugin plugin.json / CHANGELOG.md pairs plus docs/PLUGIN-PHILOSOPHY.md. Unrelated changes on main (new plugins: go-format, visualization, rate-limit-guard, context-guard, plugin-quality, dometrain; rename of re-anchordiscipline; CI/runner policy updates) came along for the ride but do not touch any of the 18 files in this PR's diff.


Version arithmetic at HEAD — the cleanest state this PR has produced

Every claim is now a simple single-patch increment above main, with no collision notes and no version gaps:

Plugin origin/main This PR Math
claude-ops 0.19.1 0.19.2 main + 1 ✓
guardrails 0.14.2 0.14.3 main + 1 ✓
repo-hygiene 0.7.0 0.7.1 main + 1 ✓
source-control 0.26.0 0.26.1 main + 1 ✓
work-items 0.24.0 0.24.1 main + 1 ✓

All previous minor-version jumps (work-items 0.20.1, source-control 0.17.1, and all the intermediate "bumped past" versions) have been absorbed by the sibling PRs landing on main. The CHANGELOG entries are correspondingly clean — five straight [x.y.z+1] ### Documentation entries with no "Version bumped past..." notes, because there are no collisions to document.


CHANGELOG entries

All five are now in the cleanest form they have been at any point in this PR's history:

  • claude-ops: [0.19.2][0.19.1] — no gap ✓
  • guardrails: [0.14.3][0.14.2] — no gap ✓
  • repo-hygiene: [0.7.1][0.7.0] — no gap ✓
  • source-control: [0.26.1][0.26.0] — no gap ✓
  • work-items: [0.24.1][0.24.0] — no gap ✓

Each entry body is consistent: one bullet naming the modified helper file, one sentence explaining the owner doc, and a "No behavior change" note. Pattern is uniform across all five.


Documentation and shell files

Unchanged since ae5c5aa. All prior review findings hold:

  • docs/conventions/shell-test-helpers/README.md — three-shape taxonomy accurate, "Deferred, not rejected" trigger precise, "Why not the existing vendoring mechanism" section correct.
  • Pointer comments consistent across all six shell files (five plugin helpers + check-skill-portability.test.sh).
  • Relative paths in the README correct from docs/conventions/shell-test-helpers/.
  • The minor Conformance-section nit (that check-skill-portability.test.sh is noted in prose but not listed under the Conformance bullet) remains present and remains non-blocking.

do-not-merge holds (updated)

The PR body's collision table is now significantly stale — most of the named blockers have merged into main. Based on the current version arithmetic (all five claims are simple main+1 with no competing open-PR versions visible), this PR may be in a clear state. However, the PR body's standing warning is worth repeating verbatim: re-run the full collision protocol immediately before removing do-not-merge — new collisions have appeared within minutes in prior cycles. A fresh gh pr list scan per plugin is the gate, not this snapshot.


Verdict

Approve. This is the cleanest sync this PR has had across its entire history. Version arithmetic is correct and gap-free for all five plugins. CHANGELOG entries are clean with no collision notes needed. Documentation and shell files are unchanged and correct. The only gate before unlabeling is the fresh live-collision check the PR body mandates.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 776dda6ead

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/conventions/shell-test-helpers/README.md Outdated
…tion

remove-path.sh returns 1 both when the target is already absent and when an
--apply leaves it present, so summarizing exit 1 as an existence check alone
understated the operational failure the taxonomy is cited to justify.

The guardrails and claude-ops make_sink helpers take different arguments (a
stub body vs a capture file), so they are not near-identical bodies.
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 2s —— View job


PR #853 Review (post-sync: b2e17c8)

  • Gather context
  • Check what changed since last review (776dda6)
  • Verify corrected documentation claims against source files
  • Post findings

What's new since the last review

One new commit: b2e17c8 docs: correct exit-1 and make_sink claims in shell-test-helper convention

One file changed: docs/conventions/shell-test-helpers/README.md — 6 insertions, 2 deletions. No plugin files, version bumps, or CHANGELOG entries were touched.

Both changes directly address the Codex review comment flagging the exit-1 characterization.


Correction 1 — exit-1 dual semantics in remove-path.sh

Old text: 0/1/2/3/4: usage and existence checks plus two named blocking conditions (blocked, unpushed).

New text (lines 48–51): adds that 1 carries both meanings — target already absent, and an --apply that left the target present.

Verified accurate. Two exit-1 sites in remove-path.sh at the current HEAD:

  • Line 148: exit 1 when [[ ! -d "$TARGET" ]] — target was already absent.
  • Line 381: exit 1 when [[ -e "$TARGET_ABS" ]] after rm — removal incomplete, "locked / in use / crosses a mount".

The correction is precise. The "so the caller retries or reports rather than treating exit 1 as 'nothing to do'" note correctly captures why the dual meaning matters to callers: the two cases demand different responses.


Correction 2 — make_sink contract difference in the "Deferred, not rejected" section

Old text: "both hook-contract helpers with near-identical ok/bad/make_sink bodies"

New text (lines 65–67): "near-identical ok/bad bodies; their make_sink differs in contract — guardrails' takes a stub body, claude-ops' takes a capture file"

Verified accurate against both files:

  • guardrails-test-helpers.sh line 73: make_sink <body> — writes caller-supplied shell body directly into a temp executable (printf '%s\n' "$1"). Generates an arbitrary stub.
  • claude-ops-test-helpers.sh line 51: make_sink <envelope-capture-file> — generates a cat >%q redirect to a specific capture path. Produces one hard-coded behavior.

This correction also tightens the "Deferred, not rejected" trigger condition: the consolidation trigger now correctly applies to ok/bad bodies only, acknowledging make_sink as a remaining divergence that would need its own reconciliation. This makes the trigger condition more actionable — byte-identical convergence of the full files is now correctly excluded as the test.


No version bumps needed

Only docs/conventions/shell-test-helpers/README.md changed — a docs-level file outside every plugin directory. Plugin version bumps are not warranted. This is consistent with how the PR has handled documentation-only changes at the repo level throughout its history.


Carry-forward from prior reviews

The minor Conformance-section gap noted since the first review (that scripts/check-skill-portability.test.sh receives a pointer comment but is not listed in the Conformance bullet's copy-site enumeration) remains present and non-blocking — the opt-out rationale appears in the preceding prose.


Verdict

Approve. Both corrections in this commit are precise and verified against source. The exit-1 fix directly addresses the Codex reviewer's finding; the make_sink fix improves the accuracy of the "Deferred, not rejected" trigger condition. No version bumps were needed or included. No correctness issues, no security concerns, no behavior changes.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 38s —— View job


Security Review — PR #853

  • Gather context
  • Fetch PR diff (HEAD: b2e17c8)
  • Read changed shell files in full
  • Audit new markdown doc and JSON changes
  • Post findings

No security issues found.

All 18 changed files were audited against HEAD (b2e17c8). The diff was fetched and read line-by-line against the merge base. This PR is documentation and comments only.

Shell scripts (6 files modified): Each change inserts 3–4 lines of #-prefixed comments pointing to an internal doc path. Shell comments are never executed. The added text in every file contains no command substitutions, variable expansions, heredoc terminators, escape sequences, or control characters that could influence shell parsing.

The two files with the most security-sensitive pre-existing bodies were read in full:

  • guardrails-test-helpers.sh — The make_sink body (lines 73–83) writes caller-supplied shell content as a temporary executable under $TEST_TMPDIR. That body is unchanged by this PR; the only addition is the two-line pointer comment at lines 7–8.
  • claude-ops-test-helpers.sh — The make_sink body (lines 51–61) uses %q-formatted printf to shell-quote a capture-file path into a generated cat > redirect. Also unchanged; only the pointer comment at lines 7–8 is new.

The # shellcheck disable=SC2016 directive in check-skill-portability.test.sh is correctly preserved on line 12, immediately before set -uo pipefail on line 13 — the inserted comment block at lines 9–11 does not disturb the directive's position or effectiveness.

New markdown doc (docs/conventions/shell-test-helpers/README.md): 76 lines of prose and relative markdown links. All links resolve to paths within this repo — no external URLs in the file. No executable content, no credentials, no template expressions, no CDN or remote resource references.

docs/PLUGIN-PHILOSOPHY.md: One additional table row with a single relative internal link. Same assessment.

plugin.json files (5 files): Each modifies only the version string field. No new or modified permissions, dependencies, mcpServers, hooks, allowedTools, remote URLs, or other capability-granting fields. All five manifests were read in full; no egress surface changes.

CHANGELOG.md files (5 files): Plain-text changelog entries. No executable content.

GitHub Actions: No .github/workflows/ files are modified in this PR. No pull_request_target/workflow_run patterns, no script injection through the github context, no permission-widening changes, no supply-chain risk from loosened or unpinned action pins.

The effective attack surface of this diff is zero.

@kyle-sexton
kyle-sexton merged commit 481cd29 into main Jul 25, 2026
26 checks passed
@kyle-sexton
kyle-sexton deleted the docs/820-shell-helper-dedup-or-document branch July 25, 2026 09:41
kyle-sexton added a commit that referenced this pull request Jul 25, 2026
Closes #1270

Supersedes #1284 — same work, rebased onto `main` after #853 landed, and
narrowed from two guards to one on measurement. #1284's branch could not
be force-pushed, so this is a fresh branch; its review history is worth
reading, since all 13 findings there were real.

## Summary

One advisory `PostToolUse` guard on `Write|Edit`:
**`skill-reference-verify`** flags a `` `/plugin:skill` `` reference in
markdown that does not resolve.

Declared **detect-then-judge**, not deterministic. Globbing a plugins
tree is exact only where the reference is locally owned — in a consuming
repo it may name a plugin from another marketplace, or one simply not
installed. Per `conventions/engineering/enforceability-tiers.md` that
means advisory plus a human verdict, never an auto-fix.

Gated twice so the oracle only runs where it is meaningful: inert
outside a marketplace repo, and within one it adjudicates only a plugin
that repo's own manifests own. Resolution goes through manifest `name`
**and** skill frontmatter `name`. A renamed skill's *directory* name is
deliberately **not** an alias — treating it as one would suppress
exactly the stale pre-rename references this guard exists to catch. The
reference is the leading command token of a code span, so
argument-bearing invocations (`/plugin:skill --apply`) are scanned.

Follows `cli-flag-verify` exactly: diff-scope only (scan what the call
wrote, never re-read from disk), kill switch defaulting true,
`statusMessage`, telemetry with repo-relative path redaction,
`lib/hook-utils.sh` untouched.

**Boy Scout:** README counts were stale before this change — prose said
"nine safety guards" while ten were wired and the table omitted
`block-convention-violation`. Counts now measured against the manifest
toggle set, missing row added, plus a new enforceability-tier section so
the detect-then-judge guard cannot be read as deterministic.

## A guard was built and withdrawn

`asserted-path-verify` shipped in #1284 and is **not** in this PR. A
full-corpus sweep — all 975 tracked markdown files, each fed as a real
payload — measured:

| | |
|---|---|
| Files firing | **231 / 975 = 23.7%** |
| Findings | **389** |
| True positives | **0** |

The oracle never misfired; every finding was a scoping problem. 72% were
consumer-project config paths (`.claude/**` and similar) that a doc
describes for a *consuming* repo and that correctly do not exist in a
marketplace — its first-segment gate passed only because this repo
happens to carry same-named top-level directories. 17% were
subtree-relative citations resolvable against a skill or plugin root.
Fixing the three dominant causes still left ~4% firing at zero true
positives.

A guard that fires on a quarter of writes and is never right trains
people to ignore every advisory, including the real ones. Per
`docs/conventions/hook-precision/README.md`'s over-fire discipline, it
does not ship. The measurement is carried on **#1314** for rescoping
rather than discarded — the sweep covered one repo and that repo is the
pathological case, so the guard may be mis-scoped rather than unsound.

## Test plan

`skill-reference-verify.test.sh` — **48 cases**, covering MUST-fire,
MUST-stay-quiet, kill-switch, empty-stdin, missing-prerequisite and
telemetry. Stay-quiet coverage includes unowned plugins, manifest-less
directories, uppercase non-command tokens, unbackticked prose,
non-markdown files, non-`Write|Edit` tools, files outside
`CLAUDE_PROJECT_DIR`, and CHANGELOGs.

`require-jq-notice-isolation.test.sh` proves the notice key is unique
plugin-wide by glob discovery.

Guard counts reconcile three ways at **11** — README table rows,
manifest `*_enabled` toggles, wired hook scripts. Catalog regenerated
via `node scripts/generate-catalog.mjs`.

Gates: `validate-plugins.sh`, `check-silent-skips.sh`,
`check-changelog-parity.sh --check` and `--check-bump origin/main`,
ShellCheck against the repo rcfile — all pass.

## Related

Closes #1270, whose scope was amended twice during the build: three
guards → two (a version-vs-manifest guard had no buildable trigger,
already covered by `check-changelog-parity --check-bump`), then two →
one on the measurement above.

#1314 carries the withdrawn guard's rescope. #1284 is superseded. #853
is the merge whose `0.14.3` bump forced this rebase — the collision
#1284's body predicted.

**Review status, stated plainly:** the contract suite and repo gates are
verified, and the noise measurement above is the empirical case for the
one guard that ships. A subagent security review was dispatched twice
and never reported, so this PR relies on the repo's own
`security-review`, `review` and cross-vendor checks. A shell hook
running on every `Write`/`Edit` is a code-execution trust surface —
please do not merge on the author's word alone.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 25, 2026
…OT (#401) (#1021)

## Summary

Plugin-side deliverable for #401. The retired external prose doc
`~/.claude/docs/ghq-layout-sibling-pr-worktrees.md` (dotfiles/chezmoi
repo) prescribed a stale
sibling-at-`ghq`-level worktree layout and carried the babysit
worktree-exemption policy. This PR
closes the SSOT gap named in the issue: it states, in the plugin itself,
that the plugin — not any
external prose doc — is the canonical, sole source for both worktree
conventions.

**Nothing was actually missing or migrated.** Verification (below)
confirms the babysit exemption
policy is already fully captured *and machine-enforced* in the plugin,
and that the "Codex-home
root" the issue flags is a per-user `babysit_worktree_root` config
**value**, not plugin content —
migrating that literal path into a user/machine/org-agnostic plugin
would be wrong. This PR
clarifies canonical ownership; it does not restate the retired prose
(point-don't-copy).

## Fix

- `plugins/source-control/skills/babysit-prs/reference/worktrees.md` —
added a statement that this
file is the canonical, sole source for the ephemeral babysit-worktree
exemption, with the
load-bearing rationale a future maintainer could otherwise undo: rooting
these worktrees outside
every repository's discoverable tree (plugin data dir by default) keeps
ephemeral scratch out of
repository enumeration such as `ghq list`; repointing
`babysit_worktree_root` back under a
  discoverable tree reintroduces that pollution.
- `plugins/source-control/skills/worktree/SKILL.md` — added one sentence
stating this skill owns
the parallel-session **external-root** convention going forward (the
discoverable replacement),
explicitly scoped to the external-root replacement, not the abandoned
sibling layout.

## Verification

- **Babysit exemption fully captured + machine-enforced.**
`reference/worktrees.md` Policy already
documents lease-scoped cleanup and "never request global open-PR
cleanup" (lines 12-31), and the
operational runbook step 8 in `SKILL.md` (line 433) says "Never globally
prune open-PR
worktrees." `scripts/prune_babysit_worktrees.py` lines 189-194
machine-enforce it: `--lease-token`
requires `--pr`, and `--prune-open-clean` requires **both** `--pr` and
`--lease-token` — so no
  global open-PR prune path exists.
- **Codex-home root is a config value, not plugin content.**
`babysit_worktree_root` (userConfig in
`.claude-plugin/plugin.json`) defaults to the `worktrees/` subdir of the
plugin data dir. The
babysit skill already migrated its root off `CODEX_HOME` to
`${CLAUDE_PLUGIN_DATA}` (CHANGELOG
line 735). Live grep: `CODEX_HOME|\.codex|\.agents` across the whole
babysit skill → **zero
  matches**.
- **No dangling pointer to the retired doc.** Live grep for
`ghq-layout|sibling-pr-worktree` and
`.claude/docs` worktree pointers across the plugin → **no plugin
reference** (the only `ghq list`
  hits are unrelated `repo-hygiene` tree-batch features).
- **markdownlint-cli2** on all three edited markdown files (root
`.markdownlint-cli2.jsonc`) →
  `0 error(s)`.
- **Commit signed** (SSH, ED25519), pushed via explicit refspec.
- Out of scope, intentionally deferred (epic #398, not this issue): the
setup skill emitting a
per-tool worktree-root map + exemptions as *output*. Not missed —
deferred.

Closes #401

## Related

- Part of epic #398.
- **Cross-repo follow-up (out of scope here):** physical deletion of
`ghq-layout-sibling-pr-worktrees.md` and removal of its pointer from the
user's `CLAUDE.md`
"Reference docs" list happen in the **dotfiles/chezmoi** repo — not
reachable from this worktree.
- **Draft hold released:** the PR was held as draft while unrelated
source-control PRs landed first
(#1055 → `0.17.1`, #1029 → `0.18.0`, #1032 → `0.19.0`, and later main
advanced to `0.26.2`),
each requiring a merge-only freshness re-merge and re-bump. PR #853 (the
last open
source-control PR behind the hold) has merged, so the hold is lifted.
Current state bumps
`0.26.2 → 0.26.3` with the CHANGELOG entry moved to `[0.26.3]`; every
`plugin.json` +
`CHANGELOG.md` conflict was resolved by composing both sides (kept
main's entries and newer
  description, re-slotted the worktree-SSOT entry on top).

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 25, 2026
…ontract (#1285)

Closes #1265

## Summary

The facts a host permission classifier has to know about the babysit
lane — which entry points
mutate, which flags gate which guard, where a refusal is enforced, and
how a mutation is actually
performed — were restated in prose by every consumer with nothing
detecting drift. This makes them
a table that CI executes, and renders that table to a citable reference
doc.

Issue #1265 sketched three options. Option 1 (a capability manifest
derived by argparse
introspection) was investigated and falsified: across the nine CLI entry
points,
`add_mutually_exclusive_group` appears in two parsers and neither guards
a mutation. Every
inter-flag constraint on a mutating path is imperative
post-`parse_args()` code, and the guard
predicates that matter are boolean expressions over runtime API data —
`(autonomous or
only_outdated) and not thread["isOutdated"]` — which argparse cannot
see. Mutating-vs-read-only is
not derivable from flag names either: `manage_babysit_lease.py`'s
`--apply` help says "reap only",
yet `acquire`, `heartbeat`, and `release` write lease files
unconditionally, so a flag-name-derived
manifest would call `acquire` read-only. The real axis is not
manifest-vs-prose; it is whether each
claim is bound to an executable assertion.

## Fix

`skills/babysit-prs/scripts/tests/guard_contract.py` holds the table as
the single source of truth.
`test_guards.py` executes every row.
`skills/babysit-prs/reference/guard-contract.md` is generated
from the same tables and asserted current, so consumers cite a document
CI proves is what the code
does. Every row carries the prose claim it backs — a broken guard fails
naming the downstream claim,
not `AssertionError: 3 != 2`.

Five binding kinds, chosen per fact rather than uniformly:

- **Refusals** — the entry point is invoked; exit code, message, and
envelope fields asserted. A
`bin/` row additionally asserts *which layer* refused, using the
observable discriminator that a
bash-wrapper refusal never reaches the interpreter and so emits no JSON
envelope. This is what
pins the bash-vs-Python asymmetry: `source-control-babysit-merge`
filters `--allow-unpinned-head`
in bash, while `source-control-babysit-resolve-thread` is a pure
passthrough whose
  `--allow-unpinned-thread` refusal demonstrably comes from Python.
- **Predicates** — the classifier is called directly, because
`--autonomous`'s `isOutdated`
requirement and the bot-only line are conditions over fetched API data
that no argument shape
expresses. Includes the claim consumers get wrong: "resolves only
outdated bot threads" is true
  *only* under `--autonomous` or `--only-outdated`.
- **Effects** — run offline against a throwaway state dir, comparing
state before and after. This
is what proves `manage_babysit_lease.py acquire` writes with no
`--apply`.
- **Mechanisms** — asserted against source, because
`refresh_pr_branch.py` calls GitHub's
server-side `update-branch` and never pushes; a consumer reasoning about
push guards is reasoning
  about the wrong mechanism.
- **Documented command lines** — every `bin/`-path wrapper command
spelled in a plugin document is
checked against the backing CLI's own parser. This covers
`reference/safety.md`
§Pinned-Command Degradation, the live instance of this issue inside the
plugin's own docs.

Catalogue gates fail when a new entry point, wrapper, or
command-spelling document arrives without a
row, and when an entry point's mutation classification has nothing
asserting it.

Deferred with its trigger recorded at `guard_contract.py`'s docstring:
an argparse flag catalogue
(names, types, defaults) would additionally catch a renamed flag or
changed default that no behavior
row exercises, but needs a `build_parser()` extraction across all nine
entry points, whose parsers
are built inside `main()`.

The existing hand-written cases in `test_guards.py` were ported to rows
with their assertions
intact and strengthened — the scope rows now assert `inScope` is present
and `false` rather than
`payload.get("inScope")` being falsy, which passed vacuously when the
key was absent. The bash-level
wrapper checks in `engine.test.sh` are left in place untouched.

## Verification

`bash plugins/source-control/skills/babysit-prs/scripts/engine.test.sh`:

```text
== unittest suite ==
Ran 348 tests in 63.549s

OK
== ruff ==
All checks passed!
== guarded-wrapper behavior ==
PASS: merge wrapper rejects --allow-unpinned-head
PASS: merge wrapper reaches fail-closed CLI (no allowlist)
PASS: resolve wrapper reaches fail-closed CLI (no allowlist)
PASS: merge wrapper rejects --autopilot-merge-tier without required sets
```

Fault injection — relaxing `classify`'s `isOutdated` condition to
`only_outdated and not
thread["isOutdated"]` produced:

```text
FAIL: test_every_predicate_row (row='classify.autonomous-requires-outdated')
AssertionError: 'eligible' != 'skipped-not-outdated'
GUARD CONTRACT ROW `classify.autonomous-requires-outdated` NO LONGER HOLDS.
  claim: --autonomous requires the deterministic isOutdated 'addressed' signal: a still-current bot
  thread is skipped so an unattended worker cannot resolve a live finding and self-satisfy the merge
  gate.
  Fix the guard, or update the row in scripts/tests/guard_contract.py and regenerate
  reference/guard-contract.md.
  observed: classify returned 'eligible'
```

Two real defects were caught by the gates while building them: the
doc-coverage gate found that
`reference/orchestration.md` carries its own copy of the wrapper command
lines (now a covered row),
and markdownlint caught a dropped `## [0.26.0]` heading in the
CHANGELOG.

Also run clean: `markdownlint-cli2` over the four changed markdown
files,
`scripts/check-changelog-parity.sh --check-bump origin/main`,
`scripts/check-silent-skips.sh`.
`scripts/check-orphaned-fixtures.sh` exceeded a local timeout on this
machine and is left to CI;
this PR adds no eval fixtures.

## Related

- Refs #787 — `/source-control:setup`'s lane-script reachability probe,
which names the plugin's
  scripts from prose and could enumerate `ENTRY_POINTS` instead.
- Refs #571 — the displacement case that makes `isOutdated` a weak
signal.
- Version reconciliation: this bumps `source-control` to `0.27.0` on top
of `0.26.0`. PRs #853,
#1264, and #1021 also bump the same manifest and append to the same
CHANGELOG, landing in the
order #853#1264#1021. Whichever lands last before this one, the
version number and
  CHANGELOG position here need rebasing onto it.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-ready Fully specified and briefed; eligible for autonomous pickup from the frontier. automated Opened by automation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

disk-hygiene/clean: document per-plugin shell-helper + exit-code divergence as deliberate

1 participant