diff --git a/docs/PLUGIN-PHILOSOPHY.md b/docs/PLUGIN-PHILOSOPHY.md index c54f4eba5d..e37cb6f019 100644 --- a/docs/PLUGIN-PHILOSOPHY.md +++ b/docs/PLUGIN-PHILOSOPHY.md @@ -352,6 +352,7 @@ doc before a second plugin adopts it. Fleet audits check conformance per row. | Review severity vocabulary | `review` plugin (`context/severity.md`) | | Seam phrasing (presence-gated fallbacks) | [`docs/conventions/seam-phrasing/`](conventions/seam-phrasing/README.md) | | Loop-lane topology, escalation, capability tiers, loop invariants | [`docs/conventions/loop-lane/`](conventions/loop-lane/README.md) | +| Shell test-helper duplication and exit-code divergence | [`docs/conventions/shell-test-helpers/`](conventions/shell-test-helpers/README.md) | ## Cross-platform contract diff --git a/docs/conventions/shell-test-helpers/README.md b/docs/conventions/shell-test-helpers/README.md new file mode 100644 index 0000000000..1feb5c2181 --- /dev/null +++ b/docs/conventions/shell-test-helpers/README.md @@ -0,0 +1,76 @@ +# Shell test helpers — per-plugin duplication and exit-code divergence are deliberate + +Owner doc for one fork this marketplace has already decided: a plugin's shell `*.test.sh` assertion +primitives and a plugin script's exit-code taxonomy are **not** consolidated into a shared, +cross-plugin mechanism. Both stay duplicated or divergent per plugin, on purpose. The +[plugin philosophy](../../PLUGIN-PHILOSOPHY.md) owns the portability boundary this rests on: a plugin +never imports files from a sibling plugin, and cooperation crosses that boundary only through a +documented public seam — a shared shell assertion library is neither. + +## Why not the existing vendoring mechanism + +This repo already has one sanctioned way to share source across plugins: a canonical file under +[`lib/`](../../../lib/), copied (not imported) into each carrying plugin by a dedicated +`scripts/sync-*.sh`, and tracked in +[`scripts/cross-plugin-source-registry.txt`](../../../scripts/cross-plugin-source-registry.txt) so +`check-cross-plugin-source-drift.sh --check` fails if a copy drifts. `lib/hook-utils.sh` is the +worked example. + +That mechanism exists for clusters that are meant to stay **byte-identical**. The assert-helper copies +below are not that: they are already three genuinely different shapes, not one library that drifted — + +- **Hook-contract shape** (`ok`/`bad`, `PASS`/`FAIL` counters, plus `make_sink`/`wait_for_sink` for + hook telemetry): [`guardrails/hooks/guardrails-test-helpers.sh`](../../../plugins/guardrails/hooks/guardrails-test-helpers.sh), + [`claude-ops/hooks/claude-ops-test-helpers.sh`](../../../plugins/claude-ops/hooks/claude-ops-test-helpers.sh). +- **Skill-script shape** (`pass`/`fail`, `FAILED`/`CASE_NUM` counters, file-existence assertions): + [`source-control/scripts/test-helpers.sh`](../../../plugins/source-control/scripts/test-helpers.sh), + [`repo-hygiene/skills/clean/scripts/lib/test-helpers.sh`](../../../plugins/repo-hygiene/skills/clean/scripts/lib/test-helpers.sh). +- **Vendored-seam shape** (same `pass`/`fail` primitives, but owned by the seam itself so it stays + correct wherever the seam is resolved from — bundled or consumer-vendored — independent of this + repo's tooling): [`work-items/tools/work-item-tracker/tests/lib.sh`](../../../plugins/work-items/tools/work-item-tracker/tests/lib.sh). + +Forcing these into one shared, synced library 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, for a mechanism (`check-cross-plugin-source-drift.sh`) that already classifies these files as +outside its scope: they live at different paths per plugin and are not byte-identical, so `discover` +never flags them as an unregistered cluster. + +`scripts/check-skill-portability.test.sh` follows the same reasoning at the repo-tooling layer: it is +not a plugin, so no plugin assertion library is available to source, and it carries its own minimal +`PASS`/`FAIL` counters rather than reaching into a plugin's copy. + +## Exit-code taxonomies also diverge, deliberately + +Plugin scripts document their own `Exit:` codes rather than sharing one enum, because each taxonomy +encodes a different per-script contract, not an arbitrary numbering: + +- [`repo-hygiene/skills/clean/scripts/remove-path.sh`](../../../plugins/repo-hygiene/skills/clean/scripts/remove-path.sh) — + `0/1/2/3/4`: usage and existence checks plus two named blocking conditions (`blocked`, `unpushed`), + where `1` carries both meanings — the target was already absent, and an `--apply` that left the + target present (locked, in use, or crossing a mount) — so the caller retries or reports rather than + treating exit 1 as "nothing to do". +- [`repo-hygiene/skills/clean/scripts/git-tree-reset-batch.sh`](../../../plugins/repo-hygiene/skills/clean/scripts/git-tree-reset-batch.sh) — + `0/1/2` for the batch runner itself, forwarding a child's `5`/`7` (from + [`git-tree-reset.sh`](../../../plugins/repo-hygiene/skills/clean/scripts/git-tree-reset.sh)'s own + `0`–`7` taxonomy) into its own `1`. +- [`scripts/check-skill-portability.sh`](../../../scripts/check-skill-portability.sh) — `0/1/2`: gate + pass/fail plus usage error. + +A shared usage/exit helper would need to either flatten these distinct contracts into a lowest common +denominator or grow branching per caller — neither is simpler than each script documenting its own +`Exit:` line, which every script here already does at its own usage banner. + +## Deferred, not rejected + +`guardrails-test-helpers.sh` and `claude-ops-test-helpers.sh` are the one pair above that already +share a shape closely (both hook-contract helpers with near-identical `ok`/`bad` bodies; their +`make_sink` differs in contract — guardrails' takes a stub body, claude-ops' takes a capture file). +If they converge to byte-identical, vendoring just that pair through the existing `lib/`, +`sync-*.sh`, and registry mechanism — the same pattern `hook-utils.sh` already uses — is the smaller, +precedented move, revisited then rather than spread across all five plugins now. + +## Conformance + +Each copy site above carries a one-line pointer back to this doc. A new plugin adding its own +`*.test.sh` assertion helper is not required to register anything here — duplication of this shape is +the accepted default, not an opt-in. diff --git a/plugins/claude-ops/.claude-plugin/plugin.json b/plugins/claude-ops/.claude-plugin/plugin.json index c8a4f67f5a..4d78e55026 100644 --- a/plugins/claude-ops/.claude-plugin/plugin.json +++ b/plugins/claude-ops/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "claude-ops", - "version": "0.19.1", + "version": "0.19.2", "description": "Claude Code operations toolkit. Seven skills: observability (read locally captured telemetry — OTEL store, collector, hook-event JSONL, ccusage — with trend reports and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand — marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), morning-brief (read-only gh-based operator morning view — queue-label counts, merge-ready PRs, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness), lanes (start/restart/stop/status loop lanes as named background Claude Code sessions seeded from canonical prompt files, with per-lane model/effort and a repo-pull + marketplace-refresh launch step), and a re-runnable setup action that settles where the known-issues registry lives. Plus a family of seven advisory *-audit telemetry-emitter hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures) that emit the shared hook-telemetry envelope, and a reference sink that maps envelopes into the hook-events.jsonl the observability skill reads.", "author": { "name": "Melodic Software", diff --git a/plugins/claude-ops/CHANGELOG.md b/plugins/claude-ops/CHANGELOG.md index 6de6cabfa5..a184d0c4c8 100644 --- a/plugins/claude-ops/CHANGELOG.md +++ b/plugins/claude-ops/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to the `claude-ops` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.19.2] + +### Documentation + +- `hooks/claude-ops-test-helpers.sh` now points at + `docs/conventions/shell-test-helpers/README.md`, the repo's owner doc recording that per-plugin + shell assert-helper duplication and per-script exit-code taxonomies are deliberate, not drift. No + behavior change. + ## [0.19.1] ### Fixed diff --git a/plugins/claude-ops/hooks/claude-ops-test-helpers.sh b/plugins/claude-ops/hooks/claude-ops-test-helpers.sh index abbbccf926..6c7370675a 100644 --- a/plugins/claude-ops/hooks/claude-ops-test-helpers.sh +++ b/plugins/claude-ops/hooks/claude-ops-test-helpers.sh @@ -3,6 +3,9 @@ # Sourced (never *.test.sh-named, so the test runner's glob ignores it) by each # hook's *.test.sh after that file sets up its own TEST_TMPDIR + trap. No # dependency on any host-repo assertion library — the plugin is standalone. +# +# Duplicated across plugins by design, not drift — see +# docs/conventions/shell-test-helpers/README.md at the repo root. : "${PASS:=0}" : "${FAIL:=0}" diff --git a/plugins/guardrails/.claude-plugin/plugin.json b/plugins/guardrails/.claude-plugin/plugin.json index de4b0fdad7..23f30afade 100644 --- a/plugins/guardrails/.claude-plugin/plugin.json +++ b/plugins/guardrails/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "guardrails", - "version": "0.14.2", + "version": "0.14.3", "description": "Nine safety guards that block secret/credential writes, hardcoded machine-specific paths, git hook-bypass attempts, irreversible git operations (force-push, reset --hard, worktree-wide checkout/restore discards), Bash file-write workarounds that circumvent Write/Edit hooks, commit subjects and gh pr create titles that violate the repo's tracked team convention (when one is declared in .claude/source-control.md), (advisory) hallucinated CLI flags, (advisory) un-throttled Workflow fan-out that risks burst 529s, and (advisory) direct git commit/gh pr create calls bypassing this marketplace's own commit/pull-request skills — each independently toggleable.", "author": { "name": "Melodic Software", diff --git a/plugins/guardrails/CHANGELOG.md b/plugins/guardrails/CHANGELOG.md index 5a2a0a8fe3..6d97f2f420 100644 --- a/plugins/guardrails/CHANGELOG.md +++ b/plugins/guardrails/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to the `guardrails` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.14.3] + +### Documentation + +- `hooks/guardrails-test-helpers.sh` now points at + `docs/conventions/shell-test-helpers/README.md`, the repo's owner doc recording that per-plugin + shell assert-helper duplication and per-script exit-code taxonomies are deliberate, not drift. No + behavior change. + ## [0.14.2] ### Fixed diff --git a/plugins/guardrails/hooks/guardrails-test-helpers.sh b/plugins/guardrails/hooks/guardrails-test-helpers.sh index 78411f72af..2bf95b8bc5 100644 --- a/plugins/guardrails/hooks/guardrails-test-helpers.sh +++ b/plugins/guardrails/hooks/guardrails-test-helpers.sh @@ -3,6 +3,9 @@ # Sourced (never *.test.sh-named, so a test runner's glob ignores it) by each # hook's *.test.sh after that file sets up its own TEST_TMPDIR + trap. No # dependency on any host-repo assertion library — the plugin is standalone. +# +# Duplicated across plugins by design, not drift — see +# docs/conventions/shell-test-helpers/README.md at the repo root. : "${PASS:=0}" : "${FAIL:=0}" diff --git a/plugins/repo-hygiene/.claude-plugin/plugin.json b/plugins/repo-hygiene/.claude-plugin/plugin.json index 2fb2b2c559..ba245fbf89 100644 --- a/plugins/repo-hygiene/.claude-plugin/plugin.json +++ b/plugins/repo-hygiene/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "repo-hygiene", - "version": "0.7.0", + "version": "0.7.1", "description": "Repo hygiene action-router: /repo-hygiene:clean sweeps reclaimable caches, build artifacts, and stale git metadata, and can realign the working tree to a fresh-pull state — dry-run-first, with destructive tiers gated behind explicit confirmation and a session-scoped destructive-command guard. Ecosystem targets are detected at runtime; secrets, runtime dependencies, and skill data are preserved by default.", "author": { "name": "Melodic Software", diff --git a/plugins/repo-hygiene/CHANGELOG.md b/plugins/repo-hygiene/CHANGELOG.md index 724fa7b361..1a87b9c3bb 100644 --- a/plugins/repo-hygiene/CHANGELOG.md +++ b/plugins/repo-hygiene/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to the `repo-hygiene` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.7.1] + +### Documentation + +- `skills/clean/scripts/lib/test-helpers.sh` now points at + `docs/conventions/shell-test-helpers/README.md`, the repo's owner doc recording that per-plugin + shell assert-helper duplication and per-script exit-code taxonomies are deliberate, not drift. No + behavior change. + ## [0.7.0] ### Added diff --git a/plugins/repo-hygiene/skills/clean/scripts/lib/test-helpers.sh b/plugins/repo-hygiene/skills/clean/scripts/lib/test-helpers.sh index cf332addfb..e94fda9bc5 100644 --- a/plugins/repo-hygiene/skills/clean/scripts/lib/test-helpers.sh +++ b/plugins/repo-hygiene/skills/clean/scripts/lib/test-helpers.sh @@ -7,6 +7,9 @@ # # Each test file owns its own FAILED / CASE_NUM counters and exits non-zero at # the end: `[[ $FAILED -eq 0 ]] || exit 1`. +# +# Duplicated across plugins by design, not drift — see +# docs/conventions/shell-test-helpers/README.md at the repo root. [[ -n "${_CLEAN_TEST_HELPERS_LOADED:-}" ]] && return 0 readonly _CLEAN_TEST_HELPERS_LOADED=1 diff --git a/plugins/source-control/.claude-plugin/plugin.json b/plugins/source-control/.claude-plugin/plugin.json index c0a4f1457f..eb1b9cab6f 100644 --- a/plugins/source-control/.claude-plugin/plugin.json +++ b/plugins/source-control/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "source-control", - "version": "0.26.0", + "version": "0.26.1", "description": "Git and GitHub delivery workflow: /commit (Conventional Commits + Co-Authored-By trailer via safe heredoc mechanics), /pull-request (prep, create, CI monitoring, review-comment triage, merge, CI-log fetch), /babysit-prs (self-pacing fleet loop — safe by default; opt-in worker/autopilot tiers add gate-checked merge and thread resolution behind a deterministic Python engine), /babysit-loop (the loop-lane merge lane: a standing or drain loop that invokes babysit-prs per cycle, configured through repo-scoped babysit_loop_* keys on the layered source-control.md seam, with merge authority human-only until the target repo's tracked config adopts the lane, a gate-proven C2-mechanical baseline once adopted, and merge-rung raises binding from the team-tracked layer only), /worktree (create, status, cleanup, audit for parallel-session isolation), /setup (check the effective commit-subject / PR-title convention merged across its config layers and the babysit-prs config, or apply — interview the repo and write the convention config to a chosen layer), and /resolve-conflicts (intent-first merge/rebase conflict resolution with a semantic-conflict sweep — never --abort). The commit-subject / PR-title convention is configurable via a source-control.md config written by a re-runnable setup skill, layered across a ~/.claude user-global file, the tracked team file, and a gitignored .claude/source-control.local.md personal overlay merged per key; Conventional Commits is the default when no convention is declared.", "author": { "name": "Melodic Software", diff --git a/plugins/source-control/CHANGELOG.md b/plugins/source-control/CHANGELOG.md index 3d81dd77cc..fa4dcc3bc7 100644 --- a/plugins/source-control/CHANGELOG.md +++ b/plugins/source-control/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to the `source-control` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.26.1] + +### Documentation + +- `scripts/test-helpers.sh` now points at `docs/conventions/shell-test-helpers/README.md`, the + repo's owner doc recording that per-plugin shell assert-helper duplication and per-script exit-code + taxonomies are deliberate, not drift. No behavior change. + ## [0.26.0] ### Added diff --git a/plugins/source-control/scripts/test-helpers.sh b/plugins/source-control/scripts/test-helpers.sh index 75c229c4b5..f27b9167be 100644 --- a/plugins/source-control/scripts/test-helpers.sh +++ b/plugins/source-control/scripts/test-helpers.sh @@ -9,6 +9,9 @@ # # Param order: subject (haystack/actual) BEFORE expected (needle), except # assert_eq / assert_exit which take (label, expected, actual). +# +# Duplicated across plugins by design, not drift — see +# docs/conventions/shell-test-helpers/README.md at the repo root. [[ -n "${_TESTS_LIB_LOADED:-}" ]] && return 0 readonly _TESTS_LIB_LOADED=1 diff --git a/plugins/work-items/.claude-plugin/plugin.json b/plugins/work-items/.claude-plugin/plugin.json index 56dffcd074..7238841aa5 100644 --- a/plugins/work-items/.claude-plugin/plugin.json +++ b/plugins/work-items/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "work-items", - "version": "0.24.0", + "version": "0.24.1", "description": "Manages development work items through a provider-neutral tracker seam that ships with the plugin (bundled dispatcher plus github and local-markdown adapters; seam plugin-dir canonical, adapters consumer-local-first): dashboard, taxonomy-labeled creation, a race-safe assignee-plus-lease claim protocol, recurring-schedule checks, TODO scanning, stale-lease auditing, plan decomposition into vertical-slice items, raw-intake triage (issues and unsolicited PRs through raw, verified, briefed, autonomous-eligible states), plus the two work-items loop lanes of the loop-lane convention: a self-paced autonomous work-loop drain (work-class admission gate, adaptive item cap, PR-only) and an attended attend-queue escalation lane. The re-runnable setup skill binds the provider (.work-item-tracker.json), seeds the recurring-schedule seam (.github/recurring-schedule.json), and remaps canonical role labels.", "author": { "name": "Melodic Software", diff --git a/plugins/work-items/CHANGELOG.md b/plugins/work-items/CHANGELOG.md index b92c92e693..2617dd31a8 100644 --- a/plugins/work-items/CHANGELOG.md +++ b/plugins/work-items/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to the `work-items` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.24.1] + +### Documentation + +- `tools/work-item-tracker/tests/lib.sh` now points at + `docs/conventions/shell-test-helpers/README.md`, the repo's owner doc recording that per-plugin + shell assert-helper duplication and per-script exit-code taxonomies are deliberate, not drift. No + behavior change. + ## [0.24.0] ### Changed diff --git a/plugins/work-items/tools/work-item-tracker/tests/lib.sh b/plugins/work-items/tools/work-item-tracker/tests/lib.sh index ccb71233b6..b2ea6a8aa5 100644 --- a/plugins/work-items/tools/work-item-tracker/tests/lib.sh +++ b/plugins/work-items/tools/work-item-tracker/tests/lib.sh @@ -14,6 +14,9 @@ # # Param order: subject (actual) BEFORE expected — reads as "in , # expect ". +# +# Duplicated across plugins by design, not drift — see +# docs/conventions/shell-test-helpers/README.md at the repo root. [[ -n "${_WIT_TESTS_LIB_LOADED:-}" ]] && return 0 readonly _WIT_TESTS_LIB_LOADED=1 diff --git a/scripts/check-skill-portability.test.sh b/scripts/check-skill-portability.test.sh index 1005ee14cb..71df32dc7b 100755 --- a/scripts/check-skill-portability.test.sh +++ b/scripts/check-skill-portability.test.sh @@ -5,6 +5,10 @@ # and runs the script's --all mode from a fixture root, exactly as the # silent-skip suite does. Two cases run against the REAL corpus to prove the # bare-vs-guarded discrimination on live files, not only synthetic ones. +# +# Bespoke PASS/FAIL counters by design, not drift: this is repo tooling, not a +# plugin, so no plugin assertion library applies here — see +# docs/conventions/shell-test-helpers/README.md. # shellcheck disable=SC2016 # fixture bodies are literal skill content in single quotes; expansion is never wanted set -uo pipefail