diff --git a/CHANGELOG.md b/CHANGELOG.md index a2a9a95..7869a83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Everything below is new; there is no earlier release to change or fix against. T - Add the five SPDD pipeline stage skills and their orchestrator: `forge` (PRD authoring on Fowler's REASONS Canvas; Norms in machine-checkable form, each Safeguard the source of a property test), `anvil` (spec/plan/tasks decomposition via spec-kit slash commands or the vendored templates; `[UX]`/`[REFACTOR]`/`[HARD]` task tags and a ≤400-changed-line task budget), `temper` (local adversarial review of the spec triplet: three critics with mandatory active procedures, a judge with a capture-recapture overlap signal, A++ = two consecutive zero-blocking rounds, max 5 rounds), `quench` (BDD-first red-amber-green execution with the amber test freeze, diff-scoped mutation gate, static gates, FR ↔ test traceability, stable-green 3× rule, property tests, characterization-first `[REFACTOR]`, sample-and-select `[HARD]`, append-only `quench-log.md`), `hone` (the same adversarial loop pointed at the implementation diff: conformance / security / simplicity lenses, ≤400-changed-line review units, every fix shown to fail without itself, max 3 rounds), and `smithy` (stateless orchestrator whose every state is decidable from disk artifacts, halting at every gate; finish step updates README/CHANGELOG, runs Atlas if installed, and logs `FINISH:` to `smithy-log.md`) - Add invocation aliases: `prd-authoring`, `speckit-decomposition`, `adversarial-review-loop`, `bdd-tdd-execution`, `code-review-loop`, `spdd-pipeline` - Add the five quench dispatch agents — `bdd-scenario-writer`, `tdd-test-generator`, `playwright-e2e-tester`, `fastapi-implementer`, `labcoat` — each a concise contract (amber = fails for the right reason, tests frozen from amber, test agents never implement, the implementer never edits tests, coverage reported never gated) with one short example per concept; quench's dispatch table states how to bind another stack to the same contract -- Add `install.sh` for symlinking skills and agents into a consumer repo's `.claude/`: idempotent, bash 3.2 / stock macOS compatible, relative links only (pure-bash relative-path computation, no `realpath --relative-to`), touches only links that resolve into the damascus checkout, prunes damascus-owned links whose names are no longer shipped, refuses missing targets, and exits non-zero when any link could not be placed; `--verify` reports link health for bug reports, `--dry-run` prints planned actions, `--uninstall` removes every owned link +- Add `install.sh` for symlinking skills and agents into a consumer repo's `.claude/`: idempotent, bash 3.2 / stock macOS compatible, relative links only (pure-bash relative-path computation, no `realpath --relative-to`), touches only links that resolve into the damascus checkout, prunes damascus-owned links whose names are no longer shipped and never one it still ships, refuses missing targets, and exits non-zero when any link could not be placed; `--verify` reports link health for bug reports, `--dry-run` prints planned actions, `--uninstall` removes every owned link - Add `tests/smoke.sh` — end-to-end installer test against a throwaway consumer repo: idempotency, the ownership guarantee, relative links, DENY skills never linked, pruning, verify, dry-run, a non-damascus path in the way fails the run, clean uninstall - Add CI: shellcheck + syntax checks; repo integrity sourced from `install.sh`'s own name arrays (stage skills, aliases, agents, KEEP skills all exist; every upstream superpowers skill is classified KEEP or DENY); frontmatter lint; vendor submodules must sit on an upstream tag named in the README; the example artifact trail must be complete; the smoke test on Ubuntu and macOS (including stock bash 3.2); a changelog-updated gate on PRs - Add a tag-triggered `Release` workflow that publishes the GitHub Release from the matching `CHANGELOG.md` section diff --git a/install.sh b/install.sh index b7d0b04..859713f 100755 --- a/install.sh +++ b/install.sh @@ -98,10 +98,30 @@ owned_by_damascus() { # true if existing path is a symlink resolving into damasc return 1 } -expected_skill_names() { # every name damascus ships into .claude/skills/ - local s pair - for s in "${STAGE_SKILLS[@]}" "${SUPERPOWERS_KEEP[@]}"; do printf '%s\n' "$s"; done - for pair in "${ALIASES[@]}"; do printf '%s\n' "${pair%%:*}"; done +# Membership is answered in-process, never through a pipe. `... | grep -Fxq` +# matches correctly but exits on the first hit, so every name emitted after it +# writes into a closed pipe; under `set -o pipefail` that EPIPE becomes the +# pipeline's exit status, and a name damascus DOES ship reads as one it does +# not. Only a match that is not the last line emitted can trigger it, and only +# when the producer is slower than grep — which is why it surfaced as an +# intermittent macOS-only "stale damascus-owned link" against a healthy install. +is_expected_skill() { # true if $1 is a name damascus ships into .claude/skills/ + local name=$1 s pair + for s in "${STAGE_SKILLS[@]}" "${SUPERPOWERS_KEEP[@]}"; do + if [ "$s" = "$name" ]; then return 0; fi + done + for pair in "${ALIASES[@]}"; do + if [ "${pair%%:*}" = "$name" ]; then return 0; fi + done + return 1 +} + +is_expected_agent() { # true if $1 is a name damascus ships into .claude/agents/ + local name=$1 a + for a in "${AGENTS[@]}"; do + if [ "$a" = "$name" ]; then return 0; fi + done + return 1 } link() { # link @@ -129,7 +149,7 @@ prune() { # remove damascus-owned links whose names are no longer shipped for p in "$SKILLS_DIR"/*; do [ -L "$p" ] || continue name="$(basename "$p")" - if owned_by_damascus "$p" && ! expected_skill_names | grep -Fxq "$name"; then + if owned_by_damascus "$p" && ! is_expected_skill "$name"; then run rm "$p" say_done "pruned stale link $name" fi @@ -137,7 +157,7 @@ prune() { # remove damascus-owned links whose names are no longer shipped for p in "$AGENTS_DIR"/*.md; do [ -L "$p" ] || continue name="$(basename "$p" .md)" - if owned_by_damascus "$p" && ! printf '%s\n' "${AGENTS[@]}" | grep -Fxq "$name"; then + if owned_by_damascus "$p" && ! is_expected_agent "$name"; then run rm "$p" say_done "pruned stale link $(basename "$p")" fi @@ -262,8 +282,8 @@ verify() { # report link health; include the env facts a bug report needs for p in "$SKILLS_DIR"/* "$AGENTS_DIR"/*.md; do name="$(basename "$p" .md)" if owned_by_damascus "$p" \ - && ! expected_skill_names | grep -Fxq "$name" \ - && ! printf '%s\n' "${AGENTS[@]}" | grep -Fxq "$name"; then + && ! is_expected_skill "$name" \ + && ! is_expected_agent "$name"; then err "stale damascus-owned link: $p" PROBLEMS=$((PROBLEMS + 1)) fi