From b1c4426e4aae30c73e428ebe018e2c16b2254de9 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Fri, 7 Aug 2026 21:56:09 -0600 Subject: [PATCH 01/24] =?UTF-8?q?docs:=20session=20close-out=20for=20v4.5.?= =?UTF-8?q?0=20=E2=80=94=20.STATUS=20milestone=20+=20spec=20archival?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the v4.5.0 release (repo-triage skill, 8 ecosystem tutorials, nav fix) in .STATUS milestone narrative; archives the two completed specs that drove this release. Co-Authored-By: Claude Sonnet 5 --- .STATUS | 4 ++-- docs/specs/{ => _archive}/GRILL-repo-triage-2026-08-07.md | 0 .../SPEC-ecosystem-tool-tutorials-2026-08-07.md | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename docs/specs/{ => _archive}/GRILL-repo-triage-2026-08-07.md (100%) rename docs/specs/{ => _archive}/SPEC-ecosystem-tool-tutorials-2026-08-07.md (100%) diff --git a/.STATUS b/.STATUS index 3c7d80d02..8c4b8d037 100644 --- a/.STATUS +++ b/.STATUS @@ -1,8 +1,8 @@ status: Active version: 4.5.0 progress: 100 -release_date: 2026-07-27 -milestone: **v4.4.2 SHIPPED 2026-07-27** — PR #318 (dev→main merge, tag v4.4.2, PATCH): the one fix merged to dev since v4.4.1 (PR #317, closes #316). `preflight-check` skill made reachable by its declared identity — it lived at the pre-rename `skills/check/` location while declaring `name: preflight-check`, so directory-based skill discovery couldn't find it; moved to `skills/preflight-check/`, `/craft:check`'s public argument contract unchanged. New recursive fixture-backed identity tests inventory every command/skill directory-vs-frontmatter mismatch and command/skill name collision plugin-wide, rejecting new drift; 8 pre-existing mismatches + 3 intentional shim collisions ledgered as owned debt through v5.0.0. Test automation now targets stable Python 3.14 (`.python-version` pin, 5 GitHub Actions workflows aligned, drift-guard policy test) — the local-interpreter assertion in that test was found too brittle on review (hard-failed for any contributor not on 3.14) and fixed to skip instead before merge. `docs/NEWS.md`/`docs/index.md`'s own v4.4.2 release-note prose initially contained the literal `skills/check/` string, which the new #317 regression guard (`test_no_live_legacy_check_skill_references`) correctly caught as a repo-wide scan, not scoped to code — rephrased without the literal path, second commit. Full suite 2627 passed/0 failed/52 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.2` clean (10/10); docs-staleness GREEN; live site + Homebrew formula (fresh sha256) + aggregator sync + Craft MCP Release all confirmed v4.4.2; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.2 · PR: https://github.com/Data-Wise/craft/pull/318, https://github.com/Data-Wise/craft/pull/317. Prior: **v4.4.1 SHIPPED 2026-07-26** — PR #315 (dev→main merge, tag v4.4.1, PATCH): post-4.4.0 fixes only, no features. `scripts/branch-guard.sh`'s `2>&1`/`1>&2` false-positive (a command like `git commit -m "...(7 -> 9)" 2>&1` misdetected as file creation) was fixed live on the installed hook earlier this session but never ported to the repo's canonical copy, leaving `test_repo_copy_matches_installed` red and the bug reintroducible on reinstall — ported with 2 regression tests (implements `GRILL-rtk-hook-safety-close-out-2026-07-26.md` decision #3). `docs/help/refine-flag.md` and `docs/reference/COMMAND-PARAMETERS.md` fixed for PR #313's 9-command declarer set (was still 7, missing `/craft:plan`/`/craft:smart-help`) — new dogfood test guards against recurrence. `docs/index.md`'s "Latest" callout and `docs/NEWS.md` synced (both still described v4.3.0's content under a v4.4.0 label; NEWS.md had no v4.4.0 entry at all). Also triaged 7 remaining open issues (#228, #213, #199, #170, #169, #133, #108): closed #108/#213/#133, re-scope comments on #228/#170/#169, confirmed #199 valid. Full suite 2654 passed/0 failed/16 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.1` clean; docs-staleness GREEN; live site + Homebrew formula confirmed v4.4.1; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.1 · PR: https://github.com/Data-Wise/craft/pull/315. Prior: **v4.4.0 SHIPPED 2026-07-26** — PR #314 (dev→main merge, tag v4.4.0, MINOR): the one `feat:` commit since v4.3.1. `smart-help` declares `--refine` (D7, PR #313) — opt-in, default-OFF, closing the last dangling item on #268's prompt-refiner umbrella. `prompt-refiner`'s Default Policy table made exhaustive and re-keyed on command file path — fixed 3 stale display names, added 2 live callers it was silently missing (`commands/plan.md`, `commands/arch/plan.md`); a new dogfood test (`test_refine_default_policy_table_exhaustive`) guards against future drift. #268's other two proposed items (named destinations, `--scope global`) and #266 (multi-round grill mode) were grilled then killed on adversarial review before implementation — see `docs/specs/GRILL-prompt-refiner-remaining-2026-07-26.md`'s amendment; #266 closed pointing at savant's `edit-loop` (shipped 2026-07-13), which already solves the observed pattern with tooling craft structurally lacks. Full suite 2618 passed/0 failed/49 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.0` clean (10/10); docs-staleness GREEN; live site + Homebrew formula + aggregator sync all confirmed v4.4.0. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.0 · PR: https://github.com/Data-Wise/craft/pull/314, https://github.com/Data-Wise/craft/pull/313. Prior: **v4.3.1 SHIPPED 2026-07-25** — PR #312 (dev→main merge, tag v4.3.1, PATCH): two bug fixes merged to dev since v4.3.0, both traced to the same session. `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281) surfaced in the guard-bypass-marker confirms themselves (#309, PR #310) — the documented "Claude writes `.claude/allow-once`" one-shot flow was circular for the one case where the approved action IS creating/editing that marker; added the env-var hint to the three marker-specific confirms, reconciled both branch-guard reference docs. Bare-`vX.Y.Z` version-grep bug class swept repo-wide (PR #311) — the same false positive that broke the Homebrew release gate twice (v4.2.0, v4.3.0; fixed for `docs/index.md` in #308) also affected `docs.yml`'s live-site poll, `verify-surfaces.sh`'s `resolve_docs_site`, `version-sync-precommit.sh`'s blocking `.STATUS` pre-commit check, and manual release-verification docs — all re-anchored to the actual version signal instead of the first bare match, each with a regression test verified via planted-defect positive control. Full suite 2650 passed/0 failed/16 skipped/1 xfailed (tracked)/1 xpassed (known perf flake); `pre-release-check.sh 4.3.1` clean (10/10); docs-staleness GREEN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.1 · PR: https://github.com/Data-Wise/craft/pull/312, https://github.com/Data-Wise/craft/pull/310, https://github.com/Data-Wise/craft/pull/311. Prior: **v4.3.0 SHIPPED 2026-07-25** — PR #307 (dev→main merge, tag v4.3.0): `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281, PR #305) closing the guard-bypass-marker creation deadlock, reusing the #168 env-var pre-authorization pattern; new advisory-only `reference-scope-guard.sh` hook (#286, PR #306) warning on non-conforming filenames under `~/.claude/reference/`, sharing `install-guards.sh`/`guards.json` with `branch-guard`/`no-switch-guard`. Full suite 2648 passed/0 failed. **Homebrew Release recurrence:** the exact same `pre-release-check.sh` false positive that broke v4.2.0's automated tap push (worked around manually then) broke v4.3.0's too — `docs/index.md`'s version check grepped the first bare `vX.Y.Z` string in the file, landing on a historical "since v4.0.0" migration note instead of the actual version badge. Fixed at the source this time (PR #308, fast-tracked dev→main): matched the badge pattern specifically (`version-X.Y.Z`), same as the adjacent README.md check. Re-dispatched `homebrew-release.yml` via `workflow_dispatch --ref main -f version=4.3.0` (the original `release`-event run had checked out the tag commit before the fix existed, so a plain rerun would have failed identically) — all 3 jobs green, tap formula confirmed live (`v4.3.0.tar.gz`, fresh sha256). Craft MCP Release + Aggregator Sync both green from the original release event. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.0 · PR: https://github.com/Data-Wise/craft/pull/307, https://github.com/Data-Wise/craft/pull/308. Prior: **v4.2.0 SHIPPED 2026-07-19** — PR #296 (dev→main merge, tag v4.2.0): **BREAKING:** `/craft:done` renamed to `/craft:finish` (ADR-006) — matches the existing zsh `finish` alias, no back-compat shim, command count unchanged (48, a rename not an addition); `/craft:restore` spec/report status corrected DRAFT→SHIPPED (had already shipped in v4.1.0 when written). Full suite 2649 passed/1 failed (a genuine broken-link regression from the rename sweep, `docs/commands/brief.md` still linked pre-rename `done.md` — caught by the test run itself, fixed same-commit)/10 skipped. Also merged to dev this cycle (not yet in a tagged release): PR #297 (`utils/docs_detector.py`'s `_extract_command_name()` truncated multi-hyphen command names past a 500-char read window, bumped to 2000), PR #298 (extends `/craft:finish` Step 1.10.5 with a `~/.claude/projects//memory/`+`.remember/` staleness check, 14-day threshold, report-only), and a `docs/reports/` folder established as the `/savant:report` output convention (moved `REPORT-craft-restore-2026-07-17.md` there, added to `scripts/config/exclusions.txt` alongside `docs/specs/`). **Homebrew:** v4.2.0's automated tap-formula push failed — `homebrew-tap`'s `main` gained branch protection (PR + 2 required checks) since `homebrew-release.yml` was last touched, so its direct `git push origin main` was rejected (GH006); recovered manually via `homebrew-tap`#164, which surfaced 2 more CI-gate bugs fixed directly in that repo (#165: `check-revision-bump.sh` false-positived on URL-tag-versioned formulas like craft's, no explicit `version` field to read; #170: the ".STATUS conflict markers" required check was path-filtered to only `.STATUS` changes, so it could never fire — and the PR stayed BLOCKED forever — on formula-only PRs). craft's own workflow fixed to match (PR #299: push a bot branch + open/auto-merge a PR instead of pushing directly), landing independently from a background-dispatched session whose worktree had gone stale after only completing 1 of the 2 related craft/homebrew-tap fixes — recovered by inspecting the worktree directly rather than trusting the dispatch's completion state. Release: https://github.com/Data-Wise/craft/releases/tag/v4.2.0 · PR: https://github.com/Data-Wise/craft/pull/296. Prior: **v4.1.0 SHIPPED 2026-07-17** — PR #295 (dev→main merge, tag v4.1.0): `/craft:restore` (NEW root command, combines `dev/git` skill's mode-aware git-activity recap with `adhd-workflow`'s `.STATUS`-based session recap into one read-only entry point; replaces the deleted `git-recap`/`recap` commands; no `--sync`; a cross-plugin `/restore` dispatcher to `savant:restore` was investigated and explicitly descoped — router can't dispatch cross-plugin) + `/craft:refine` confirm-flow fix (a 2026-07-01 design — fenced refined-prompt block + 4-way confirm: Execute now/Copy for elsewhere/Edit first/Skip — was locked but never implemented; now shipped across all 5 `--refine` callers + standalone, closing the "asks for acceptance without printing first" bug) + full post-v4.0.0 doc-staleness cleanup (47→48 count-drift sweep across ~35 files, mermaid health score 82.3→100 on new diagrams, `docs-staleness-check.sh` RED→GREEN). New architecture docs: `docs/architecture/craft-restore-pipeline.md`, `docs/architecture/prompt-refiner-pipeline.md`. Homebrew formula PR data-wise/homebrew-tap#153 open (tested locally: `brew style`/`brew audit --strict`/`brew install` all clean, 48 commands). Full suite 2650 passed/0 failed. Release: https://github.com/Data-Wise/craft/releases/tag/v4.1.0 · PR: https://github.com/Data-Wise/craft/pull/295. Prior: **v4.0.0 SHIPPED 2026-07-16** — PR #294 (dev→main merge-commit `678e4d4a`, tag v4.0.0): craft docs-authoring surface extracted to the standalone `folio` plugin (folio v1.0.0 released FIRST, per the rollback runbook's ordering). Counts 94→47 commands / 45→40 skills / 8→2 agents. Homebrew formula regenerated with real values (sha256, command_count=47) via homebrew-release.yml (3/3 jobs green); `brew audit`/`brew upgrade` verified clean for both craft and folio formulas. Post-release docs sweep (same session): commands/hub.md + its docs/ mirror had drifted badly (stale example counts, 2 stray text artifacts, a main-menu ASCII template whose header counts didn't match listed items and referenced commands moved to folio or folded into skills); rewrote to match the real 47-command roster. API-REFERENCE-COMMANDS.md (1850 lines, last synced v1.24.0) converted to a thin index into docs/commands/*.md + docs/REFCARD.md rather than re-syncing a third documentation surface indefinitely; DEPENDENCY-ARCHITECTURE.md left untouched (its craft-vs-folio ownership question is still open, test_integration_dependency_system.py:38). Full pytest suite 2641 passed/0 failed. KNOWN, NOT FIXED: docs/REFCARD.md has the same staleness pattern across multiple sections (Site/Distribution/CI/CD subtotals, 20+ stale `/craft:git:worktree` examples for a command folded into the dev/git skill) — flagged, not yet swept. Folio's Aggregator Sync workflow fails by design (folio is a new plugin, never added to the shared Data-Wise aggregator 's marketplace.json — aggregator-sync.sh deliberately refuses to silently add unknown plugins; adding folio there is a separate, reviewed decision). CP-4 tasks/ cleanup: craft's tasks/ deleted directly on dev (unprotected); folio's tasks/session-plan-T2.4-CP2.md removed via PRs #15 (dev) and #16 (main) rather than a direct-branch delete (blocked by the permission classifier). Release: https://github.com/Data-Wise/craft/releases/tag/v4.0.0 · PR: https://github.com/Data-Wise/craft/pull/294. Prior: **v4 FOLIO SPLIT MERGED to dev 2026-07-16** — PR #290 (merge-commit `4af10f27d`, phase history preserved for the eventual v4 release tag): craft's docs-authoring surface extracted to the standalone `folio` plugin. **Counts 95/45/8 → 47/40/2.** NOT yet released (v2.61.2 still the current tag); `feature/folio-split` worktree + branch PRESERVED — Phase 4 (folio `/folio:do` + coordinated release, folio first / craft tag LAST) rides it. Phase 3.6 (router consolidations) CLOSED at **1 router of 5 planned**: `code:audit` shipped shrunk 5 cmds→2 (the `audit-router` skill, D12); `orch` (D11), `arch` (D6), and `ci` (D13) all DROPPED after reading the command bodies in full; `plan:feature` stayed excluded (pre-existing D2 lock). Root cause of the 4 drops, recorded in `docs/specs/GRILL-phase-3-6-router-consolidation-2026-07-15.md`: **the grill locked load-bearing decisions from line counts and flag names without reading the bodies** — every family that looked cohesive from surface metrics collapsed on a full read. D13 also withdrew its own planned `repo`-flag normalization as a premise artifact (no validator enforces cross-family flag consistency; the distinguishing test is *find the thing that fails if it's wrong*). Real defects fixed: 4 `category:` metadata mismatches (`ci/{fix,local}.md` declared `code`; `orch/{drive,workflow}.md` declared `orchestrate` against a nonexistent dir) — flagged by `help_file_validator._check_category_mismatch`, live-wired via `docs_update_orchestrator` → `/craft:docs:update`; a repo-wide sweep found the `orch` pair (pre-existing on dev). **Repo-wide category mismatches now 0.** T3.4 command floor 60→40 (`ci.yml:92`): the floor is a mass-deletion guardrail and had gone stale TWICE (86→60) while the real count fell 94→70→47, leaving it ABOVE the surface and hard-failing CI every run; rationale now commented in-file. `tests/test_git_shim_correctness.sh` INVERTED (not deleted) — it asserted `commands/git/*.md` were thin shims, but T3.5.2 completed that migration by deleting all 11, making the assertions unsatisfiable (7 FAIL on the branch, 21/21 on dev); it now guards the migration's END STATE (shims stay deleted · Operations 1–12 survive in `skills/dev/git/SKILL.md` · `issue-check` stays live · no unsanctioned command reappears), verified by 3 planted defects. `/craft:git:issue-check` KEPT as a live command (NOT folded into the git skill, reversing the initial plan): `tests/test_issue_check_unit.py` extracts its `classify_issue()` block from `commands/git/issue-check.md` and `exec()`s it, so the command file is the classifier's single source of truth — the same constraint as `commands/ci/triage.md` (D13). Git's directory-rename detection had silently relocated dev's new command into `skills/dev/git/references/` during the merge, which would have broken that test. Merge resolved 34 conflicts (~30 count-cascade, dev 95/45/8 vs branch 46/40/2) — every tracked count recomputed mechanically via `bump-version.sh --counts-only` rather than hand-resolved; the guard-script conflicts expected in `no-switch-guard.sh`/`install-guards.sh`/`version-check.sh` did NOT materialise. Prior: **PR #291 MERGED to dev 2026-07-16** (squash `88b221da9`): `ci-bash-suites` validator (`.claude-plugin/skills/validation/ci-bash-suites.md`) — closes the **gate-divergence** gap that caused two of #290's three CI blockers. `/craft:check` never ran the shell suites CI invokes directly because pytest doesn't collect them, so the local gate reported green for a surface it never touched; T3.5.2 shipped a regression citing "full pytest 2575 passed" while a CI-required bash suite had been red for days. Derives its suite list FROM `.github/workflows/*.yml` rather than hardcoding (craft has 22 `tests/*.sh`, CI runs **6** — the parse corrected a 4-suite assumption held all session); advisory in default mode, blocking in `release`, per the release-gated validator contract (`CRAFT_MODE` can't distinguish `--for pr` from `--for commit`). Verified by replaying the REAL T3.5.2 bug (pre-fix suite restored from `d41de18ee`) → caught. No count cascade (validators sit outside the counted surface). Also 2026-07-16: two stale claims fixed in `docs/commands.md` (`5034aef7d`) — a dry-run tip saying "46 commands" (the old TOTAL pasted into a dry-run claim; now 27) and "46 commands for documentation generation, validation, and site management" (now 2 — that sentence described a surface craft no longer has). Local `~/.claude/hooks/branch-guard.sh` re-synced via `install-guards.sh` (#290 merged the v4 hook text into dev, so the installed hook predated the repo and was advising a DELETED command, `/craft:git:guard disable branch-guard`) — this cleared the last known local test failure; dogfood 51 passed/0 failed. ⚠️ KNOWN, NOT FIXED: `docs/commands.md:10`'s "17 Categories" list is stale (`discovery-usage` demoted, `orchestrate`→`orch`, no `workflow` dir) but "category" has two defensible definitions that disagree — frontmatter-declared gives 10 (…`smart`), directories give a different 10 (…`utils`), and the current list matches neither; left for a decision rather than guessed at. Prior: Guard-hardening arc SHIPPED to dev 2026-07-15 — PR #287 (cumulative-cwd cd-target resolution, both guard hooks), PR #288 (guards.json write-race mkdir-lock, both writers), PR #289 (plan-orchestrator dispatch hardening); full detail in the 2026-07-15 Session Recap below. Counts unchanged 95/45/8; NOT yet released. Prior: PR #283 SHIPPED to dev 2026-07-14 (squash `057ea6785`): `/craft:git:issue-check ` — before implementing a fix requested by an open GitHub issue, checks whether the issue's premise still holds against current code (structured verdict: valid/moot/unclear, always cited evidence, never cached, advisory-only). Also gates `/craft:orch:drive` with a cheap `#NNN`-citation pre-filter and corrects `/recap`'s pointer copy. Built via `/craft:brainstorm` → `/craft:grill` (9 locked decisions in `docs/specs/GRILL-github-attention-triage-2026-07-14.md`; adversarial-review pass caught and corrected a factually-wrong dogfood target — issue #199 assumed moot, actually still open) → `/craft:plan` (plan-orchestrator, `ORCHESTRATE-github-attention-triage.md`) → `orchestrate-dispatch`. Dispatched agent completed all 5 phases correctly but silently failed to commit Phase 5 or update its own checkboxes before stopping mid-sentence — caught by the hang-detection cross-check (notification text vs. ORCHESTRATE file state), not a code failure; recovered by verifying git log + a fresh full suite run, fixing 5 newly-stale "94 commands" doc refs the count-cascade tooling missed, then committing + correcting checkboxes. PR CI initially failed on a genuine regression (broken relative link in the ORCHESTRATE file's own header, fixed same session) plus the 3 already-known `test_v115_adhd_enhancements` pre-existing failures; merged via `--admin` once confirmed unrelated. Counts 94→95/45/8; NOT yet released (v2.61.2 still current tag). Prior: Docs/website audit COMMITTED to dev 2026-07-09 (`d6aaa9096`) — 264 deleted-command refs stripped across 40 files, 5 whole-docs archived to `docs/archive/deleted-commands-v3/`, `mkdocs build --strict` clean. FOLIO SPLIT PLANNED (new initiative, supersedes dropped Phase 3): grill ledger `docs/specs/GRILL-folio-split-2026-07-09.md` (5 locked: tools+docs-standards scope · caller-based cut · drop DOCS from /do+hub · dup count tooling · history-preserving subtree, craft v4/folio v1) + `docs/plans/ORCHESTRATE-folio-split.md` (5 phases, P0 gates). P0 caller-audit RAN via dynamic Workflow (7 agents, adversarially verified, partition_sound): STAYS = docs:update + docs:changelog + site:deploy-as-shell; MOVES = 26; flagged docs:claude-md:* trio as craft-internal governance (likely stays) — partition NOT yet recorded into the plan docs. EXECUTION STARTED 2026-07-09 ("build auto"): worktree ~/.git-worktrees/craft/feature-folio-split live (T1.1 done, ORCHESTRATE + tasks/ moved to branch root); T1.2 folio repo creation gated on ask. ⚠️ ACCOUNT CI: zero-CI mode active across private repos since 2026-07-09 (craft is public = unaffected/free) — see memory `github-actions-zero-ci-strategy-2026-07` + savant .STATUS for the full record. Prior: Phase 1 native-first prune MERGED to dev 2026-07-09 — PR #279 (squash `4fa44fbb`): pruned 21 dead-namespace commands (115→94, −5533 lines) + excluded dead docs (specs/plans/archive) from the built mkdocs site. ⏹ NATIVE-FIRST THINNING REFUTED: a Stage-2 dynamic workflow (3 pilots × 4-axis gate + cross-plugin routing test) returned NO-GO on Phase 2 — the `do`/`hub` router structurally can't dispatch cross-plugin, AND 3/3 pilot replacements (native `/simplify`, `/code-review`, `agent-skills:ci-cd`) aren't ≥ craft on all 4 axes (weaker/auto-apply, third-party dep, or generate nothing). Phases 2 (cascade thinning) + 3 (`folio` docs/publishing split) DROPPED; folio only revisitable on docs-maintenance merits, grilled fresh. ADR-002 rich-body gate held mid-build: 3 of 24 "safe" deletions (`git:docs:refcard`, `check:gen-validator`, `workflow:insights`) carried unique logic → restored, not deleted (caught by `test_skill_referenced_commands_exist`). Verification gap fixed: pytest passed 2676 but CI's Validate-Plugin-Structure bash suite (`test_git_shim_correctness`) asserted deleted `git/{sync,git-recap}.md` exist → shim array updated (see memory `pytest-doesnt-cover-craft-ci-bash-suites`). Counts now 94/45/8 on dev; NOT yet released — v2.61.2 still the current tag. Full record: `docs/specs/SPEC-craft-native-first-breakup-2026-07-09.md` OUTCOME block. Worktree `feature/craft-native-first-breakup` removed + remote branch deleted 2026-07-09; local branch ref left stale (branch-guard blocks force-delete of squash-merged branch — documented deadlock, harmless). Prior: v2.61.2 SHIPPED 2026-07-09 — PR #275 (dev→main merge, tag v2.61.2) + PR #276 (CI fix): docs/NEWS.md release highlights page, CHANGELOG/VERSION-HISTORY v2.61.1 section, REFCARD.md version box corrected (2.61.0→2.61.2, counts 117→115), exclusions.txt archived-doc path fixed. PATCH bump. Docs Staleness + Markdown Linting GREEN; Validate Plugin Structure + Link Validation pending (known hang). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.2. Counts unchanged 115/45/8. Prior: v2.61.0 SHIPPED 2026-07-06 — PR #271 (dev→main merge `6c3e2e95`, tag v2.61.0): do.md dead-agent-dispatch removal + 24-doc sweep, `verify-surfaces.sh --report-only`/`--version` + 2 new legs (GitHub release, docs site) + release-rollback runbook, 4 confirmed branch-guard/no-switch-guard gaps closed, quota tooling removed (all from PR #270, squashed to dev pre-release), plus post-merge docs-audit fixes: 2 dead-agent surface-forms PR #270's hyphenated-only grep missed (`docs/index.md`'s feature grid, `skills/orchestrator-resilience/SKILL.md`'s fallback-dispatch example), a docs-staleness false positive (`orch.md`'s mode-table "4 agents"), and backfilled `CHANGELOG.md`/`docs/CHANGELOG.md` `[Unreleased]` entries missed during #270's own PR prep. MINOR bump. Full suite 2707 passed/1 pre-existing failure (`test_roadmap_orchestrator_enhancements`, stale `ROADMAP.md` path predating this release — root-caused and FIXED post-release in `dc6ee28a`, corrected to `docs/archive/ROADMAP.md`; this exact failure had forced `--admin` overrides on PR #270, PR #271, and `main` directly, three releases running). Merged via `--admin` (same pre-existing failure, confirmed by log each time). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.0 · PR: https://github.com/Data-Wise/craft/pull/271. Downstream: docs site live at v2.61.0 (confirmed via curl), tap formula confirmed v2.61.0 (live raw.githubusercontent fetch, not local clone), Homebrew Release + Aggregator Sync + Craft MCP Release workflows all SUCCESS. `dev` fast-forwarded to `main`'s merge commit, pushed. Counts unchanged 115/45/8. Also this session: grilled and CLOSED the orchestrate token-usage-hooks backlog (captured 2026-07-02) with NO build — two review passes (backend/architecture + interface/CLI-contract lenses) found hook 1 infeasible (no live token-introspection API exists for a prompt-driven orchestrator session), hook 3 structurally uncomputable for an in-progress run; the one surviving feasible piece (`--token-report`) wouldn't have fixed the retroactive-analysis problem that motivated the backlog, and duplicates the accounting-tooling category this same session already removed (quota tooling) — see `docs/specs/GRILL-orchestrate-token-usage-hooks-2026-07-06.md`. Prior: v2.60.0 SHIPPED 2026-07-05 — PR #269 (dev→main merge `763c5e8c`, tag v2.60.0): command-namespace reorganization (full grill-and-implement cycle, `docs/specs/SPEC-command-namespace-reorganization-2026-07-05.md`) — root promotions `/craft:next`/`/craft:done`/`/craft:refine`/`/craft:brief`/`/craft:brainstorm` (heavy real-world use); `orchestrate`→`orch` rename across ~110 files; `ci:` consolidation (`code:ci-fix`/`code:ci-local`→`ci:fix`/`ci:local`); new `docs:generate` router (9 generators, none merged — genuinely distinct logic); new `task:` namespace; `/craft:quota` deleted (folded into `/craft:orch` pre-flight); `code:desktop-watch` deleted (was a pure redirect shim); `adhd-guide` converted to a skill reference. Pre-flight caught and fixed 8 real test failures before release: 6 stale "117 commands" claims (actual 115) across dist/docs files not covered by `bump-version.sh --counts-only`'s file list, one hardcoded pre-rename path in `test_drive_command_in_nav`, and a missing TOC on the newly-moved `adhd-guide.md` (>300 lines, tripped the skill-standards release gate). MINOR bump. Full suite 2707 passed/0 failed/5 skipped; pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.60.0; homebrew-release workflow SUCCESS (tap formula confirmed v2.60.0 via live `gh api`, NOT the stale local `~/projects/dev-tools/homebrew-tap` clone which still read 2.58.0); aggregator PR #19 auto-merged in `Data-Wise/claude-plugins`; post-release sweep ALL CLEAN. Counts 115/45/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.60.0 · PR: https://github.com/Data-Wise/craft/pull/269. Prior: v2.59.0 SHIPPED 2026-07-04 — PR #260 (dev→main merge, tag v2.59.0): `plugin-audit` skill (44→45, cross-plugin command/skill collision detector, schema + false-positive fixes before merge, #237) + docs-site staleness hardening H1/H2/H3 (`docs.yml` live-site post-deploy version poll, stale-count guard extended to all of `docs/**/*.md`, #259). Counts 116/45/8 (later corrected to 115 in v2.60.0 after the `resume.md` deletion cascade settled). Prior: v2.58.0 SHIPPED 2026-07-02 — PR #251 (dev→main merge, tag v2.58.0): distribution-surface hardening — curl-pipe installer fixed to clone current `main` instead of a frozen v1.16.0 mirror (D1, 11 contract tests); homebrew-tap copy-based install (no symlinks); net-new `mcp/` TS MCP server (craft-mcp Desktop bridge, 3 read-only tools, DXT/.mcpb); esbuild audit-clear. Counts 116/44/8. Prior: v2.57.0 SHIPPED 2026-07-01 — PR #242 (dev→main merge `ec5797e6`, tag v2.57.0): grill attack-angles (#238 — 7 project-agnostic adversarial axes, angle 3 reads target project's own CLAUDE.md/ADRs at runtime; skill count 43→44) + orchestrate-family wave-1 (#239 — deleted fictional 523-line `resume.md` session-teleportation command, command count 117→116; thin-shimmed `orchestrate:plan` to `plan-orchestrator` skill) + doc-impact rubric extension (#241 — 4 new scored types Tutorial/API/Cookbook/Architecture-doc with tiered thresholds + arch-doc double-count subtraction rule + advisory Site Consistency checklist) + orchestrate-dispatch mode (#240 — 3rd `--output` value dispatching a background Agent from the live planning session, with confirm-before-dispatch gate, scoped concurrency cap, failure/hang detection at 2×-effort-estimate window, resumability, scoped `.STATUS` auto-write). MINOR bump (4 feat/refactor PRs). #240/#241 were background-agent dispatches using the very pattern #240 formalizes — monitoring protocol caught 1 self-introduced broken-link regression (a cross-tree link into `~/.claude/...` from this session's own BRAINSTORM doc, fixed on dev) + recovered a killed agent's completed-but-uncommitted Phase 3 work. A CHANGELOG merge-order conflict between #240/#241 (both added `[Unreleased]` entries) was resolved by rebase, keeping both. Pre-release-check initially reported doc-coverage as BLOCKING due to a macOS bash-3.2 `mapfile` incompatibility (false positive — confirmed PASS, 0 gaps, via Homebrew bash 5.x/zsh). Full suite 2072 passed/7 failed/13 errors (documented env-caused baseline, not regressions); pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.57.0; homebrew-release workflow SUCCESS (formula confirmed v2.57.0, desc "116 commands"); post-release sweep ALL CLEAN. Counts 116/44/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.57.0 · PR: https://github.com/Data-Wise/craft/pull/242. Prior: v2.56.0 SHIPPED 2026-07-01 — PR #234 (dev→main merge `43beb7b3`, tag v2.56.0): token-usage reduction (PR #232 squash `e6c24a27` — orchestrator model pinning, `/refine` 631→42 lines, `/brainstorm` redesign, new `command-skill-token-efficiency` skill) + release-tracking hardening (namespace-refactor token-cost go/no-go measured 68.3% reduction; broken `/usage` scheduled-trigger replaced with `ccusage`+`claude-monitor`; 2 cross-spec factual bugs fixed via a second grill pass). MINOR bump (feat: #232). All CI checks GREEN on first pass — no `--admin` needed (Validate Plugin Structure resolved cleanly this time). Full suite 2089 passed; pre-release-check PASSED; main CI GREEN; live site confirmed v2.56.0; homebrew-release workflow SUCCESS; post-release sweep ALL CLEAN. Counts 117/43/8. Prior: v2.54.0 SHIPPED 2026-06-27 — `/craft:workflow:brief` (117th command): 3-line action block generator (Next step / Watch out for / Connects to) with `--plan`/`--verbose`/`--show-context` flags; `--brief` integrated into `/craft:do` Step 5.5. 29 e2e + 12 dogfood tests. Main CI GREEN; docs deployed; homebrew tap updated (117 commands); post-release sweep CLEAN (getting-started.md fixed); all BLOCK surfaces aligned. Counts: 117 / 39 / 8. Prior: v2.53.0 SHIPPED 2026-06-26 — multi-surface-aware release (surface registry + aggregator CI propagation + Cowork verify leg + `/craft:dist:surfaces`, #218/#184) + docs-staleness fix (docs/plans/ excluded from count-consistency scan). MINOR bump (feat). All tests pass; main CI GREEN; docs deployed; homebrew tap updated (116 commands); post-release sweep ALL CLEAN; aggregator sync wired (all 4 satellite repos). Counts 116/39/8. Prior: v2.52.0 SHIPPED 2026-06-26 — interactive-by-default commands (#215) + default-on test/docs scaffolding (#216) (PR #217 dev→main merge `c5f704f3`, tag v2.52.0, `--admin` on stuck Validate-Plugin-Structure): grill/orchestrate AskUserQuestion model (Recommended-first + per-option consequence) + `--yes`/`--non-interactive`; refine default-on for brainstorm/do/plan:feature/grill (opt-in elsewhere) + `--yes` cascade + orchestrate Step 0.5 structured model; AND brainstorm/plan:feature/grill emit a tier-inferred test-plan + doc-scorer Documentation section BY DEFAULT (`--no-tests`/`--no-docs`; arch:plan/spec-review opt-in via `--tests`/`--docs`), logic in skills (brainstorm-insights/plan-orchestrator, deprecation-trap-safe + guarded), doc-scorer REUSED, lifecycle-split, count-cascade-excluded. Built spec→grill→plan→subagent-driven-TDD (2 plans, 7+8 tasks, per-task spec/quality + opus whole-branch reviews; gates caught a dropped-phrase regression, a refine over-flip, a decorative deprecation-guard sentinel, and a MEMORY.md over-compaction that dropped 37/102 pointers → all fixed). MINOR bump (4 feat). Full suite 1990 passed; pre-release-check PASSED; main CI GREEN; docs deployed (live site 2.52.0); Homebrew Release auto-updated tap; verify-surfaces 6/6 ALIGNED (refreshed local lag: brew formula 2.51→2.52 via `--formula`, marketplace-update-THEN-plugin-update for the Code pin); post-release sweep ALL CLEAN. Counts 115/39/8. Prior: v2.51.0 SHIPPED 2026-06-25 — governance retro (#205) + skill-standards release gate (PRs #211/#212→dev, #214 dev→main merge `180181f8`, tag v2.51.0; CLOSED #205 + #138 via default-branch auto-close): D1 **R09 status-not-drift** governance check (`governance/checks/status_drift.py`, warn) — audits `.STATUS` `version:` vs manifest + SHIPPED/tagged vX.Y.Z vs git tag, fixture `--selftest` + LIVE in `pre-release-check.sh` (this very release was self-gated by it: ".STATUS: version + release claims match" ✓); D3 ADR-003 (release-drift advisory not hard-gate + P7 freeze); skill-standards validator graduated to a **release-tier gate** (`CRAFT_MODE=release` blocks, advisory below) + fixed a latent CRAFT_MODE no-op (LLM-prose command never exported the gating var → gate+version-check silently dead). MINOR bump (2 feat: #211/#212). Full suite 1970 passed; pre-release-check PASSED; main CI GREEN; **verify-surfaces 6/6 ALIGNED** (after refreshing local lag: brew upgrade 2.50→2.51, `claude plugin update` 2.50→2.51, pulled stale `~/projects/dev-tools/homebrew-tap` checkout — the script reads that local clone FIRST, was the only "blocker"); post-release sweep ALL CLEAN. Counts 115/39/8. Process: merged #211/#212 (conflict-free despite branches behind dev — `--stat` looked conflicted but they authored ZERO changes to `.STATUS`/`CLAUDE.md`/`governance.md`; verified via `merge-tree --write-tree`), pushed a local-only gate branch first (data-loss risk), `git:clean` removed 2 worktrees (11+2 squash-merged branch refs left dangling — branch-guard blocks `-D`, harmless). Prior: v2.50.0 SHIPPED 2026-06-25 — v2.49.x issues sprint (PRs #206/#207/#208→dev, #209 dev→main --admin on phantom Validate Plugin Structure, tag v2.50.0): advisory skill-standards validator in `/craft:check` (hot-reload, never fails); homebrew dist-gates `verify_caveats.py`+`post_install_check.py` wired into `/release` Steps 10b/10c (advisory, strict via HOMEBREW_GATE_STRICT) + 10d (BLOCKING aggregator-sync + Cowork verify) (#200/#199); SessionEnd insights facet hook + idempotent installer, per-session-id dedup (#183); ADR-001 Workflow-vs-worktree (#171); +14 e2e/dogfood tests pinning locked decisions. Built via craft:grill (10 decisions D1–D10) → superpowers writing-plans (4 TDD tracks) → 2 dynamic Workflows (impl+verify, ran from dev session under branch-guard bypass, re-protected after). MINOR bump (3 feat: commits). Full suite 1958 passed; pre-release-check PASSED; main CI GREEN; tap+brew+Code+marketplace all 2.50.0 (verify-surfaces 6/6 ALIGNED); post-release sweep CLEAN. KEY INCIDENTS (recovered): (1) #206 squash folded UNPUSHED local-dev base commits → #207/#208 phantom conflicts → rebased --onto origin/dev to untangle (memory squash-folds-unpushed-dev-base-commits; push dev before PRing off it); (2) GitHub comma-list `Closes #200, #199, #183, #171` only auto-closed #200 (+#171 via Track-0 trailer) — each issue needs its OWN keyword; closed #183/#199 manually. Counts 115/39/8. Prior: v2.49.0 SHIPPED 2026-06-23 — skill-standards auditor + /done memory/settings + ADR-002 + release re-split (PR #203 auditor→dev, #204 release dev→main --admin, tag v2.49.0): `/craft:code:skill-standards` batch-audits `skills/**/SKILL.md` vs a vendored copy of Anthropic's standards (score+exit 0/1/2, `--json`/`--markdown`, `--fix` = version-tag-strip + frontmatter-normalize + TOC-stub, never prose; `--refresh-standards`); `/done` Memory Optimize (Step 1.12) + Settings Sync (Step 1.10.5); ADR-002 consolidated `/done` into the `adhd-workflow` skill (`references/done.md` SoT + thin shim); 4 oversized skills split to `references/` (release re-split loss-gated after a lossy first attempt, 0 lines lost) → 39 skills 100/100. Counts 114→115. Release: dev was unsynced with main (v2.48.0 release merge commits) → dev-wins sync merge resolved the PR #204 conflict; full suite 1929 passed; main CI GREEN; docs deployed; tap synced. POST-RELEASE TOOLING (homebrew-tap + savant): root-caused the local Code-registered lag — the formula `post_install` ran `claude plugin update` WITHOUT first `claude plugin marketplace update local-plugins` (no-op on a stale cache); fixed in `homebrew-tap/generator/generate.py` for ALL claude-plugin formulas; reconciled manifest-drift across 6 formulas (rforge-orchestrator's `deprecate!` was at risk of silent un-deprecation; himalaya-mcp 12 skills/22 MCP tools; scholar 28→34; craft `{command_count}` single-source token) gated by regen==committed; added a Formula Drift Guard CI workflow (regen-vs-committed, first run GREEN); savant `CLAUDE.md` 19→25 skills. Memories: post-install-marketplace-refresh-before-update, deprecated-command-rich-body-trap, skill-authoring-tooling-landscape. Prior: v2.48.0 SHIPPED 2026-06-23 (grill + orchestrate Step 0.5 Clarify). Prior: v2.47.0 SHIPPED 2026-06-21 — Governance roadmap COMPLETE (PR #196/#197→dev, #198→main, tag v2.47.0): R04 automated as content-drift (manual→checks/no_drifted_copy.py — installed SKILL.md must be byte-identical to canon; DISTINCT from R07 version-pin, catches a hand-edited body even when the version matches; gates session, vacuous-skip otherwise) + #184 release pre-flight advisory (pre-release-check.sh runs run_rules.py --json, prints RED count, NEVER blocks — gentle-ramp; TestReleaseGuard184 invariant) + live-flow test coverage (e2e soak lifecycle hook→ledger→promote-check + R04-in-audit; dogfood ledger edges). Governance suite 47→59. KEY FINDING (memory r04-content-drift-not-filetype): naïve "real-dir=copy" heuristic was dead (installs are real dirs, no symlinks) → content-drift; always fire a new WARN checker on the live env first (a false positive poisons the soak ledger). #198 merged CLEAN (no --admin). Homebrew tap synced to v2.47.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Governance now: R01–R04+R06 automated · R05 advisory · R07 external · R08; 5 surfaces (visibility·prevention·promotion·cross-repo·release-advisory). Prior: v2.46.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #3 (PR #194→dev, #195→main, tag v2.46.0): soak-then-flip promotion + cross-repo wrapper, completing Phase 2 (visibility · prevention · promotion · cross-repo). soak.py = LOCAL gitignored STATE.json ledger (first_seen/last_seen/last_red per rule) fed by the SessionStart hook each audit; run_rules.py --promote-check (--window 14d, --state) lists warn rules soaked clean long enough to recommend a human warn→error flip (machinery recommends, human promotes; advisory exit 0). run.sh = cross-repo wrapper (consumers invoke the one installed engine, cwd-portable, no drift, R07). 8 dogfood tests (injected today → no flake), 47 governance suite green. Bundled #193: test_performance_cached_run → xfail(strict=False) (wall-clock microbenchmark flaked on shared CI, forced --admin; now a soft signal) — and #195 MERGED CLEAN with NO --admin (the xfail paid off). Homebrew tap synced to v2.46.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. R04 automation + #184 release-pre-flight guard remain deferred. Prior: v2.45.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #2 (PR #190→dev, #192→main, tag v2.45.0): SessionStart visibility hook (governance/session_hook.py) — audits the live ~/.claude/skills tree at session open and injects a compact RED-only summary into context via SessionStart additionalContext; silent when clean, mtime-cached, no-op where skills tree absent, never breaks a session on error. Visibility not prevention (SessionStart hooks inject context, can't block). Completes the three-surface model: pre-commit + CI (prevention) · SessionStart (visibility). Implemented direct-to-worktree from the dev session (worktree-path-aware). #192 admin-merged (strict up-to-date BEHIND from the v2.44.0 merge-commit divergence; PR-context full suite GREEN). Homebrew tap synced to v2.45.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Global install of the hook DEFERRED to post-v2.45.0 (point at released plugin path; docs in governance/README.md). PR #3 (soak-then-flip + cross-repo wrapper) + R04 deferred. Also: removed leaked ORCHESTRATE-guard-suite.md (PR #191). Prior: v2.44.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #1 (PR #188→dev, #189→main, tag v2.44.0): R03 automated (manual→script — checks/no_private_in_public_marketplace.py scans a marketplace manifest, fails on a public-marketplace ref to a private/PII repo, denylist {savant}, dual-mode file/dir + good/bad fixtures; closes the last error-rule enforcement gap), pre-commit governance-gate hook (^governance/ → selftest + render --check drift) + CI gate in ci.yml (with explicit "CI does NOT evaluate R01/R07 live-env" note), engine {marketplace} substitution through audit()+selftest() + --marketplace. Built from a draft→adversarial-verify→ultra-finalize workflow chain (verified packet); implemented directly into the worktree from the dev session (branch-guard is worktree-path-aware — corrected a stale assumption). Also corrected stale "not yet wired" governance docs. #189 admin-merged (only failure was the flaky test_performance_cached_run timing test, unrelated to the diff; PR-context full suite GREEN). Homebrew tap synced to v2.44.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 PR #2 (SessionStart hook + soak-then-flip + cross-repo wrapper) NEXT; R04 deferred. Prior: v2.43.0 SHIPPED 2026-06-20 — Skill-Ecosystem Governance (Phase 0) (PR #185→dev, #187→main, tag v2.43.0): policy-as-code under governance/ — RULES.yaml (8 skill-location rules, single source of truth), run_rules.py (audit + --selftest meta-validation, fail-closed), render_rules.py (CLAUDE.md block generation + --check drift gate), portable checkers + good/bad fixtures, guide + Plugin Release Runbook, 24 e2e/dogfood tests + governance pytest marker. PR #185 review hardening (all 5 issues): fail-closed gating (broken checker on error rule exits 1), recursive broken-symlink detection, R01 gates session (not ci) + visible canon-skip, --check exit-2, --selftest surfaces external rules, UTF-8. Homebrew tap synced to v2.43.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 enforced-gates roadmap spec open in PR #186. Prior: v2.42.0 SHIPPED 2026-06-19 — /craft:code:fewer-prompts curated read-only Bash allowlist (PR #181). Prior: v2.41.1 SHIPPED 2026-06-19 — Documentation Coverage (PR #180): 25 tutorial stubs for all Phase 8 commands + broken link fix in TUTORIAL-smart-help.md. Prior: v2.41.0 SHIPPED 2026-06-19 — token-efficiency (quota gate, engine flag, parity runbook). v2.40.0 SHIPPED 2026-06-19 — Guard Suite (PR #174): /craft:git:guard + guards.json registry & ADHD toggle UX, no-switch-guard promoted to scripts/, guard-consistency validator, install-guards.sh. Destructive-restore reconciled to no-switch-guard; branch-guard rm-rf-.git/reset--hard softened to confirm (catastrophic ops stay NON-muteable). 111 commands. Review found 1 CRITICAL (catastrophic-bypass) + 6 issues — ALL fixed pre-merge; +5 registry tests; ci.yml now installs both guards + runs the no-switch suite. Prior: v2.39.0 SHIPPED 2026-06-19 — doc-coverage enforcement (PR #172→dev, #173→main). Prior: v2.38.2 SHIPPED 2026-06-15 — multi-surface release arc (3 patches). v2.38.0: multi-surface engine (verify-surfaces Step 13.6, cache-prune Step 13.7, Data-Wise aggregator marketplace) + branch-guard draft=dev (PRs #158/#159/#160/#161). Adversarial review found 3 HIGH + 6 MEDIUM/LOW bugs → v2.38.1 (PR #163: branch-aware dogfood tests fixed RED main CI; cache-prune data-loss; verify-surfaces git-tag no-op) → v2.38.2 (PR #165: corrupt-surface blocks, symlink GC, arg-parse, subshell). All 3 → main, main CI GREEN, tap + aggregator (Data-Wise/claude-plugins) pinned to 2.38.2, post-release sweep CLEAN. Counts: 112 commands / 39 skills / 8 agents. Prior: v2.37.0 SHIPPED 2026-06-13 — drift-prevention chain + CI triage/watch. Bundled brainstorm items 1-7 (PRs #151/#152/#153/#154/#155): #1 strict-markers guard + #4 workflow recipe discoverability; #7 bump-version categorical-subtotal sweep; #2 CI count-drift tripwire (README live counts); #5 /craft:ci:triage + #6 /craft:ci:watch. PR #156 → main 8c21a54d (merge commit), release tag v2.37.0. Downstream all GREEN: main CI verified, homebrew-release.yml updated tap to v2.37.0 (desc "112 commands", url v2.37.0.tar.gz). Post-release sweep ALL CLEAN (drift tooling paid off). Counts: 112 commands / 39 skills / 8 agents. Prior: v2.36.0 SHIPPED 2026-06-13 (PR #150 → main eb7e143d). +release_date: 2026-08-07 +milestone: **v4.5.0 SHIPPED 2026-08-07** — PR #329 (dev→main merge, tag v4.5.0, MINOR): new `repo-triage` skill (41st skill) batch-grounds open GitHub issues and stale worktrees/branches against current repo state, offering confirmed (never automatic) deletion/closure — built via brainstorm→grill (`GRILL-repo-triage-2026-08-07.md`, 2 factual corrections)→dispatched ORCHESTRATE worktree, code-reviewed (8 findings, all fixed pre-merge: jq-null pr_state bug, swallowed git-diff failures, not-merged-branch passthrough, tracked ORCHESTRATE artifact, stale skill-count prose, duplicated evidence-normalization + fence-extraction logic consolidated into `classifier_loader.py`, cross-repo file-check bug), merged PR #328 squash to dev first. Also shipped: 8 new ADHD-friendly ecosystem-tool cheat-sheet tutorials (Codex, Remember, i-have-adhd, Token Optimizer, Security Guidance, Claude HUD, Dropbox, Agent Skills) under a new nested `Ecosystem Tool Cheat Sheets` mkdocs subsection (`SPEC-ecosystem-tool-tutorials-2026-08-07.md`), plus a `docs/commands/hub.md` nav-completeness fix (`TUTORIAL-opencode-plugins.md` was missing from mkdocs.yml). Pre-flight found 2 non-blocking local-only failures (not regressions, CI confirmed green on the same commit): `test_branch_guard_dogfood.py` (the installed hook is a symlink to the sibling `cc-config` repo, out of scope per cross-repo permission boundary) and `test_no_live_legacy_check_skill_references` (was walking gitignored `.remember/` via raw filesystem `rglob()` — fixed to scope to `git ls-files`, genuine bug, fixed pre-release). Full suite 2632 passed/0 failed (post-fix); `pre-release-check.sh 4.5.0` clean; docs-staleness GREEN; live site + Homebrew formula + aggregator sync (claude-plugins PR #46, auto-merged) all confirmed v4.5.0; `verify-surfaces.sh` needed a local `homebrew-tap` `git pull` + `brew upgrade` + `claude plugin update` refresh before reporting ALIGNED (known pattern — the script reads the local tap checkout, not live GitHub). KNOWN, NOT FIXED: `aggregator-sync.yml` returns 404 by that exact workflow name in `claude-plugins` (the release skill's own docs name a workflow that doesn't exist under that filename) — propagation still worked via some other mechanism (PR #46 auto-merged), doc/workflow-name discrepancy left unswept, out of scope for this release. Release: https://github.com/Data-Wise/craft/releases/tag/v4.5.0 · PR: https://github.com/Data-Wise/craft/pull/329, https://github.com/Data-Wise/craft/pull/328. Prior: **v4.4.2 SHIPPED 2026-07-27** — PR #318 (dev→main merge, tag v4.4.2, PATCH): the one fix merged to dev since v4.4.1 (PR #317, closes #316). `preflight-check` skill made reachable by its declared identity — it lived at the pre-rename `skills/check/` location while declaring `name: preflight-check`, so directory-based skill discovery couldn't find it; moved to `skills/preflight-check/`, `/craft:check`'s public argument contract unchanged. New recursive fixture-backed identity tests inventory every command/skill directory-vs-frontmatter mismatch and command/skill name collision plugin-wide, rejecting new drift; 8 pre-existing mismatches + 3 intentional shim collisions ledgered as owned debt through v5.0.0. Test automation now targets stable Python 3.14 (`.python-version` pin, 5 GitHub Actions workflows aligned, drift-guard policy test) — the local-interpreter assertion in that test was found too brittle on review (hard-failed for any contributor not on 3.14) and fixed to skip instead before merge. `docs/NEWS.md`/`docs/index.md`'s own v4.4.2 release-note prose initially contained the literal `skills/check/` string, which the new #317 regression guard (`test_no_live_legacy_check_skill_references`) correctly caught as a repo-wide scan, not scoped to code — rephrased without the literal path, second commit. Full suite 2627 passed/0 failed/52 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.2` clean (10/10); docs-staleness GREEN; live site + Homebrew formula (fresh sha256) + aggregator sync + Craft MCP Release all confirmed v4.4.2; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.2 · PR: https://github.com/Data-Wise/craft/pull/318, https://github.com/Data-Wise/craft/pull/317. Prior: **v4.4.1 SHIPPED 2026-07-26** — PR #315 (dev→main merge, tag v4.4.1, PATCH): post-4.4.0 fixes only, no features. `scripts/branch-guard.sh`'s `2>&1`/`1>&2` false-positive (a command like `git commit -m "...(7 -> 9)" 2>&1` misdetected as file creation) was fixed live on the installed hook earlier this session but never ported to the repo's canonical copy, leaving `test_repo_copy_matches_installed` red and the bug reintroducible on reinstall — ported with 2 regression tests (implements `GRILL-rtk-hook-safety-close-out-2026-07-26.md` decision #3). `docs/help/refine-flag.md` and `docs/reference/COMMAND-PARAMETERS.md` fixed for PR #313's 9-command declarer set (was still 7, missing `/craft:plan`/`/craft:smart-help`) — new dogfood test guards against recurrence. `docs/index.md`'s "Latest" callout and `docs/NEWS.md` synced (both still described v4.3.0's content under a v4.4.0 label; NEWS.md had no v4.4.0 entry at all). Also triaged 7 remaining open issues (#228, #213, #199, #170, #169, #133, #108): closed #108/#213/#133, re-scope comments on #228/#170/#169, confirmed #199 valid. Full suite 2654 passed/0 failed/16 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.1` clean; docs-staleness GREEN; live site + Homebrew formula confirmed v4.4.1; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.1 · PR: https://github.com/Data-Wise/craft/pull/315. Prior: **v4.4.0 SHIPPED 2026-07-26** — PR #314 (dev→main merge, tag v4.4.0, MINOR): the one `feat:` commit since v4.3.1. `smart-help` declares `--refine` (D7, PR #313) — opt-in, default-OFF, closing the last dangling item on #268's prompt-refiner umbrella. `prompt-refiner`'s Default Policy table made exhaustive and re-keyed on command file path — fixed 3 stale display names, added 2 live callers it was silently missing (`commands/plan.md`, `commands/arch/plan.md`); a new dogfood test (`test_refine_default_policy_table_exhaustive`) guards against future drift. #268's other two proposed items (named destinations, `--scope global`) and #266 (multi-round grill mode) were grilled then killed on adversarial review before implementation — see `docs/specs/GRILL-prompt-refiner-remaining-2026-07-26.md`'s amendment; #266 closed pointing at savant's `edit-loop` (shipped 2026-07-13), which already solves the observed pattern with tooling craft structurally lacks. Full suite 2618 passed/0 failed/49 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.0` clean (10/10); docs-staleness GREEN; live site + Homebrew formula + aggregator sync all confirmed v4.4.0. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.0 · PR: https://github.com/Data-Wise/craft/pull/314, https://github.com/Data-Wise/craft/pull/313. Prior: **v4.3.1 SHIPPED 2026-07-25** — PR #312 (dev→main merge, tag v4.3.1, PATCH): two bug fixes merged to dev since v4.3.0, both traced to the same session. `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281) surfaced in the guard-bypass-marker confirms themselves (#309, PR #310) — the documented "Claude writes `.claude/allow-once`" one-shot flow was circular for the one case where the approved action IS creating/editing that marker; added the env-var hint to the three marker-specific confirms, reconciled both branch-guard reference docs. Bare-`vX.Y.Z` version-grep bug class swept repo-wide (PR #311) — the same false positive that broke the Homebrew release gate twice (v4.2.0, v4.3.0; fixed for `docs/index.md` in #308) also affected `docs.yml`'s live-site poll, `verify-surfaces.sh`'s `resolve_docs_site`, `version-sync-precommit.sh`'s blocking `.STATUS` pre-commit check, and manual release-verification docs — all re-anchored to the actual version signal instead of the first bare match, each with a regression test verified via planted-defect positive control. Full suite 2650 passed/0 failed/16 skipped/1 xfailed (tracked)/1 xpassed (known perf flake); `pre-release-check.sh 4.3.1` clean (10/10); docs-staleness GREEN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.1 · PR: https://github.com/Data-Wise/craft/pull/312, https://github.com/Data-Wise/craft/pull/310, https://github.com/Data-Wise/craft/pull/311. Prior: **v4.3.0 SHIPPED 2026-07-25** — PR #307 (dev→main merge, tag v4.3.0): `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281, PR #305) closing the guard-bypass-marker creation deadlock, reusing the #168 env-var pre-authorization pattern; new advisory-only `reference-scope-guard.sh` hook (#286, PR #306) warning on non-conforming filenames under `~/.claude/reference/`, sharing `install-guards.sh`/`guards.json` with `branch-guard`/`no-switch-guard`. Full suite 2648 passed/0 failed. **Homebrew Release recurrence:** the exact same `pre-release-check.sh` false positive that broke v4.2.0's automated tap push (worked around manually then) broke v4.3.0's too — `docs/index.md`'s version check grepped the first bare `vX.Y.Z` string in the file, landing on a historical "since v4.0.0" migration note instead of the actual version badge. Fixed at the source this time (PR #308, fast-tracked dev→main): matched the badge pattern specifically (`version-X.Y.Z`), same as the adjacent README.md check. Re-dispatched `homebrew-release.yml` via `workflow_dispatch --ref main -f version=4.3.0` (the original `release`-event run had checked out the tag commit before the fix existed, so a plain rerun would have failed identically) — all 3 jobs green, tap formula confirmed live (`v4.3.0.tar.gz`, fresh sha256). Craft MCP Release + Aggregator Sync both green from the original release event. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.0 · PR: https://github.com/Data-Wise/craft/pull/307, https://github.com/Data-Wise/craft/pull/308. Prior: **v4.2.0 SHIPPED 2026-07-19** — PR #296 (dev→main merge, tag v4.2.0): **BREAKING:** `/craft:done` renamed to `/craft:finish` (ADR-006) — matches the existing zsh `finish` alias, no back-compat shim, command count unchanged (48, a rename not an addition); `/craft:restore` spec/report status corrected DRAFT→SHIPPED (had already shipped in v4.1.0 when written). Full suite 2649 passed/1 failed (a genuine broken-link regression from the rename sweep, `docs/commands/brief.md` still linked pre-rename `done.md` — caught by the test run itself, fixed same-commit)/10 skipped. Also merged to dev this cycle (not yet in a tagged release): PR #297 (`utils/docs_detector.py`'s `_extract_command_name()` truncated multi-hyphen command names past a 500-char read window, bumped to 2000), PR #298 (extends `/craft:finish` Step 1.10.5 with a `~/.claude/projects//memory/`+`.remember/` staleness check, 14-day threshold, report-only), and a `docs/reports/` folder established as the `/savant:report` output convention (moved `REPORT-craft-restore-2026-07-17.md` there, added to `scripts/config/exclusions.txt` alongside `docs/specs/`). **Homebrew:** v4.2.0's automated tap-formula push failed — `homebrew-tap`'s `main` gained branch protection (PR + 2 required checks) since `homebrew-release.yml` was last touched, so its direct `git push origin main` was rejected (GH006); recovered manually via `homebrew-tap`#164, which surfaced 2 more CI-gate bugs fixed directly in that repo (#165: `check-revision-bump.sh` false-positived on URL-tag-versioned formulas like craft's, no explicit `version` field to read; #170: the ".STATUS conflict markers" required check was path-filtered to only `.STATUS` changes, so it could never fire — and the PR stayed BLOCKED forever — on formula-only PRs). craft's own workflow fixed to match (PR #299: push a bot branch + open/auto-merge a PR instead of pushing directly), landing independently from a background-dispatched session whose worktree had gone stale after only completing 1 of the 2 related craft/homebrew-tap fixes — recovered by inspecting the worktree directly rather than trusting the dispatch's completion state. Release: https://github.com/Data-Wise/craft/releases/tag/v4.2.0 · PR: https://github.com/Data-Wise/craft/pull/296. Prior: **v4.1.0 SHIPPED 2026-07-17** — PR #295 (dev→main merge, tag v4.1.0): `/craft:restore` (NEW root command, combines `dev/git` skill's mode-aware git-activity recap with `adhd-workflow`'s `.STATUS`-based session recap into one read-only entry point; replaces the deleted `git-recap`/`recap` commands; no `--sync`; a cross-plugin `/restore` dispatcher to `savant:restore` was investigated and explicitly descoped — router can't dispatch cross-plugin) + `/craft:refine` confirm-flow fix (a 2026-07-01 design — fenced refined-prompt block + 4-way confirm: Execute now/Copy for elsewhere/Edit first/Skip — was locked but never implemented; now shipped across all 5 `--refine` callers + standalone, closing the "asks for acceptance without printing first" bug) + full post-v4.0.0 doc-staleness cleanup (47→48 count-drift sweep across ~35 files, mermaid health score 82.3→100 on new diagrams, `docs-staleness-check.sh` RED→GREEN). New architecture docs: `docs/architecture/craft-restore-pipeline.md`, `docs/architecture/prompt-refiner-pipeline.md`. Homebrew formula PR data-wise/homebrew-tap#153 open (tested locally: `brew style`/`brew audit --strict`/`brew install` all clean, 48 commands). Full suite 2650 passed/0 failed. Release: https://github.com/Data-Wise/craft/releases/tag/v4.1.0 · PR: https://github.com/Data-Wise/craft/pull/295. Prior: **v4.0.0 SHIPPED 2026-07-16** — PR #294 (dev→main merge-commit `678e4d4a`, tag v4.0.0): craft docs-authoring surface extracted to the standalone `folio` plugin (folio v1.0.0 released FIRST, per the rollback runbook's ordering). Counts 94→47 commands / 45→40 skills / 8→2 agents. Homebrew formula regenerated with real values (sha256, command_count=47) via homebrew-release.yml (3/3 jobs green); `brew audit`/`brew upgrade` verified clean for both craft and folio formulas. Post-release docs sweep (same session): commands/hub.md + its docs/ mirror had drifted badly (stale example counts, 2 stray text artifacts, a main-menu ASCII template whose header counts didn't match listed items and referenced commands moved to folio or folded into skills); rewrote to match the real 47-command roster. API-REFERENCE-COMMANDS.md (1850 lines, last synced v1.24.0) converted to a thin index into docs/commands/*.md + docs/REFCARD.md rather than re-syncing a third documentation surface indefinitely; DEPENDENCY-ARCHITECTURE.md left untouched (its craft-vs-folio ownership question is still open, test_integration_dependency_system.py:38). Full pytest suite 2641 passed/0 failed. KNOWN, NOT FIXED: docs/REFCARD.md has the same staleness pattern across multiple sections (Site/Distribution/CI/CD subtotals, 20+ stale `/craft:git:worktree` examples for a command folded into the dev/git skill) — flagged, not yet swept. Folio's Aggregator Sync workflow fails by design (folio is a new plugin, never added to the shared Data-Wise aggregator 's marketplace.json — aggregator-sync.sh deliberately refuses to silently add unknown plugins; adding folio there is a separate, reviewed decision). CP-4 tasks/ cleanup: craft's tasks/ deleted directly on dev (unprotected); folio's tasks/session-plan-T2.4-CP2.md removed via PRs #15 (dev) and #16 (main) rather than a direct-branch delete (blocked by the permission classifier). Release: https://github.com/Data-Wise/craft/releases/tag/v4.0.0 · PR: https://github.com/Data-Wise/craft/pull/294. Prior: **v4 FOLIO SPLIT MERGED to dev 2026-07-16** — PR #290 (merge-commit `4af10f27d`, phase history preserved for the eventual v4 release tag): craft's docs-authoring surface extracted to the standalone `folio` plugin. **Counts 95/45/8 → 47/40/2.** NOT yet released (v2.61.2 still the current tag); `feature/folio-split` worktree + branch PRESERVED — Phase 4 (folio `/folio:do` + coordinated release, folio first / craft tag LAST) rides it. Phase 3.6 (router consolidations) CLOSED at **1 router of 5 planned**: `code:audit` shipped shrunk 5 cmds→2 (the `audit-router` skill, D12); `orch` (D11), `arch` (D6), and `ci` (D13) all DROPPED after reading the command bodies in full; `plan:feature` stayed excluded (pre-existing D2 lock). Root cause of the 4 drops, recorded in `docs/specs/GRILL-phase-3-6-router-consolidation-2026-07-15.md`: **the grill locked load-bearing decisions from line counts and flag names without reading the bodies** — every family that looked cohesive from surface metrics collapsed on a full read. D13 also withdrew its own planned `repo`-flag normalization as a premise artifact (no validator enforces cross-family flag consistency; the distinguishing test is *find the thing that fails if it's wrong*). Real defects fixed: 4 `category:` metadata mismatches (`ci/{fix,local}.md` declared `code`; `orch/{drive,workflow}.md` declared `orchestrate` against a nonexistent dir) — flagged by `help_file_validator._check_category_mismatch`, live-wired via `docs_update_orchestrator` → `/craft:docs:update`; a repo-wide sweep found the `orch` pair (pre-existing on dev). **Repo-wide category mismatches now 0.** T3.4 command floor 60→40 (`ci.yml:92`): the floor is a mass-deletion guardrail and had gone stale TWICE (86→60) while the real count fell 94→70→47, leaving it ABOVE the surface and hard-failing CI every run; rationale now commented in-file. `tests/test_git_shim_correctness.sh` INVERTED (not deleted) — it asserted `commands/git/*.md` were thin shims, but T3.5.2 completed that migration by deleting all 11, making the assertions unsatisfiable (7 FAIL on the branch, 21/21 on dev); it now guards the migration's END STATE (shims stay deleted · Operations 1–12 survive in `skills/dev/git/SKILL.md` · `issue-check` stays live · no unsanctioned command reappears), verified by 3 planted defects. `/craft:git:issue-check` KEPT as a live command (NOT folded into the git skill, reversing the initial plan): `tests/test_issue_check_unit.py` extracts its `classify_issue()` block from `commands/git/issue-check.md` and `exec()`s it, so the command file is the classifier's single source of truth — the same constraint as `commands/ci/triage.md` (D13). Git's directory-rename detection had silently relocated dev's new command into `skills/dev/git/references/` during the merge, which would have broken that test. Merge resolved 34 conflicts (~30 count-cascade, dev 95/45/8 vs branch 46/40/2) — every tracked count recomputed mechanically via `bump-version.sh --counts-only` rather than hand-resolved; the guard-script conflicts expected in `no-switch-guard.sh`/`install-guards.sh`/`version-check.sh` did NOT materialise. Prior: **PR #291 MERGED to dev 2026-07-16** (squash `88b221da9`): `ci-bash-suites` validator (`.claude-plugin/skills/validation/ci-bash-suites.md`) — closes the **gate-divergence** gap that caused two of #290's three CI blockers. `/craft:check` never ran the shell suites CI invokes directly because pytest doesn't collect them, so the local gate reported green for a surface it never touched; T3.5.2 shipped a regression citing "full pytest 2575 passed" while a CI-required bash suite had been red for days. Derives its suite list FROM `.github/workflows/*.yml` rather than hardcoding (craft has 22 `tests/*.sh`, CI runs **6** — the parse corrected a 4-suite assumption held all session); advisory in default mode, blocking in `release`, per the release-gated validator contract (`CRAFT_MODE` can't distinguish `--for pr` from `--for commit`). Verified by replaying the REAL T3.5.2 bug (pre-fix suite restored from `d41de18ee`) → caught. No count cascade (validators sit outside the counted surface). Also 2026-07-16: two stale claims fixed in `docs/commands.md` (`5034aef7d`) — a dry-run tip saying "46 commands" (the old TOTAL pasted into a dry-run claim; now 27) and "46 commands for documentation generation, validation, and site management" (now 2 — that sentence described a surface craft no longer has). Local `~/.claude/hooks/branch-guard.sh` re-synced via `install-guards.sh` (#290 merged the v4 hook text into dev, so the installed hook predated the repo and was advising a DELETED command, `/craft:git:guard disable branch-guard`) — this cleared the last known local test failure; dogfood 51 passed/0 failed. ⚠️ KNOWN, NOT FIXED: `docs/commands.md:10`'s "17 Categories" list is stale (`discovery-usage` demoted, `orchestrate`→`orch`, no `workflow` dir) but "category" has two defensible definitions that disagree — frontmatter-declared gives 10 (…`smart`), directories give a different 10 (…`utils`), and the current list matches neither; left for a decision rather than guessed at. Prior: Guard-hardening arc SHIPPED to dev 2026-07-15 — PR #287 (cumulative-cwd cd-target resolution, both guard hooks), PR #288 (guards.json write-race mkdir-lock, both writers), PR #289 (plan-orchestrator dispatch hardening); full detail in the 2026-07-15 Session Recap below. Counts unchanged 95/45/8; NOT yet released. Prior: PR #283 SHIPPED to dev 2026-07-14 (squash `057ea6785`): `/craft:git:issue-check ` — before implementing a fix requested by an open GitHub issue, checks whether the issue's premise still holds against current code (structured verdict: valid/moot/unclear, always cited evidence, never cached, advisory-only). Also gates `/craft:orch:drive` with a cheap `#NNN`-citation pre-filter and corrects `/recap`'s pointer copy. Built via `/craft:brainstorm` → `/craft:grill` (9 locked decisions in `docs/specs/GRILL-github-attention-triage-2026-07-14.md`; adversarial-review pass caught and corrected a factually-wrong dogfood target — issue #199 assumed moot, actually still open) → `/craft:plan` (plan-orchestrator, `ORCHESTRATE-github-attention-triage.md`) → `orchestrate-dispatch`. Dispatched agent completed all 5 phases correctly but silently failed to commit Phase 5 or update its own checkboxes before stopping mid-sentence — caught by the hang-detection cross-check (notification text vs. ORCHESTRATE file state), not a code failure; recovered by verifying git log + a fresh full suite run, fixing 5 newly-stale "94 commands" doc refs the count-cascade tooling missed, then committing + correcting checkboxes. PR CI initially failed on a genuine regression (broken relative link in the ORCHESTRATE file's own header, fixed same session) plus the 3 already-known `test_v115_adhd_enhancements` pre-existing failures; merged via `--admin` once confirmed unrelated. Counts 94→95/45/8; NOT yet released (v2.61.2 still current tag). Prior: Docs/website audit COMMITTED to dev 2026-07-09 (`d6aaa9096`) — 264 deleted-command refs stripped across 40 files, 5 whole-docs archived to `docs/archive/deleted-commands-v3/`, `mkdocs build --strict` clean. FOLIO SPLIT PLANNED (new initiative, supersedes dropped Phase 3): grill ledger `docs/specs/GRILL-folio-split-2026-07-09.md` (5 locked: tools+docs-standards scope · caller-based cut · drop DOCS from /do+hub · dup count tooling · history-preserving subtree, craft v4/folio v1) + `docs/plans/ORCHESTRATE-folio-split.md` (5 phases, P0 gates). P0 caller-audit RAN via dynamic Workflow (7 agents, adversarially verified, partition_sound): STAYS = docs:update + docs:changelog + site:deploy-as-shell; MOVES = 26; flagged docs:claude-md:* trio as craft-internal governance (likely stays) — partition NOT yet recorded into the plan docs. EXECUTION STARTED 2026-07-09 ("build auto"): worktree ~/.git-worktrees/craft/feature-folio-split live (T1.1 done, ORCHESTRATE + tasks/ moved to branch root); T1.2 folio repo creation gated on ask. ⚠️ ACCOUNT CI: zero-CI mode active across private repos since 2026-07-09 (craft is public = unaffected/free) — see memory `github-actions-zero-ci-strategy-2026-07` + savant .STATUS for the full record. Prior: Phase 1 native-first prune MERGED to dev 2026-07-09 — PR #279 (squash `4fa44fbb`): pruned 21 dead-namespace commands (115→94, −5533 lines) + excluded dead docs (specs/plans/archive) from the built mkdocs site. ⏹ NATIVE-FIRST THINNING REFUTED: a Stage-2 dynamic workflow (3 pilots × 4-axis gate + cross-plugin routing test) returned NO-GO on Phase 2 — the `do`/`hub` router structurally can't dispatch cross-plugin, AND 3/3 pilot replacements (native `/simplify`, `/code-review`, `agent-skills:ci-cd`) aren't ≥ craft on all 4 axes (weaker/auto-apply, third-party dep, or generate nothing). Phases 2 (cascade thinning) + 3 (`folio` docs/publishing split) DROPPED; folio only revisitable on docs-maintenance merits, grilled fresh. ADR-002 rich-body gate held mid-build: 3 of 24 "safe" deletions (`git:docs:refcard`, `check:gen-validator`, `workflow:insights`) carried unique logic → restored, not deleted (caught by `test_skill_referenced_commands_exist`). Verification gap fixed: pytest passed 2676 but CI's Validate-Plugin-Structure bash suite (`test_git_shim_correctness`) asserted deleted `git/{sync,git-recap}.md` exist → shim array updated (see memory `pytest-doesnt-cover-craft-ci-bash-suites`). Counts now 94/45/8 on dev; NOT yet released — v2.61.2 still the current tag. Full record: `docs/specs/SPEC-craft-native-first-breakup-2026-07-09.md` OUTCOME block. Worktree `feature/craft-native-first-breakup` removed + remote branch deleted 2026-07-09; local branch ref left stale (branch-guard blocks force-delete of squash-merged branch — documented deadlock, harmless). Prior: v2.61.2 SHIPPED 2026-07-09 — PR #275 (dev→main merge, tag v2.61.2) + PR #276 (CI fix): docs/NEWS.md release highlights page, CHANGELOG/VERSION-HISTORY v2.61.1 section, REFCARD.md version box corrected (2.61.0→2.61.2, counts 117→115), exclusions.txt archived-doc path fixed. PATCH bump. Docs Staleness + Markdown Linting GREEN; Validate Plugin Structure + Link Validation pending (known hang). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.2. Counts unchanged 115/45/8. Prior: v2.61.0 SHIPPED 2026-07-06 — PR #271 (dev→main merge `6c3e2e95`, tag v2.61.0): do.md dead-agent-dispatch removal + 24-doc sweep, `verify-surfaces.sh --report-only`/`--version` + 2 new legs (GitHub release, docs site) + release-rollback runbook, 4 confirmed branch-guard/no-switch-guard gaps closed, quota tooling removed (all from PR #270, squashed to dev pre-release), plus post-merge docs-audit fixes: 2 dead-agent surface-forms PR #270's hyphenated-only grep missed (`docs/index.md`'s feature grid, `skills/orchestrator-resilience/SKILL.md`'s fallback-dispatch example), a docs-staleness false positive (`orch.md`'s mode-table "4 agents"), and backfilled `CHANGELOG.md`/`docs/CHANGELOG.md` `[Unreleased]` entries missed during #270's own PR prep. MINOR bump. Full suite 2707 passed/1 pre-existing failure (`test_roadmap_orchestrator_enhancements`, stale `ROADMAP.md` path predating this release — root-caused and FIXED post-release in `dc6ee28a`, corrected to `docs/archive/ROADMAP.md`; this exact failure had forced `--admin` overrides on PR #270, PR #271, and `main` directly, three releases running). Merged via `--admin` (same pre-existing failure, confirmed by log each time). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.0 · PR: https://github.com/Data-Wise/craft/pull/271. Downstream: docs site live at v2.61.0 (confirmed via curl), tap formula confirmed v2.61.0 (live raw.githubusercontent fetch, not local clone), Homebrew Release + Aggregator Sync + Craft MCP Release workflows all SUCCESS. `dev` fast-forwarded to `main`'s merge commit, pushed. Counts unchanged 115/45/8. Also this session: grilled and CLOSED the orchestrate token-usage-hooks backlog (captured 2026-07-02) with NO build — two review passes (backend/architecture + interface/CLI-contract lenses) found hook 1 infeasible (no live token-introspection API exists for a prompt-driven orchestrator session), hook 3 structurally uncomputable for an in-progress run; the one surviving feasible piece (`--token-report`) wouldn't have fixed the retroactive-analysis problem that motivated the backlog, and duplicates the accounting-tooling category this same session already removed (quota tooling) — see `docs/specs/GRILL-orchestrate-token-usage-hooks-2026-07-06.md`. Prior: v2.60.0 SHIPPED 2026-07-05 — PR #269 (dev→main merge `763c5e8c`, tag v2.60.0): command-namespace reorganization (full grill-and-implement cycle, `docs/specs/SPEC-command-namespace-reorganization-2026-07-05.md`) — root promotions `/craft:next`/`/craft:done`/`/craft:refine`/`/craft:brief`/`/craft:brainstorm` (heavy real-world use); `orchestrate`→`orch` rename across ~110 files; `ci:` consolidation (`code:ci-fix`/`code:ci-local`→`ci:fix`/`ci:local`); new `docs:generate` router (9 generators, none merged — genuinely distinct logic); new `task:` namespace; `/craft:quota` deleted (folded into `/craft:orch` pre-flight); `code:desktop-watch` deleted (was a pure redirect shim); `adhd-guide` converted to a skill reference. Pre-flight caught and fixed 8 real test failures before release: 6 stale "117 commands" claims (actual 115) across dist/docs files not covered by `bump-version.sh --counts-only`'s file list, one hardcoded pre-rename path in `test_drive_command_in_nav`, and a missing TOC on the newly-moved `adhd-guide.md` (>300 lines, tripped the skill-standards release gate). MINOR bump. Full suite 2707 passed/0 failed/5 skipped; pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.60.0; homebrew-release workflow SUCCESS (tap formula confirmed v2.60.0 via live `gh api`, NOT the stale local `~/projects/dev-tools/homebrew-tap` clone which still read 2.58.0); aggregator PR #19 auto-merged in `Data-Wise/claude-plugins`; post-release sweep ALL CLEAN. Counts 115/45/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.60.0 · PR: https://github.com/Data-Wise/craft/pull/269. Prior: v2.59.0 SHIPPED 2026-07-04 — PR #260 (dev→main merge, tag v2.59.0): `plugin-audit` skill (44→45, cross-plugin command/skill collision detector, schema + false-positive fixes before merge, #237) + docs-site staleness hardening H1/H2/H3 (`docs.yml` live-site post-deploy version poll, stale-count guard extended to all of `docs/**/*.md`, #259). Counts 116/45/8 (later corrected to 115 in v2.60.0 after the `resume.md` deletion cascade settled). Prior: v2.58.0 SHIPPED 2026-07-02 — PR #251 (dev→main merge, tag v2.58.0): distribution-surface hardening — curl-pipe installer fixed to clone current `main` instead of a frozen v1.16.0 mirror (D1, 11 contract tests); homebrew-tap copy-based install (no symlinks); net-new `mcp/` TS MCP server (craft-mcp Desktop bridge, 3 read-only tools, DXT/.mcpb); esbuild audit-clear. Counts 116/44/8. Prior: v2.57.0 SHIPPED 2026-07-01 — PR #242 (dev→main merge `ec5797e6`, tag v2.57.0): grill attack-angles (#238 — 7 project-agnostic adversarial axes, angle 3 reads target project's own CLAUDE.md/ADRs at runtime; skill count 43→44) + orchestrate-family wave-1 (#239 — deleted fictional 523-line `resume.md` session-teleportation command, command count 117→116; thin-shimmed `orchestrate:plan` to `plan-orchestrator` skill) + doc-impact rubric extension (#241 — 4 new scored types Tutorial/API/Cookbook/Architecture-doc with tiered thresholds + arch-doc double-count subtraction rule + advisory Site Consistency checklist) + orchestrate-dispatch mode (#240 — 3rd `--output` value dispatching a background Agent from the live planning session, with confirm-before-dispatch gate, scoped concurrency cap, failure/hang detection at 2×-effort-estimate window, resumability, scoped `.STATUS` auto-write). MINOR bump (4 feat/refactor PRs). #240/#241 were background-agent dispatches using the very pattern #240 formalizes — monitoring protocol caught 1 self-introduced broken-link regression (a cross-tree link into `~/.claude/...` from this session's own BRAINSTORM doc, fixed on dev) + recovered a killed agent's completed-but-uncommitted Phase 3 work. A CHANGELOG merge-order conflict between #240/#241 (both added `[Unreleased]` entries) was resolved by rebase, keeping both. Pre-release-check initially reported doc-coverage as BLOCKING due to a macOS bash-3.2 `mapfile` incompatibility (false positive — confirmed PASS, 0 gaps, via Homebrew bash 5.x/zsh). Full suite 2072 passed/7 failed/13 errors (documented env-caused baseline, not regressions); pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.57.0; homebrew-release workflow SUCCESS (formula confirmed v2.57.0, desc "116 commands"); post-release sweep ALL CLEAN. Counts 116/44/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.57.0 · PR: https://github.com/Data-Wise/craft/pull/242. Prior: v2.56.0 SHIPPED 2026-07-01 — PR #234 (dev→main merge `43beb7b3`, tag v2.56.0): token-usage reduction (PR #232 squash `e6c24a27` — orchestrator model pinning, `/refine` 631→42 lines, `/brainstorm` redesign, new `command-skill-token-efficiency` skill) + release-tracking hardening (namespace-refactor token-cost go/no-go measured 68.3% reduction; broken `/usage` scheduled-trigger replaced with `ccusage`+`claude-monitor`; 2 cross-spec factual bugs fixed via a second grill pass). MINOR bump (feat: #232). All CI checks GREEN on first pass — no `--admin` needed (Validate Plugin Structure resolved cleanly this time). Full suite 2089 passed; pre-release-check PASSED; main CI GREEN; live site confirmed v2.56.0; homebrew-release workflow SUCCESS; post-release sweep ALL CLEAN. Counts 117/43/8. Prior: v2.54.0 SHIPPED 2026-06-27 — `/craft:workflow:brief` (117th command): 3-line action block generator (Next step / Watch out for / Connects to) with `--plan`/`--verbose`/`--show-context` flags; `--brief` integrated into `/craft:do` Step 5.5. 29 e2e + 12 dogfood tests. Main CI GREEN; docs deployed; homebrew tap updated (117 commands); post-release sweep CLEAN (getting-started.md fixed); all BLOCK surfaces aligned. Counts: 117 / 39 / 8. Prior: v2.53.0 SHIPPED 2026-06-26 — multi-surface-aware release (surface registry + aggregator CI propagation + Cowork verify leg + `/craft:dist:surfaces`, #218/#184) + docs-staleness fix (docs/plans/ excluded from count-consistency scan). MINOR bump (feat). All tests pass; main CI GREEN; docs deployed; homebrew tap updated (116 commands); post-release sweep ALL CLEAN; aggregator sync wired (all 4 satellite repos). Counts 116/39/8. Prior: v2.52.0 SHIPPED 2026-06-26 — interactive-by-default commands (#215) + default-on test/docs scaffolding (#216) (PR #217 dev→main merge `c5f704f3`, tag v2.52.0, `--admin` on stuck Validate-Plugin-Structure): grill/orchestrate AskUserQuestion model (Recommended-first + per-option consequence) + `--yes`/`--non-interactive`; refine default-on for brainstorm/do/plan:feature/grill (opt-in elsewhere) + `--yes` cascade + orchestrate Step 0.5 structured model; AND brainstorm/plan:feature/grill emit a tier-inferred test-plan + doc-scorer Documentation section BY DEFAULT (`--no-tests`/`--no-docs`; arch:plan/spec-review opt-in via `--tests`/`--docs`), logic in skills (brainstorm-insights/plan-orchestrator, deprecation-trap-safe + guarded), doc-scorer REUSED, lifecycle-split, count-cascade-excluded. Built spec→grill→plan→subagent-driven-TDD (2 plans, 7+8 tasks, per-task spec/quality + opus whole-branch reviews; gates caught a dropped-phrase regression, a refine over-flip, a decorative deprecation-guard sentinel, and a MEMORY.md over-compaction that dropped 37/102 pointers → all fixed). MINOR bump (4 feat). Full suite 1990 passed; pre-release-check PASSED; main CI GREEN; docs deployed (live site 2.52.0); Homebrew Release auto-updated tap; verify-surfaces 6/6 ALIGNED (refreshed local lag: brew formula 2.51→2.52 via `--formula`, marketplace-update-THEN-plugin-update for the Code pin); post-release sweep ALL CLEAN. Counts 115/39/8. Prior: v2.51.0 SHIPPED 2026-06-25 — governance retro (#205) + skill-standards release gate (PRs #211/#212→dev, #214 dev→main merge `180181f8`, tag v2.51.0; CLOSED #205 + #138 via default-branch auto-close): D1 **R09 status-not-drift** governance check (`governance/checks/status_drift.py`, warn) — audits `.STATUS` `version:` vs manifest + SHIPPED/tagged vX.Y.Z vs git tag, fixture `--selftest` + LIVE in `pre-release-check.sh` (this very release was self-gated by it: ".STATUS: version + release claims match" ✓); D3 ADR-003 (release-drift advisory not hard-gate + P7 freeze); skill-standards validator graduated to a **release-tier gate** (`CRAFT_MODE=release` blocks, advisory below) + fixed a latent CRAFT_MODE no-op (LLM-prose command never exported the gating var → gate+version-check silently dead). MINOR bump (2 feat: #211/#212). Full suite 1970 passed; pre-release-check PASSED; main CI GREEN; **verify-surfaces 6/6 ALIGNED** (after refreshing local lag: brew upgrade 2.50→2.51, `claude plugin update` 2.50→2.51, pulled stale `~/projects/dev-tools/homebrew-tap` checkout — the script reads that local clone FIRST, was the only "blocker"); post-release sweep ALL CLEAN. Counts 115/39/8. Process: merged #211/#212 (conflict-free despite branches behind dev — `--stat` looked conflicted but they authored ZERO changes to `.STATUS`/`CLAUDE.md`/`governance.md`; verified via `merge-tree --write-tree`), pushed a local-only gate branch first (data-loss risk), `git:clean` removed 2 worktrees (11+2 squash-merged branch refs left dangling — branch-guard blocks `-D`, harmless). Prior: v2.50.0 SHIPPED 2026-06-25 — v2.49.x issues sprint (PRs #206/#207/#208→dev, #209 dev→main --admin on phantom Validate Plugin Structure, tag v2.50.0): advisory skill-standards validator in `/craft:check` (hot-reload, never fails); homebrew dist-gates `verify_caveats.py`+`post_install_check.py` wired into `/release` Steps 10b/10c (advisory, strict via HOMEBREW_GATE_STRICT) + 10d (BLOCKING aggregator-sync + Cowork verify) (#200/#199); SessionEnd insights facet hook + idempotent installer, per-session-id dedup (#183); ADR-001 Workflow-vs-worktree (#171); +14 e2e/dogfood tests pinning locked decisions. Built via craft:grill (10 decisions D1–D10) → superpowers writing-plans (4 TDD tracks) → 2 dynamic Workflows (impl+verify, ran from dev session under branch-guard bypass, re-protected after). MINOR bump (3 feat: commits). Full suite 1958 passed; pre-release-check PASSED; main CI GREEN; tap+brew+Code+marketplace all 2.50.0 (verify-surfaces 6/6 ALIGNED); post-release sweep CLEAN. KEY INCIDENTS (recovered): (1) #206 squash folded UNPUSHED local-dev base commits → #207/#208 phantom conflicts → rebased --onto origin/dev to untangle (memory squash-folds-unpushed-dev-base-commits; push dev before PRing off it); (2) GitHub comma-list `Closes #200, #199, #183, #171` only auto-closed #200 (+#171 via Track-0 trailer) — each issue needs its OWN keyword; closed #183/#199 manually. Counts 115/39/8. Prior: v2.49.0 SHIPPED 2026-06-23 — skill-standards auditor + /done memory/settings + ADR-002 + release re-split (PR #203 auditor→dev, #204 release dev→main --admin, tag v2.49.0): `/craft:code:skill-standards` batch-audits `skills/**/SKILL.md` vs a vendored copy of Anthropic's standards (score+exit 0/1/2, `--json`/`--markdown`, `--fix` = version-tag-strip + frontmatter-normalize + TOC-stub, never prose; `--refresh-standards`); `/done` Memory Optimize (Step 1.12) + Settings Sync (Step 1.10.5); ADR-002 consolidated `/done` into the `adhd-workflow` skill (`references/done.md` SoT + thin shim); 4 oversized skills split to `references/` (release re-split loss-gated after a lossy first attempt, 0 lines lost) → 39 skills 100/100. Counts 114→115. Release: dev was unsynced with main (v2.48.0 release merge commits) → dev-wins sync merge resolved the PR #204 conflict; full suite 1929 passed; main CI GREEN; docs deployed; tap synced. POST-RELEASE TOOLING (homebrew-tap + savant): root-caused the local Code-registered lag — the formula `post_install` ran `claude plugin update` WITHOUT first `claude plugin marketplace update local-plugins` (no-op on a stale cache); fixed in `homebrew-tap/generator/generate.py` for ALL claude-plugin formulas; reconciled manifest-drift across 6 formulas (rforge-orchestrator's `deprecate!` was at risk of silent un-deprecation; himalaya-mcp 12 skills/22 MCP tools; scholar 28→34; craft `{command_count}` single-source token) gated by regen==committed; added a Formula Drift Guard CI workflow (regen-vs-committed, first run GREEN); savant `CLAUDE.md` 19→25 skills. Memories: post-install-marketplace-refresh-before-update, deprecated-command-rich-body-trap, skill-authoring-tooling-landscape. Prior: v2.48.0 SHIPPED 2026-06-23 (grill + orchestrate Step 0.5 Clarify). Prior: v2.47.0 SHIPPED 2026-06-21 — Governance roadmap COMPLETE (PR #196/#197→dev, #198→main, tag v2.47.0): R04 automated as content-drift (manual→checks/no_drifted_copy.py — installed SKILL.md must be byte-identical to canon; DISTINCT from R07 version-pin, catches a hand-edited body even when the version matches; gates session, vacuous-skip otherwise) + #184 release pre-flight advisory (pre-release-check.sh runs run_rules.py --json, prints RED count, NEVER blocks — gentle-ramp; TestReleaseGuard184 invariant) + live-flow test coverage (e2e soak lifecycle hook→ledger→promote-check + R04-in-audit; dogfood ledger edges). Governance suite 47→59. KEY FINDING (memory r04-content-drift-not-filetype): naïve "real-dir=copy" heuristic was dead (installs are real dirs, no symlinks) → content-drift; always fire a new WARN checker on the live env first (a false positive poisons the soak ledger). #198 merged CLEAN (no --admin). Homebrew tap synced to v2.47.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Governance now: R01–R04+R06 automated · R05 advisory · R07 external · R08; 5 surfaces (visibility·prevention·promotion·cross-repo·release-advisory). Prior: v2.46.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #3 (PR #194→dev, #195→main, tag v2.46.0): soak-then-flip promotion + cross-repo wrapper, completing Phase 2 (visibility · prevention · promotion · cross-repo). soak.py = LOCAL gitignored STATE.json ledger (first_seen/last_seen/last_red per rule) fed by the SessionStart hook each audit; run_rules.py --promote-check (--window 14d, --state) lists warn rules soaked clean long enough to recommend a human warn→error flip (machinery recommends, human promotes; advisory exit 0). run.sh = cross-repo wrapper (consumers invoke the one installed engine, cwd-portable, no drift, R07). 8 dogfood tests (injected today → no flake), 47 governance suite green. Bundled #193: test_performance_cached_run → xfail(strict=False) (wall-clock microbenchmark flaked on shared CI, forced --admin; now a soft signal) — and #195 MERGED CLEAN with NO --admin (the xfail paid off). Homebrew tap synced to v2.46.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. R04 automation + #184 release-pre-flight guard remain deferred. Prior: v2.45.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #2 (PR #190→dev, #192→main, tag v2.45.0): SessionStart visibility hook (governance/session_hook.py) — audits the live ~/.claude/skills tree at session open and injects a compact RED-only summary into context via SessionStart additionalContext; silent when clean, mtime-cached, no-op where skills tree absent, never breaks a session on error. Visibility not prevention (SessionStart hooks inject context, can't block). Completes the three-surface model: pre-commit + CI (prevention) · SessionStart (visibility). Implemented direct-to-worktree from the dev session (worktree-path-aware). #192 admin-merged (strict up-to-date BEHIND from the v2.44.0 merge-commit divergence; PR-context full suite GREEN). Homebrew tap synced to v2.45.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Global install of the hook DEFERRED to post-v2.45.0 (point at released plugin path; docs in governance/README.md). PR #3 (soak-then-flip + cross-repo wrapper) + R04 deferred. Also: removed leaked ORCHESTRATE-guard-suite.md (PR #191). Prior: v2.44.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #1 (PR #188→dev, #189→main, tag v2.44.0): R03 automated (manual→script — checks/no_private_in_public_marketplace.py scans a marketplace manifest, fails on a public-marketplace ref to a private/PII repo, denylist {savant}, dual-mode file/dir + good/bad fixtures; closes the last error-rule enforcement gap), pre-commit governance-gate hook (^governance/ → selftest + render --check drift) + CI gate in ci.yml (with explicit "CI does NOT evaluate R01/R07 live-env" note), engine {marketplace} substitution through audit()+selftest() + --marketplace. Built from a draft→adversarial-verify→ultra-finalize workflow chain (verified packet); implemented directly into the worktree from the dev session (branch-guard is worktree-path-aware — corrected a stale assumption). Also corrected stale "not yet wired" governance docs. #189 admin-merged (only failure was the flaky test_performance_cached_run timing test, unrelated to the diff; PR-context full suite GREEN). Homebrew tap synced to v2.44.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 PR #2 (SessionStart hook + soak-then-flip + cross-repo wrapper) NEXT; R04 deferred. Prior: v2.43.0 SHIPPED 2026-06-20 — Skill-Ecosystem Governance (Phase 0) (PR #185→dev, #187→main, tag v2.43.0): policy-as-code under governance/ — RULES.yaml (8 skill-location rules, single source of truth), run_rules.py (audit + --selftest meta-validation, fail-closed), render_rules.py (CLAUDE.md block generation + --check drift gate), portable checkers + good/bad fixtures, guide + Plugin Release Runbook, 24 e2e/dogfood tests + governance pytest marker. PR #185 review hardening (all 5 issues): fail-closed gating (broken checker on error rule exits 1), recursive broken-symlink detection, R01 gates session (not ci) + visible canon-skip, --check exit-2, --selftest surfaces external rules, UTF-8. Homebrew tap synced to v2.43.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 enforced-gates roadmap spec open in PR #186. Prior: v2.42.0 SHIPPED 2026-06-19 — /craft:code:fewer-prompts curated read-only Bash allowlist (PR #181). Prior: v2.41.1 SHIPPED 2026-06-19 — Documentation Coverage (PR #180): 25 tutorial stubs for all Phase 8 commands + broken link fix in TUTORIAL-smart-help.md. Prior: v2.41.0 SHIPPED 2026-06-19 — token-efficiency (quota gate, engine flag, parity runbook). v2.40.0 SHIPPED 2026-06-19 — Guard Suite (PR #174): /craft:git:guard + guards.json registry & ADHD toggle UX, no-switch-guard promoted to scripts/, guard-consistency validator, install-guards.sh. Destructive-restore reconciled to no-switch-guard; branch-guard rm-rf-.git/reset--hard softened to confirm (catastrophic ops stay NON-muteable). 111 commands. Review found 1 CRITICAL (catastrophic-bypass) + 6 issues — ALL fixed pre-merge; +5 registry tests; ci.yml now installs both guards + runs the no-switch suite. Prior: v2.39.0 SHIPPED 2026-06-19 — doc-coverage enforcement (PR #172→dev, #173→main). Prior: v2.38.2 SHIPPED 2026-06-15 — multi-surface release arc (3 patches). v2.38.0: multi-surface engine (verify-surfaces Step 13.6, cache-prune Step 13.7, Data-Wise aggregator marketplace) + branch-guard draft=dev (PRs #158/#159/#160/#161). Adversarial review found 3 HIGH + 6 MEDIUM/LOW bugs → v2.38.1 (PR #163: branch-aware dogfood tests fixed RED main CI; cache-prune data-loss; verify-surfaces git-tag no-op) → v2.38.2 (PR #165: corrupt-surface blocks, symlink GC, arg-parse, subshell). All 3 → main, main CI GREEN, tap + aggregator (Data-Wise/claude-plugins) pinned to 2.38.2, post-release sweep CLEAN. Counts: 112 commands / 39 skills / 8 agents. Prior: v2.37.0 SHIPPED 2026-06-13 — drift-prevention chain + CI triage/watch. Bundled brainstorm items 1-7 (PRs #151/#152/#153/#154/#155): #1 strict-markers guard + #4 workflow recipe discoverability; #7 bump-version categorical-subtotal sweep; #2 CI count-drift tripwire (README live counts); #5 /craft:ci:triage + #6 /craft:ci:watch. PR #156 → main 8c21a54d (merge commit), release tag v2.37.0. Downstream all GREEN: main CI verified, homebrew-release.yml updated tap to v2.37.0 (desc "112 commands", url v2.37.0.tar.gz). Post-release sweep ALL CLEAN (drift tooling paid off). Counts: 112 commands / 39 skills / 8 agents. Prior: v2.36.0 SHIPPED 2026-06-13 (PR #150 → main eb7e143d). last_session: 2026-07-30 ## Session Recap — 2026-08-01 — craft-mcp bundling committed; cwd-marker bug found diff --git a/docs/specs/GRILL-repo-triage-2026-08-07.md b/docs/specs/_archive/GRILL-repo-triage-2026-08-07.md similarity index 100% rename from docs/specs/GRILL-repo-triage-2026-08-07.md rename to docs/specs/_archive/GRILL-repo-triage-2026-08-07.md diff --git a/docs/specs/SPEC-ecosystem-tool-tutorials-2026-08-07.md b/docs/specs/_archive/SPEC-ecosystem-tool-tutorials-2026-08-07.md similarity index 100% rename from docs/specs/SPEC-ecosystem-tool-tutorials-2026-08-07.md rename to docs/specs/_archive/SPEC-ecosystem-tool-tutorials-2026-08-07.md From c410f80b4aba27a8aab0eef6c04909f322ec5713 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Fri, 7 Aug 2026 22:00:05 -0600 Subject: [PATCH 02/24] docs(cookbook): add repo-triage recipe Closes the one content gap left after the v4.5.0 docs sync: repo-triage had SKILL.md/REFCARD/getting-started coverage but no task-focused cookbook recipe, unlike triage-a-red-pr's equivalent workflow. Co-Authored-By: Claude Sonnet 5 --- docs/cookbook/recipes/triage-your-repo.md | 27 +++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 28 insertions(+) create mode 100644 docs/cookbook/recipes/triage-your-repo.md diff --git a/docs/cookbook/recipes/triage-your-repo.md b/docs/cookbook/recipes/triage-your-repo.md new file mode 100644 index 000000000..50146ba63 --- /dev/null +++ b/docs/cookbook/recipes/triage-your-repo.md @@ -0,0 +1,27 @@ +# Recipe: Triage your repo + +Open issues piling up, worktrees you forgot about, branches you're not sure are safe to +delete. Ground all of it against current repo state before acting on any of it. + +1. Ask naturally — "triage the repo" or "what needs attention here" — or invoke the + `repo-triage` skill directly. +2. It batch-checks: + - **Open GitHub issues** — still valid, or moot against current code? + - **Worktrees** — stale (no commits in N days) or already merged? + - **Branches** — merged-and-safe-to-delete, or still live work? +3. Read the buckets: + - **Confirmed-safe** → offered for deletion/closure, but never automatic — you + confirm each one. + - **Grill-ready** → issues/ideas with enough shape to take straight into + `/craft:grill`. + - **Plan-ready** → already scoped enough for `/craft:plan`. + - **Defer** → needs more context before any of the above. +4. Confirm only what you actually want gone. Nothing is deleted or closed without your + explicit yes. + +> **Why grounded, not just listed:** a plain `gh issue list` or `git worktree list` +> tells you what exists — it doesn't tell you what's still true. repo-triage checks +> each item against the current branch/commit state before bucketing it, so "closed as +> moot" and "still valid" are evidence-backed, not a guess. + +See [`skills/orchestration/repo-triage/SKILL.md`](https://github.com/Data-Wise/craft/blob/dev/skills/orchestration/repo-triage/SKILL.md). diff --git a/mkdocs.yml b/mkdocs.yml index c144b49aa..bd46b2f64 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -335,6 +335,7 @@ nav: - Run a Coded Workflow: cookbook/recipes/run-a-coded-workflow.md - Fan a Workflow Across Files: cookbook/recipes/fan-a-workflow-across-files.md - Triage a Red PR: cookbook/recipes/triage-a-red-pr.md + - Triage Your Repo: cookbook/recipes/triage-your-repo.md - Set Up New Project: cookbook/common/setup-new-project.md - Deploy Course Website: cookbook/common/deploy-course-website.md - Automate Release Workflow: cookbook/common/automate-release-workflow.md From dcaee7f4bcfdc8c8a471b4ab184617203b76126a Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Fri, 7 Aug 2026 22:05:35 -0600 Subject: [PATCH 03/24] docs: fix stale version box and agent count (missed by count-consistency gate) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit REFCARD.md's quick-reference box had the right version NUMBER (4.5.0, bump-version.sh's literal-string target) but stale content around it: release date (2026-07-17, actually v4.1.0's) and highlight line (still describing v4.1.0's /craft:restore, not v4.5.0's repo-triage skill) — same bump-version.sh blind-spot pattern as the docs/index.md fix earlier this release. Skill count also read 40, not 41. skills-agents.md's TL;DR line said "8 specialized agents" two lines above its own correct "2 specialized agents" — stale since the v4.0.0 folio split (8→2 agents), never caught because count-consistency only anchors on specific badge/count patterns, not every prose mention. Co-Authored-By: Claude Sonnet 5 --- docs/REFCARD.md | 8 ++++---- docs/skills-agents.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/REFCARD.md b/docs/REFCARD.md index 4856e1d89..7c7c98e80 100644 --- a/docs/REFCARD.md +++ b/docs/REFCARD.md @@ -4,11 +4,11 @@ ┌─────────────────────────────────────────────────────────────┐ │ CRAFT PLUGIN QUICK REFERENCE │ ├─────────────────────────────────────────────────────────────┤ -│ Version: 4.5.0 (released 2026-07-17) │ -│ Commands: 48 | Agents: 2 | Skills: 40 │ -│ Tests: 2647+ passing │ +│ Version: 4.5.0 (released 2026-08-07) │ +│ Commands: 48 | Agents: 2 | Skills: 41 │ +│ Tests: 2632+ passing │ │ Docs: https://data-wise.github.io/craft/ │ -│ v4.5.0: /craft:restore + post-v4 doc-staleness cleanup │ +│ v4.5.0: repo-triage skill + ecosystem-tool tutorials │ └─────────────────────────────────────────────────────────────┘ ``` diff --git a/docs/skills-agents.md b/docs/skills-agents.md index c8eb7b283..fdcb25cc9 100644 --- a/docs/skills-agents.md +++ b/docs/skills-agents.md @@ -1,6 +1,6 @@ # Craft Skills & Agents -> **TL;DR**: 41 skills auto-activate from conversation context (no command to memorize) and 8 specialized agents handle deep work. Browse by category below; use [`/craft:hub`](commands/hub.md) to discover commands. +> **TL;DR**: 41 skills auto-activate from conversation context (no command to memorize) and 2 specialized agents handle deep work. Browse by category below; use [`/craft:hub`](commands/hub.md) to discover commands. Craft includes 41 auto-activating skills and 2 specialized agents for comprehensive development support. From 8cc168662463c0106ac4b8e08fbf56fed9c2ea39 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Fri, 7 Aug 2026 22:13:15 -0600 Subject: [PATCH 04/24] docs: brainstorm + spec doc-staleness prose-gap checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captures today's REFCARD.md/skills-agents.md incident (correct version numbers, stale surrounding prose) as 3 concrete Phase 7 checks to add to docs-staleness-check.sh: release-date claims, agent/skill count prose, and version-highlight staleness proxy. External research (fiberplane/drift, Vale, dosu.dev freshness scoring) confirmed no existing tool fits craft's count/version-anchored staleness model — extending the existing gate with targeted patterns, no new dependency. Co-Authored-By: Claude Sonnet 5 --- ...ORM-doc-staleness-prose-gaps-2026-08-07.md | 142 ++++++++++++++++++ ...PEC-doc-staleness-prose-gaps-2026-08-07.md | 89 +++++++++++ 2 files changed, 231 insertions(+) create mode 100644 docs/specs/BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md create mode 100644 docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md diff --git a/docs/specs/BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md b/docs/specs/BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md new file mode 100644 index 000000000..a9fe150e2 --- /dev/null +++ b/docs/specs/BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md @@ -0,0 +1,142 @@ +# BRAINSTORM: Doc-Staleness Prose Gaps (closing what count-consistency misses) + +**Date:** 2026-08-07 · **Depth:** deep · **Focus:** ops +**Branch:** dev · **Categories:** req, existing, tech, risks, scope, success + +## Origin + +Seed request (refined via `--refine`): research how doc-staleness detection is handled +elsewhere (other Claude Code plugin/skill projects, adjacent coding-agent ecosystems), then +compare against craft's own tooling to find patterns worth adopting. + +Directly triggered by two bugs found and fixed *this session*, both while +`docs-staleness-check.sh` reported GREEN: + +- `docs/REFCARD.md`'s quick-reference box had the correct version **number** (4.5.0 — + `bump-version.sh`'s literal-string target) but a stale release **date** (2026-07-17, + actually v4.1.0's) and a stale highlight line (still describing v4.1.0's `/craft:restore`, + not v4.5.0's repo-triage skill). +- `docs/skills-agents.md`'s TL;DR line said "8 specialized agents" two lines above its own + correct "2 specialized agents" — stale since the v4.0.0 folio split, never caught. + +Both are **free prose**, not the structured badges/counts `docs-staleness-check.sh`'s +count-consistency phase (Phase 7) anchors on. The gate did its job on every surface it +watches; these two just weren't watched surfaces. + +## Context Scan + +- **No existing spec on this topic.** `find`/`grep` across `docs/specs/` for + `staleness`/`drift`/`doc-track` found nothing prior — genuinely new ground for craft, + despite already having the most mature doc-tooling stack of the ~25 dev-tools sibling repos. +- **Existing tooling inventory** (all already in craft, none of it is a green-field build): + - `scripts/docs-staleness-check.sh` (35.7K) — 4-phase gate: nav completeness, count + consistency, skill/agent/cmd coverage, cross-doc freshness. Anchors on specific + badge/count patterns (`version-X.Y.Z`, `N commands`, `N skills`), not arbitrary prose. + - `scripts/doc-coverage-check.sh` — blocks release on missing REFCARD rows / mkdocs nav + entries for new commands. + - `utils/claude_md_sync.py` — CLAUDE.md-specific drift detector (documented-command + existence, error/warning/info tiers); this session's `/craft:done` run caught 1 real + error (`/craft:done` still referenced, file deleted) via this tool, not the staleness + gate. + - `utils/docs_detector.py` / `utils/docs_update_orchestrator.py` — semantic doc-update + orchestration for `/craft:docs:update`. + - None of these do free-prose semantic staleness checking today — every one of them + matches a structured pattern (a badge, a count line, a declared command name). + +## External Research (WebSearch, 2026-08-07) + +Two search passes: general doc-drift-in-CI practice, and Claude-Code-specific tooling. + +**General CI doc-drift practice:** + +- **[fiberplane/drift](https://github.com/fiberplane/drift)** — anchors markdown docs to + source code via tree-sitter + git; on a supported language, hashes a normalized AST + fingerprint (node kinds + token text) and flags the doc when the anchored code changes. + Closest match to craft's own `bump-version.sh`-touches-N-files pattern, but source-code-anchored + rather than count/version-anchored — not directly reusable (craft's staleness is prose-vs-metadata, + not prose-vs-code). +- **[dosu.dev: doc freshness scoring](https://dosu.dev/blog/score-documentation-freshness-in-ci)** — + a 0–100 freshness signal from three deterministic checks plus a semantic layer for edge + cases; the "deterministic checks first, semantic layer only for what's left" shape is the + most directly transferable idea here. +- **[jbrockSTL/doc-drift](https://github.com/jbrockSTL/doc-drift)** — LLM-in-CI stale-doc + catcher on every PR via GitHub Actions; cost-controlled via label-gating, cron batching, and + path filters rather than running on every push. +- **[Vale](https://vale.sh/)** — YAML-rule prose linter (existence/repetition/spelling + extension points); proves custom prose-pattern rules are a well-trodden, low-tooling-cost + approach — closer in spirit to extending `docs-staleness-check.sh`'s existing grep-based + phases than adopting a new dependency. + +**Claude Code / coding-agent specific:** + +- **[dosu.dev: catching doc drift with Claude Code + GitHub Actions](https://dosu.dev/blog/how-to-catch-documentation-drift-claude-code-github-actions)** — + triggers Claude Code itself as the drift-detector in CI, reading diffs and flagging + affected docs semantically. +- **[dev.to: "How Claude Skills Replaced Our Documentation"](https://dev.to/magnusrodseth/how-claude-skills-replaced-our-documentation-emi)** — + reframes skills as living docs: traditional docs rot because they're write-once/read-rarely, + but a skill the agent actually executes gets exercised constantly, so drift surfaces + immediately. Doesn't apply directly to REFCARD/skills-agents.md (reference docs, not + executable skill bodies) but validates craft's own pattern of thin command-shims pointing + at canonical skill files (already this repo's convention). + +**Takeaway:** nothing found is a drop-in replacement for `docs-staleness-check.sh` — the +external tools solve *code*-anchored drift (source changed, doc didn't) or run a full LLM +pass per PR (cost/latency for a check craft already does deterministically in seconds). The +one directly transferable idea is Vale/dosu's "deterministic pattern checks first, escalate +only what's left" shape — which is exactly what today's bugs argue for: add deterministic +prose-pattern checks for the *specific* phrase shapes that already bit us, not a general +semantic-diff engine. + +## Locked Decisions + +| # | Question | Decision | +|---|---|---| +| 1 | Trigger for this work | **Both** — the gate missed real bugs today AND external research should inform the fix, not just today's incident. | +| 2 | Integration approach | **Extend `docs-staleness-check.sh`**, not a separate scan — same gate, wider prose-pattern coverage. No new script, no new dependency. | +| 3 | Scope | **Craft only.** Craft already has the most mature doc-tooling of the ~25 dev-tools sibling repos; solve here first, consider porting the pattern later if it proves out. | +| 4 | Rigor level | **Targeted prose-pattern additions**, not general NLP/semantic staleness detection. Low false-positive risk, addresses today's exact bug class (version-box "released DATE" lines, "N specialized agents"/"N skills" free-prose count mentions, stale highlight/callout lines that repeat a phrase from a prior release). | +| 5 | External-tool adoption | **None wholesale.** No new dependency (Vale, drift, LLM-in-CI) — the deterministic-pattern-first shape is worth copying, the tools themselves aren't a fit for craft's count/version-anchored (not code-anchored) staleness problem. | + +## New Prose-Pattern Checks (Phase 7 extension) + +Concrete patterns to add to `docs-staleness-check.sh`'s count-consistency phase, each +regex-anchored the same way existing checks are (compare against the single source of truth — +`plugin.json`'s command/skill/agent counts and the current version): + +1. **"released YYYY-MM-DD" / "(released ...)" mentions** — any doc claiming a release date + for the *current* version must match `.STATUS`'s `release_date:` field. Today's REFCARD.md + bug is exactly this pattern. +2. **"N specialized agent(s)" / "N agent(s)"** free-prose mentions (excluding files that are + historical logs by convention — `VERSION-HISTORY.md`, `CHANGELOG.md` entries about past + versions) — must match the current agent count. +3. **Version "highlight"/"latest"/callout lines** (`docs/index.md`'s `!!! info "Latest: ..."`, + `docs/REFCARD.md`'s box highlight line) — flag if the line's version token matches current + but hasn't been touched in the same commit range as the last version bump (a cheap git-log + proxy for "this prose is stale even though the number is current"). + +## Test-Plan + +| Tier | Coverage | +|---|---| +| `unit` | New regex/pattern-matcher functions for each of the 3 checks above, tested against both a clean fixture and a planted-defect fixture (today's exact REFCARD.md/skills-agents.md bugs, restored as regression fixtures). | +| `dogfood` | `docs-staleness-check.sh` run against the current repo state must stay GREEN; run against a git-stash of today's pre-fix REFCARD.md/skills-agents.md must go RED on the new checks (positive control — proves the fix would have caught this exact incident). | +| `e2e` | Full `/craft:check --for release` pass unaffected in runtime (new checks stay in Phase 7's existing budget, no new phase). | +| `integration` | N/A — no cross-command data flow introduced. | +| `dependency` | N/A — no external dependency added (explicit decision #5). | + +## Documentation + +| Doc type | Needed? | +|---|---| +| Guide/reference | `[x]` `docs/reference/REFCARD-DOCS-STALENESS.md` — add the 3 new pattern checks to its existing check inventory. | +| REFCARD (main) | N/A — score <3, no new command surface. | +| Demo/GIF | N/A — score <3, no new interactive flow. | +| Mermaid | N/A — score <3, no new architecture shape (extends an existing phase, doesn't add one). | +| CHANGELOG `[Unreleased]` | `[x]` One-line entry once implemented: "docs-staleness-check.sh: 3 new prose-pattern checks (release-date claims, agent-count mentions, stale highlight lines) — closes the REFCARD.md/skills-agents.md gap found 2026-08-07." | + +## Next Step + +`/craft:plan docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md` to scope implementation +(the 3 pattern checks + fixtures + REFCARD-DOCS-STALENESS.md update) — small enough that grill +is optional (low ambiguity, no unresolved dependency), but available if any pattern's exact +regex/false-positive tradeoff needs interrogation before building. diff --git a/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md new file mode 100644 index 000000000..4bea6090e --- /dev/null +++ b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md @@ -0,0 +1,89 @@ +# Doc-Staleness Prose Gaps — Spec + +**Generated:** 2026-08-07 +**Context:** Craft Plugin v4.5.0 — extends `scripts/docs-staleness-check.sh` Phase 7 +**Sources:** [`BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md`](BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md) (6 locked decisions, external research) +**Status:** draft + +--- + +## Problem + +`docs-staleness-check.sh`'s count-consistency phase (Phase 7) anchors on specific structured +patterns — badges (`version-X.Y.Z`), declared count lines (`N commands`) — and reported GREEN +today while two real staleness bugs sat live in `docs/REFCARD.md` and `docs/skills-agents.md`: +a version box with the right number but a wrong release date and a stale highlight line +describing the prior release, and a TL;DR line claiming "8 specialized agents" two lines above +its own correct "2 specialized agents". Both are free prose the gate doesn't watch. + +## User Story + +As a maintainer running `/craft:check --for release` or `docs-staleness-check.sh` directly, I +want the gate to catch the specific prose-staleness shapes that have actually bitten this repo +(stale release dates, stale agent/skill-count mentions, stale version-highlight lines) so a +release doesn't ship with correct version *numbers* sitting next to incorrect surrounding prose. + +## Locked Decisions (from BRAINSTORM) + +| # | Decision | +|---|---| +| D1 | Extend `docs-staleness-check.sh` directly — no new script, no new dependency. | +| D2 | Scope: craft only, not the dev-tools-wide shared tooling. | +| D3 | Targeted prose-pattern regexes, not general NLP/semantic diffing. | +| D4 | No external tool adoption (Vale/drift/LLM-in-CI) — pattern-first shape borrowed, not the tools themselves. | +| D5 | 3 concrete checks (see below), each with a clean-fixture and planted-defect-fixture regression test. | + +## Scope + +### New checks (Phase 7 extension) + +1. **Release-date claims.** Any doc prose matching `released? \d{4}-\d{2}-\d{2}` (or + `\(released ...\)`) tied to the *current* version token must match `.STATUS`'s + `release_date:` field. Flag on mismatch. +2. **Agent/skill count prose.** Free-text `\d+ (specialized )?agents?` / `\d+ skills?` mentions + must match `plugin.json`'s current counts — **except** in files that are historical logs by + convention (`VERSION-HISTORY.md`, `CHANGELOG.md` entries under a past version heading, + `docs/MIGRATION-v4.md`'s "was X before the split" phrasing). Exclusion list lives in + `scripts/config/exclusions.txt` alongside the existing nav-completeness exclusions. +3. **Version-highlight staleness proxy.** For `docs/index.md`'s `!!! info "Latest: ..."` block + and `docs/REFCARD.md`'s quick-reference box highlight line: if the version token matches + current, but the file's last git-log touch predates the current version's `bump-version.sh` + commit, flag as **possibly stale content, current number** (advisory, not blocking — a + cheap proxy, not a semantic diff). + +### Out of scope + +- General semantic/LLM-based staleness detection (D3/D4). +- Porting the pattern to sibling dev-tools repos (D2) — revisit only if this proves out here. +- Any new CI workflow, cron job, or external service. + +## Acceptance Criteria + +- [ ] Checks 1 and 2 are implemented as blocking (RED) findings in Phase 7. +- [ ] Check 3 is implemented as advisory (YELLOW) — never blocks, per the existing + YELLOW/RED convention `docs-staleness-check.sh` already uses elsewhere. +- [ ] Regression fixtures restore today's exact bugs (REFCARD.md's stale date/highlight, + skills-agents.md's "8 specialized agents") and prove the new checks catch them + (planted-defect positive control, same pattern used for `test_no_live_legacy_check_skill_references`). +- [ ] Running the check against current (already-fixed) `dev` HEAD stays GREEN — no + false positive on the corrected content. +- [ ] `docs/reference/REFCARD-DOCS-STALENESS.md` documents the 3 new checks in its + existing check inventory. +- [ ] `CHANGELOG.md` / `docs/CHANGELOG.md` `[Unreleased]` gets a one-line entry. +- [ ] No new script, no new external dependency (D1/D4). + +## Test Plan + +| Tier | Coverage | +|---|---| +| `unit` | New pattern-matcher functions, tested against clean + planted-defect fixtures for all 3 checks. | +| `dogfood` | `docs-staleness-check.sh` on current `dev` HEAD → GREEN. On a git-stash of the pre-fix REFCARD.md/skills-agents.md → RED (regression proof). | +| `e2e` | `/craft:check --for release` runtime unaffected (checks stay in Phase 7's existing budget). | +| `integration` | N/A — no cross-command data flow. | +| `dependency` | N/A — no new dependency. | + +## Next Step + +`/craft:plan docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md` → routes to +`plan-orchestrator` for implementation (small scope, grill optional — low ambiguity, decisions +already locked in the BRAINSTORM). From 2ec65fe2a08c48d58e6b185debe71769c6d900d9 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Fri, 7 Aug 2026 22:17:35 -0600 Subject: [PATCH 05/24] docs: fix SPEC per adversarial review (drop falsified check, rescope check 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3 blocking findings from adversarial review, all fixed: - Check 3 (version-highlight staleness proxy) was falsified by its own target: bump-version.sh touches REFCARD.md's version line every release, so the whole-file git-log-touch proxy would never flag the exact file that caused this SPEC to exist. Dropped rather than built as a per-line proxy — not worth the complexity for an advisory-only signal until checks 1-2 prove insufficient alone. - Check 2 (agent/skill count prose) false-positived on 90+ legitimate matches (orchestration mode-limit prose, a fictional-plugin tutorial, an intentional-bug troubleshooting example) when scoped as a blanket regex. Rescoped to structured line shapes only (TL;DR lines, version boxes, badge/count-summary lines). Co-Authored-By: Claude Sonnet 5 --- ...ORM-doc-staleness-prose-gaps-2026-08-07.md | 7 ++ ...PEC-doc-staleness-prose-gaps-2026-08-07.md | 76 +++++++++++++++---- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/docs/specs/BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md b/docs/specs/BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md index a9fe150e2..a88e94261 100644 --- a/docs/specs/BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md +++ b/docs/specs/BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md @@ -99,6 +99,13 @@ semantic-diff engine. ## New Prose-Pattern Checks (Phase 7 extension) +> **Superseded by adversarial review** (2026-08-07) — the SPEC's "Review Outcome" section +> has the corrected version: check 3 (version-highlight proxy) was dropped as falsified by +> `bump-version.sh`'s own touch pattern, and check 2 was rescoped from free-text search to +> structured line shapes after a false-positive sweep found 90+ non-bug matches. This section +> is left as-is for the historical record of what was first proposed; see the SPEC for what's +> actually being built. + Concrete patterns to add to `docs-staleness-check.sh`'s count-consistency phase, each regex-anchored the same way existing checks are (compare against the single source of truth — `plugin.json`'s command/skill/agent counts and the current version): diff --git a/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md index 4bea6090e..766949f58 100644 --- a/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md +++ b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md @@ -3,10 +3,39 @@ **Generated:** 2026-08-07 **Context:** Craft Plugin v4.5.0 — extends `scripts/docs-staleness-check.sh` Phase 7 **Sources:** [`BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md`](BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md) (6 locked decisions, external research) -**Status:** draft +**Status:** draft — revised post adversarial review (see "Review Outcome" below) --- +## Review Outcome (adversarial review, 2026-08-07) + +Original draft (checks 1–3 as first written) was adversarially reviewed before build. 3 +blocking findings, all fixed in this revision — see inline `> **Review:**` notes below for +what changed and why: + +1. **Check 3's proxy was falsified by the exact incident it targets.** `bump-version.sh` + touches `docs/REFCARD.md`'s version line in every release commit — so REFCARD.md's + "last git-log touch" always coincides with the bump commit, meaning the whole-file proxy + would report "not stale" on the file that caused this SPEC to exist. **Fix: dropped + check 3 entirely** — a per-line/per-span version would work but adds real complexity for + an advisory-only signal; not worth building until a simpler check proves insufficient + (gentle-ramp, not preemptive). +2. **Check 3's stated target (`docs/index.md`) was already whole-file excluded** from Phase 7 + via `exclusions.txt`'s "curated hub page" entry — moot once check 3 is dropped. +3. **Check 2's false-positive surface was far larger than the 3-file exclusion list.** A grep + of `docs/**/*.md` found 90+ hits for count-shaped prose outside the proposed exclusions, + including legitimate non-count-of-agents phrases (`docs/guide/orch-flag-usage.md`: "2 + agents max", "4 agents" in orchestration mode-limit prose), a tutorial with an + intentionally fictional example plugin, and a troubleshooting page that intentionally + shows a wrong count to illustrate the exact bug it's teaching. **Fix: check 2 rescoped + to structured line shapes only** (TL;DR lines, quick-reference/version boxes, badge + lines) — not a blanket `\d+ agents?` search anywhere in prose. See revised check 2 below. +4. **`exclusions.txt` maintainability concern raised, accepted as a known tradeoff** — not + blocking. It's already craft's established mechanism for this class of exception (used by + nav-completeness today); rescoping check 2 to structured lines (finding 3's fix) also + shrinks how much the exclusion list needs to grow. +5. **Scope (craft-only) and acceptance criteria confirmed fine as-is** — no changes. + ## Problem `docs-staleness-check.sh`'s count-consistency phase (Phase 7) anchors on specific structured @@ -39,17 +68,31 @@ release doesn't ship with correct version *numbers* sitting next to incorrect su 1. **Release-date claims.** Any doc prose matching `released? \d{4}-\d{2}-\d{2}` (or `\(released ...\)`) tied to the *current* version token must match `.STATUS`'s - `release_date:` field. Flag on mismatch. -2. **Agent/skill count prose.** Free-text `\d+ (specialized )?agents?` / `\d+ skills?` mentions - must match `plugin.json`'s current counts — **except** in files that are historical logs by - convention (`VERSION-HISTORY.md`, `CHANGELOG.md` entries under a past version heading, - `docs/MIGRATION-v4.md`'s "was X before the split" phrasing). Exclusion list lives in - `scripts/config/exclusions.txt` alongside the existing nav-completeness exclusions. -3. **Version-highlight staleness proxy.** For `docs/index.md`'s `!!! info "Latest: ..."` block - and `docs/REFCARD.md`'s quick-reference box highlight line: if the version token matches - current, but the file's last git-log touch predates the current version's `bump-version.sh` - commit, flag as **possibly stale content, current number** (advisory, not blocking — a - cheap proxy, not a semantic diff). + `release_date:` field. Flag on mismatch. Blocking (RED). + +2. **Agent/skill count prose — structured lines only.** + > **Review:** rescoped from a blanket `\d+ agents?`/`\d+ skills?` search (which false-positived + > on orchestration mode-limit prose, a fictional-plugin tutorial, and an intentional-bug + > troubleshooting example) to specific known-good line shapes only: + - Lines matching a "TL;DR"-prefixed sentence (e.g. `docs/skills-agents.md`'s pattern). + - Lines inside a fenced quick-reference/version box (`┌─...─┐` ... `└─...─┘` block, e.g. + `docs/REFCARD.md`'s header box). + - Lines matching an explicit badge/count-summary pattern already used elsewhere + (`**N commands** · **N skills** · **N agents**`, as in `docs/QUICK-START.md`/`docs/index.md`). + + Within those line shapes only, `\d+ (specialized )?agents?` / `\d+ skills?` mentions must + match `plugin.json`'s current counts. Historical-log files (`VERSION-HISTORY.md`, + `CHANGELOG.md` past-version sections, `docs/MIGRATION-v4.md`) stay excluded via + `scripts/config/exclusions.txt`, same mechanism as today's nav-completeness exclusions — + a smaller list now that the check is line-shape-scoped, not file-wide. Blocking (RED). + +3. ~~Version-highlight staleness proxy~~ — **dropped.** + > **Review:** the proposed whole-file git-log-touch proxy is falsified by + > `bump-version.sh` itself, which touches `docs/REFCARD.md`'s version line on every + > release — the exact file this check was meant to catch would always read "recently + > touched, not stale." A correct version requires per-line/per-span touch tracking, + > which is real complexity for an advisory-only signal. Not building until checks 1–2 + > prove insufficient on their own (gentle-ramp). ### Out of scope @@ -60,9 +103,10 @@ release doesn't ship with correct version *numbers* sitting next to incorrect su ## Acceptance Criteria - [ ] Checks 1 and 2 are implemented as blocking (RED) findings in Phase 7. -- [ ] Check 3 is implemented as advisory (YELLOW) — never blocks, per the existing - YELLOW/RED convention `docs-staleness-check.sh` already uses elsewhere. -- [ ] Regression fixtures restore today's exact bugs (REFCARD.md's stale date/highlight, +- [ ] Check 2's line-shape scoping is itself tested against the 3 false-positive sources the + review found (`docs/guide/orch-flag-usage.md`, the fictional-plugin tutorial, the + intentional-bug troubleshooting page) — must NOT flag any of them. +- [ ] Regression fixtures restore today's exact bugs (REFCARD.md's stale date, skills-agents.md's "8 specialized agents") and prove the new checks catch them (planted-defect positive control, same pattern used for `test_no_live_legacy_check_skill_references`). - [ ] Running the check against current (already-fixed) `dev` HEAD stays GREEN — no @@ -76,7 +120,7 @@ release doesn't ship with correct version *numbers* sitting next to incorrect su | Tier | Coverage | |---|---| -| `unit` | New pattern-matcher functions, tested against clean + planted-defect fixtures for all 3 checks. | +| `unit` | New pattern-matcher functions, tested against clean + planted-defect fixtures for both checks, plus the 3 false-positive-source fixtures the review found for check 2. | | `dogfood` | `docs-staleness-check.sh` on current `dev` HEAD → GREEN. On a git-stash of the pre-fix REFCARD.md/skills-agents.md → RED (regression proof). | | `e2e` | `/craft:check --for release` runtime unaffected (checks stay in Phase 7's existing budget). | | `integration` | N/A — no cross-command data flow. | From 1da954cd94ab72b2ba54e7d12fb9a4bc2c042190 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Fri, 7 Aug 2026 22:25:09 -0600 Subject: [PATCH 06/24] docs: add ADHD report for doc-staleness-prose-gaps SPEC Co-Authored-By: Claude Sonnet 5 --- ...ORT-doc-staleness-prose-gaps-2026-08-07.md | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs/reports/REPORT-doc-staleness-prose-gaps-2026-08-07.md diff --git a/docs/reports/REPORT-doc-staleness-prose-gaps-2026-08-07.md b/docs/reports/REPORT-doc-staleness-prose-gaps-2026-08-07.md new file mode 100644 index 000000000..73f324ca5 --- /dev/null +++ b/docs/reports/REPORT-doc-staleness-prose-gaps-2026-08-07.md @@ -0,0 +1,75 @@ +# Report: Doc-Staleness Prose Gaps + +**Source:** [`SPEC-doc-staleness-prose-gaps-2026-08-07.md`](../specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md) +**Status:** draft — revised post adversarial review + +## tl;dr + +| Metric | Value | +|---|---| +| Checks proposed | 3 | +| Checks kept after review | 2 | +| Blocking review findings | 3 | +| New dependencies | 0 | +| New scripts | 0 | +| Acceptance criteria | 7 | +| Test tiers covered | 5 | + +## Problem + +`docs-staleness-check.sh`'s count-consistency phase (Phase 7) anchors on specific structured +patterns — badges (`version-X.Y.Z`), declared count lines (`N commands`) — and reported GREEN +while two real staleness bugs sat live in `docs/REFCARD.md` and `docs/skills-agents.md`: a +version box with the right number but a wrong release date and a stale highlight line +describing the prior release, and a TL;DR line claiming "8 specialized agents" two lines above +its own correct "2 specialized agents." Both are free prose the gate doesn't watch. + +## Locked Decisions + +| # | Decision | +|---|---| +| D1 | Extend `docs-staleness-check.sh` directly — no new script, no new dependency. | +| D2 | Scope: craft only, not the dev-tools-wide shared tooling. | +| D3 | Targeted prose-pattern regexes, not general NLP/semantic diffing. | +| D4 | No external tool adoption (Vale/drift/LLM-in-CI) — pattern-first shape borrowed, not the tools themselves. | +| D5 | Concrete checks, each with a clean-fixture and planted-defect-fixture regression test. | + +## What's Being Built (post-review) + +1. **Release-date claims** — prose matching `released? YYYY-MM-DD` tied to the current version + must match `.STATUS`'s `release_date:` field. Blocking (RED). +2. **Agent/skill count prose, structured lines only** — TL;DR lines, quick-reference/version + boxes, and badge/count-summary lines must match `plugin.json`'s current counts. Historical-log + files stay excluded via `scripts/config/exclusions.txt`. Blocking (RED). + +## Review Outcome — Adversarial Review Findings + +| Finding | Problem | Fix | +|---|---|---| +| Check 3's proxy falsified by its own target | `bump-version.sh` touches `docs/REFCARD.md`'s version line on every release, so a whole-file "last git-log touch" proxy would always read "not stale" on the exact file that caused this SPEC to exist | Dropped check 3 entirely — a per-line proxy would work but isn't worth the complexity for an advisory-only signal until checks 1–2 prove insufficient | +| Check 3's target already excluded | `docs/index.md` was already whole-file excluded from Phase 7 via `exclusions.txt`'s "curated hub page" entry | Moot once check 3 dropped | +| Check 2's false-positive surface far larger than proposed exclusions | A grep of `docs/**/*.md` found 90+ hits outside the 3-file exclusion list, including orchestration mode-limit prose, a fictional-plugin tutorial, and a troubleshooting page that intentionally shows a wrong count | Rescoped from blanket `\d+ agents?` search to structured line shapes only (TL;DR / version box / badge lines) | + +## Acceptance Criteria + +- [ ] Checks 1 and 2 implemented as blocking (RED) findings in Phase 7. +- [ ] Check 2's line-shape scoping tested against the 3 false-positive sources the review found — must NOT flag any of them. +- [ ] Regression fixtures restore today's exact bugs (REFCARD.md's stale date, skills-agents.md's "8 specialized agents") and prove the new checks catch them. +- [ ] Current (already-fixed) `dev` HEAD stays GREEN — no false positive on corrected content. +- [ ] `docs/reference/REFCARD-DOCS-STALENESS.md` documents the 2 new checks. +- [ ] `CHANGELOG.md` / `docs/CHANGELOG.md` `[Unreleased]` gets a one-line entry. +- [ ] No new script, no new external dependency. + +## Test Plan + +| Tier | Coverage | +|---|---| +| `unit` | New pattern-matcher functions, tested against clean + planted-defect fixtures for both checks, plus the 3 false-positive-source fixtures the review found for check 2. | +| `dogfood` | `docs-staleness-check.sh` on current `dev` HEAD → GREEN. On a git-stash of the pre-fix REFCARD.md/skills-agents.md → RED (regression proof). | +| `e2e` | `/craft:check --for release` runtime unaffected. | +| `integration` | N/A — no cross-command data flow. | +| `dependency` | N/A — no new dependency. | + +## Next Steps + +1. `/craft:plan docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md` — routes to `plan-orchestrator` for implementation. Grill optional (low ambiguity, decisions already locked). From 7b1d80e085030b6b344639bdaa9939d119d7d723 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Fri, 7 Aug 2026 22:34:17 -0600 Subject: [PATCH 07/24] docs(index): fix install instructions and stale command counts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quick Start only documented the dev-only local-plugins symlink path, with no real install instructions for external users. Added the two actual install paths (Homebrew, Data-Wise marketplace) and moved the symlink instructions under a "developing craft itself" note. Also fixed 5 stale "47 commands" mentions (current: 48) scattered through prose/callouts — the exact class of bug docs-staleness-check.sh doesn't catch (free prose, not a structured badge/count line); see SPEC-doc-staleness-prose-gaps-2026-08-07.md for the planned gate fix. Co-Authored-By: Claude Sonnet 5 --- docs/index.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/index.md b/docs/index.md index f98d933a1..1e86f90b1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,7 +16,7 @@ > > - **What:** Full-stack developer toolkit with 48 commands, 2 AI agents, and 41 auto-triggered skills > - **Why:** Automate documentation, testing, git workflows, CLAUDE.md management, and site creation with one command -> - **How:** Install via `claude plugin install craft@local-plugins` +> - **How:** Install via `brew install data-wise/tap/craft` > - **Next:** Run `/craft:do "your task"` and let AI route to the best workflow > Full-stack developer toolkit for Claude Code — 48 commands, 2 agents, 41 skills with smart orchestration and ADHD-friendly workflows @@ -25,7 +25,7 @@
-- :rocket:{ .lg .middle } **47 Commands** +- :rocket:{ .lg .middle } **48 Commands** Smart commands, docs, code, testing, git, CI, architecture, distribution (Homebrew), planning, and workflow automation — all in one toolkit. Docs-authoring/site-publishing commands live in the sibling [`folio`](https://github.com/Data-Wise/folio) plugin. @@ -54,11 +54,19 @@ ## Quick Start ```bash -# Install via Claude Code -claude plugin install craft@local-plugins +# Install via Homebrew (recommended) +brew install data-wise/tap/craft -# Or create symlink -ln -s ~/projects/dev-tools/claude-plugins/craft ~/.claude/plugins/craft +# Or via the Data-Wise marketplace, inside Claude Code +claude plugin marketplace add Data-Wise/claude-plugins +claude plugin install craft@data-wise +``` + +**Developing craft itself?** Clone the repo and symlink it in instead: + +```bash +git clone https://github.com/Data-Wise/craft.git ~/projects/dev-tools/craft +ln -s ~/projects/dev-tools/craft ~/.claude/plugins/craft ``` **First command:** @@ -70,7 +78,7 @@ ln -s ~/projects/dev-tools/claude-plugins/craft ~/.claude/plugins/craft The universal `/craft:do` command routes your task to the best workflow automatically. !!! success "Quick Win: Try It Now" - Run `/craft:hub` to see all 47 commands organized by category - takes 5 seconds and shows everything craft can do. + Run `/craft:hub` to see all 48 commands organized by category - takes 5 seconds and shows everything craft can do. ## Feature Highlights @@ -110,7 +118,7 @@ The universal `/craft:do` command routes your task to the best workflow automati ## API Reference -Reference documentation for all 47 Craft commands: +Reference documentation for all 48 Craft commands:
@@ -134,7 +142,7 @@ Reference documentation for all 47 Craft commands: **Quick Links:** -- [All 47 Commands, Indexed by Category](API-REFERENCE-COMMANDS.md) +- [All 48 Commands, Indexed by Category](API-REFERENCE-COMMANDS.md) - [Parameters Quick Reference](reference/COMMAND-PARAMETERS.md) - [Error Recovery Guide](reference/ERROR-SCENARIOS.md) - [Real-World Examples](reference/COMMAND-EXAMPLES.md) @@ -181,7 +189,7 @@ Reference documentation for all 47 Craft commands: - :books:{ .lg .middle } **[Commands Overview](commands/overview.md)** - All 47 commands organized + All 48 commands organized - :sparkles:{ .lg .middle } **[Skills & Agents](skills-agents.md)** From 7d4b069fe4da8f8ccc8f7621720fa8e3039f4079 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Fri, 7 Aug 2026 22:39:06 -0600 Subject: [PATCH 08/24] docs(dist): document homebrew-tap's revision-bump rule for content-only edits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit homebrew-tap#211 (craft's stale unprotect-caveat fix) tripped the Formula Drift Guard CI check on first push — content changed with no version/revision bump, so brew upgrade would never have detected it. Documents the rule in /craft:dist:homebrew and the release skill's homebrew reference so it doesn't have to be rediscovered by trial and error next time a tap formula gets a content-only fix outside a version release. Co-Authored-By: Claude Sonnet 5 --- commands/dist/homebrew.md | 10 ++++++++++ skills/release/references/homebrew.md | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/commands/dist/homebrew.md b/commands/dist/homebrew.md index 3bad8a2b9..33024a5ca 100644 --- a/commands/dist/homebrew.md +++ b/commands/dist/homebrew.md @@ -81,6 +81,16 @@ Detects project type and generates appropriate formula or cask: > **Plugin formulas** are generated by the [homebrew-tap formula generator](https://github.com/Data-Wise/homebrew-tap/tree/main/docs/generator), which supports `libexec_copy_map` for nested source directories and generates the 3-step `post_install` pattern (schema cleanup, timed install, registry sync). +> **Content-only formula edits need a `revision` bump.** Editing a formula's non-version content +> in `homebrew-tap/generator/manifest.json` (e.g. fixing caveat text) without a version bump +> will not be picked up by `brew upgrade` on already-installed machines — Homebrew only checks +> `version`/`revision`, never diffs formula body content. `homebrew-tap`'s "Formula Drift Guard" +> CI check catches this and blocks the PR (`generator/check-revision-bump.sh`); fix by adding/ +> incrementing `"revision": N` on the formula's manifest entry, then +> `python3 generator/generate.py `. This is separate from `/release` Step 10a's +> version+sha256 bump above, which doesn't need a revision field (the version change itself is +> what `brew upgrade` detects). + ### Example Output ``` diff --git a/skills/release/references/homebrew.md b/skills/release/references/homebrew.md index f01d66ed6..02f9b3f2a 100644 --- a/skills/release/references/homebrew.md +++ b/skills/release/references/homebrew.md @@ -132,6 +132,12 @@ fi Skip if no local tap exists — the GitHub Actions workflow (`homebrew-release.yml`) handles tap updates automatically on release trigger. +> **Editing tap formula content outside a version release?** (e.g. fixing caveat text in +> `generator/manifest.json` directly, not via a `/release` version bump) — that change needs a +> `revision` bump in the formula's manifest entry, or `homebrew-tap`'s Formula Drift Guard CI +> check will block the PR (and `brew upgrade` would silently never detect the change on +> already-installed machines). See `/craft:dist:homebrew formula`'s docs for the full rule. + #### Verify Homebrew Release Workflow After the GitHub release is created, verify the `homebrew-release` workflow succeeded: From 88af47624ff3df4404af6fe05cdea67aa4742454 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Fri, 7 Aug 2026 22:41:52 -0600 Subject: [PATCH 09/24] docs: note homebrew-tap caveat-prose staleness as SPEC follow-up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same blind spot as this SPEC's craft-side checks (fixed number, stale surrounding prose) exists in homebrew-tap's manifest.json caveats_extra text — confirmed live via the /craft:git:unprotect caveat bug fixed in homebrew-tap#211. Out of scope here (D2: craft only) — recorded so it isn't silently forgotten. Co-Authored-By: Claude Sonnet 5 --- .../SPEC-doc-staleness-prose-gaps-2026-08-07.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md index 766949f58..abbfd78b8 100644 --- a/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md +++ b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md @@ -100,6 +100,19 @@ release doesn't ship with correct version *numbers* sitting next to incorrect su - Porting the pattern to sibling dev-tools repos (D2) — revisit only if this proves out here. - Any new CI workflow, cron job, or external service. +### Follow-up (found 2026-08-07, not in this SPEC's scope) + +`homebrew-tap`'s `generator/manifest.json` has the same prose-staleness blind spot, in a +different repo: `command_count` is auto-synced by `homebrew-release.yml` on every version +release, but each formula's `caveats_extra` fixed prose (command names, feature bullets) is +hand-authored and never re-verified — confirmed live when craft's own caveats text still +referenced the removed `/craft:git:unprotect` command (fixed in `homebrew-tap#211`). Neither +`check-drift.sh` (formula-matches-manifest) nor `check-revision-bump.sh` (content-changed-needs- +revision) checks prose *accuracy*, only structural consistency. This SPEC's checks 1–2 don't +reach `homebrew-tap` (out of scope per D2 — craft only). If this pattern proves out here, +consider a parallel prose-accuracy check in `homebrew-tap` itself (different repo, different +owner of that decision) — not assumed, not scheduled. + ## Acceptance Criteria - [ ] Checks 1 and 2 are implemented as blocking (RED) findings in Phase 7. From 91929479288bb9996274de31e7152d9be679a429 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Fri, 7 Aug 2026 22:43:27 -0600 Subject: [PATCH 10/24] =?UTF-8?q?docs:=20session=20close-out=20=E2=80=94?= =?UTF-8?q?=20post-release=20doc=20audit=20+=20homebrew-tap=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records this session's continuation work: 3 stale-prose doc bugs found and fixed, a cookbook recipe added, SPEC-doc-staleness-prose-gaps brainstormed/spec'd/adversarially reviewed (not yet built), and homebrew-tap#211 merged (stale caveat text + required revision bump). Co-Authored-By: Claude Sonnet 5 --- .STATUS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.STATUS b/.STATUS index 8c4b8d037..e0d054ca9 100644 --- a/.STATUS +++ b/.STATUS @@ -2,8 +2,8 @@ status: Active version: 4.5.0 progress: 100 release_date: 2026-08-07 -milestone: **v4.5.0 SHIPPED 2026-08-07** — PR #329 (dev→main merge, tag v4.5.0, MINOR): new `repo-triage` skill (41st skill) batch-grounds open GitHub issues and stale worktrees/branches against current repo state, offering confirmed (never automatic) deletion/closure — built via brainstorm→grill (`GRILL-repo-triage-2026-08-07.md`, 2 factual corrections)→dispatched ORCHESTRATE worktree, code-reviewed (8 findings, all fixed pre-merge: jq-null pr_state bug, swallowed git-diff failures, not-merged-branch passthrough, tracked ORCHESTRATE artifact, stale skill-count prose, duplicated evidence-normalization + fence-extraction logic consolidated into `classifier_loader.py`, cross-repo file-check bug), merged PR #328 squash to dev first. Also shipped: 8 new ADHD-friendly ecosystem-tool cheat-sheet tutorials (Codex, Remember, i-have-adhd, Token Optimizer, Security Guidance, Claude HUD, Dropbox, Agent Skills) under a new nested `Ecosystem Tool Cheat Sheets` mkdocs subsection (`SPEC-ecosystem-tool-tutorials-2026-08-07.md`), plus a `docs/commands/hub.md` nav-completeness fix (`TUTORIAL-opencode-plugins.md` was missing from mkdocs.yml). Pre-flight found 2 non-blocking local-only failures (not regressions, CI confirmed green on the same commit): `test_branch_guard_dogfood.py` (the installed hook is a symlink to the sibling `cc-config` repo, out of scope per cross-repo permission boundary) and `test_no_live_legacy_check_skill_references` (was walking gitignored `.remember/` via raw filesystem `rglob()` — fixed to scope to `git ls-files`, genuine bug, fixed pre-release). Full suite 2632 passed/0 failed (post-fix); `pre-release-check.sh 4.5.0` clean; docs-staleness GREEN; live site + Homebrew formula + aggregator sync (claude-plugins PR #46, auto-merged) all confirmed v4.5.0; `verify-surfaces.sh` needed a local `homebrew-tap` `git pull` + `brew upgrade` + `claude plugin update` refresh before reporting ALIGNED (known pattern — the script reads the local tap checkout, not live GitHub). KNOWN, NOT FIXED: `aggregator-sync.yml` returns 404 by that exact workflow name in `claude-plugins` (the release skill's own docs name a workflow that doesn't exist under that filename) — propagation still worked via some other mechanism (PR #46 auto-merged), doc/workflow-name discrepancy left unswept, out of scope for this release. Release: https://github.com/Data-Wise/craft/releases/tag/v4.5.0 · PR: https://github.com/Data-Wise/craft/pull/329, https://github.com/Data-Wise/craft/pull/328. Prior: **v4.4.2 SHIPPED 2026-07-27** — PR #318 (dev→main merge, tag v4.4.2, PATCH): the one fix merged to dev since v4.4.1 (PR #317, closes #316). `preflight-check` skill made reachable by its declared identity — it lived at the pre-rename `skills/check/` location while declaring `name: preflight-check`, so directory-based skill discovery couldn't find it; moved to `skills/preflight-check/`, `/craft:check`'s public argument contract unchanged. New recursive fixture-backed identity tests inventory every command/skill directory-vs-frontmatter mismatch and command/skill name collision plugin-wide, rejecting new drift; 8 pre-existing mismatches + 3 intentional shim collisions ledgered as owned debt through v5.0.0. Test automation now targets stable Python 3.14 (`.python-version` pin, 5 GitHub Actions workflows aligned, drift-guard policy test) — the local-interpreter assertion in that test was found too brittle on review (hard-failed for any contributor not on 3.14) and fixed to skip instead before merge. `docs/NEWS.md`/`docs/index.md`'s own v4.4.2 release-note prose initially contained the literal `skills/check/` string, which the new #317 regression guard (`test_no_live_legacy_check_skill_references`) correctly caught as a repo-wide scan, not scoped to code — rephrased without the literal path, second commit. Full suite 2627 passed/0 failed/52 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.2` clean (10/10); docs-staleness GREEN; live site + Homebrew formula (fresh sha256) + aggregator sync + Craft MCP Release all confirmed v4.4.2; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.2 · PR: https://github.com/Data-Wise/craft/pull/318, https://github.com/Data-Wise/craft/pull/317. Prior: **v4.4.1 SHIPPED 2026-07-26** — PR #315 (dev→main merge, tag v4.4.1, PATCH): post-4.4.0 fixes only, no features. `scripts/branch-guard.sh`'s `2>&1`/`1>&2` false-positive (a command like `git commit -m "...(7 -> 9)" 2>&1` misdetected as file creation) was fixed live on the installed hook earlier this session but never ported to the repo's canonical copy, leaving `test_repo_copy_matches_installed` red and the bug reintroducible on reinstall — ported with 2 regression tests (implements `GRILL-rtk-hook-safety-close-out-2026-07-26.md` decision #3). `docs/help/refine-flag.md` and `docs/reference/COMMAND-PARAMETERS.md` fixed for PR #313's 9-command declarer set (was still 7, missing `/craft:plan`/`/craft:smart-help`) — new dogfood test guards against recurrence. `docs/index.md`'s "Latest" callout and `docs/NEWS.md` synced (both still described v4.3.0's content under a v4.4.0 label; NEWS.md had no v4.4.0 entry at all). Also triaged 7 remaining open issues (#228, #213, #199, #170, #169, #133, #108): closed #108/#213/#133, re-scope comments on #228/#170/#169, confirmed #199 valid. Full suite 2654 passed/0 failed/16 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.1` clean; docs-staleness GREEN; live site + Homebrew formula confirmed v4.4.1; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.1 · PR: https://github.com/Data-Wise/craft/pull/315. Prior: **v4.4.0 SHIPPED 2026-07-26** — PR #314 (dev→main merge, tag v4.4.0, MINOR): the one `feat:` commit since v4.3.1. `smart-help` declares `--refine` (D7, PR #313) — opt-in, default-OFF, closing the last dangling item on #268's prompt-refiner umbrella. `prompt-refiner`'s Default Policy table made exhaustive and re-keyed on command file path — fixed 3 stale display names, added 2 live callers it was silently missing (`commands/plan.md`, `commands/arch/plan.md`); a new dogfood test (`test_refine_default_policy_table_exhaustive`) guards against future drift. #268's other two proposed items (named destinations, `--scope global`) and #266 (multi-round grill mode) were grilled then killed on adversarial review before implementation — see `docs/specs/GRILL-prompt-refiner-remaining-2026-07-26.md`'s amendment; #266 closed pointing at savant's `edit-loop` (shipped 2026-07-13), which already solves the observed pattern with tooling craft structurally lacks. Full suite 2618 passed/0 failed/49 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.0` clean (10/10); docs-staleness GREEN; live site + Homebrew formula + aggregator sync all confirmed v4.4.0. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.0 · PR: https://github.com/Data-Wise/craft/pull/314, https://github.com/Data-Wise/craft/pull/313. Prior: **v4.3.1 SHIPPED 2026-07-25** — PR #312 (dev→main merge, tag v4.3.1, PATCH): two bug fixes merged to dev since v4.3.0, both traced to the same session. `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281) surfaced in the guard-bypass-marker confirms themselves (#309, PR #310) — the documented "Claude writes `.claude/allow-once`" one-shot flow was circular for the one case where the approved action IS creating/editing that marker; added the env-var hint to the three marker-specific confirms, reconciled both branch-guard reference docs. Bare-`vX.Y.Z` version-grep bug class swept repo-wide (PR #311) — the same false positive that broke the Homebrew release gate twice (v4.2.0, v4.3.0; fixed for `docs/index.md` in #308) also affected `docs.yml`'s live-site poll, `verify-surfaces.sh`'s `resolve_docs_site`, `version-sync-precommit.sh`'s blocking `.STATUS` pre-commit check, and manual release-verification docs — all re-anchored to the actual version signal instead of the first bare match, each with a regression test verified via planted-defect positive control. Full suite 2650 passed/0 failed/16 skipped/1 xfailed (tracked)/1 xpassed (known perf flake); `pre-release-check.sh 4.3.1` clean (10/10); docs-staleness GREEN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.1 · PR: https://github.com/Data-Wise/craft/pull/312, https://github.com/Data-Wise/craft/pull/310, https://github.com/Data-Wise/craft/pull/311. Prior: **v4.3.0 SHIPPED 2026-07-25** — PR #307 (dev→main merge, tag v4.3.0): `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281, PR #305) closing the guard-bypass-marker creation deadlock, reusing the #168 env-var pre-authorization pattern; new advisory-only `reference-scope-guard.sh` hook (#286, PR #306) warning on non-conforming filenames under `~/.claude/reference/`, sharing `install-guards.sh`/`guards.json` with `branch-guard`/`no-switch-guard`. Full suite 2648 passed/0 failed. **Homebrew Release recurrence:** the exact same `pre-release-check.sh` false positive that broke v4.2.0's automated tap push (worked around manually then) broke v4.3.0's too — `docs/index.md`'s version check grepped the first bare `vX.Y.Z` string in the file, landing on a historical "since v4.0.0" migration note instead of the actual version badge. Fixed at the source this time (PR #308, fast-tracked dev→main): matched the badge pattern specifically (`version-X.Y.Z`), same as the adjacent README.md check. Re-dispatched `homebrew-release.yml` via `workflow_dispatch --ref main -f version=4.3.0` (the original `release`-event run had checked out the tag commit before the fix existed, so a plain rerun would have failed identically) — all 3 jobs green, tap formula confirmed live (`v4.3.0.tar.gz`, fresh sha256). Craft MCP Release + Aggregator Sync both green from the original release event. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.0 · PR: https://github.com/Data-Wise/craft/pull/307, https://github.com/Data-Wise/craft/pull/308. Prior: **v4.2.0 SHIPPED 2026-07-19** — PR #296 (dev→main merge, tag v4.2.0): **BREAKING:** `/craft:done` renamed to `/craft:finish` (ADR-006) — matches the existing zsh `finish` alias, no back-compat shim, command count unchanged (48, a rename not an addition); `/craft:restore` spec/report status corrected DRAFT→SHIPPED (had already shipped in v4.1.0 when written). Full suite 2649 passed/1 failed (a genuine broken-link regression from the rename sweep, `docs/commands/brief.md` still linked pre-rename `done.md` — caught by the test run itself, fixed same-commit)/10 skipped. Also merged to dev this cycle (not yet in a tagged release): PR #297 (`utils/docs_detector.py`'s `_extract_command_name()` truncated multi-hyphen command names past a 500-char read window, bumped to 2000), PR #298 (extends `/craft:finish` Step 1.10.5 with a `~/.claude/projects//memory/`+`.remember/` staleness check, 14-day threshold, report-only), and a `docs/reports/` folder established as the `/savant:report` output convention (moved `REPORT-craft-restore-2026-07-17.md` there, added to `scripts/config/exclusions.txt` alongside `docs/specs/`). **Homebrew:** v4.2.0's automated tap-formula push failed — `homebrew-tap`'s `main` gained branch protection (PR + 2 required checks) since `homebrew-release.yml` was last touched, so its direct `git push origin main` was rejected (GH006); recovered manually via `homebrew-tap`#164, which surfaced 2 more CI-gate bugs fixed directly in that repo (#165: `check-revision-bump.sh` false-positived on URL-tag-versioned formulas like craft's, no explicit `version` field to read; #170: the ".STATUS conflict markers" required check was path-filtered to only `.STATUS` changes, so it could never fire — and the PR stayed BLOCKED forever — on formula-only PRs). craft's own workflow fixed to match (PR #299: push a bot branch + open/auto-merge a PR instead of pushing directly), landing independently from a background-dispatched session whose worktree had gone stale after only completing 1 of the 2 related craft/homebrew-tap fixes — recovered by inspecting the worktree directly rather than trusting the dispatch's completion state. Release: https://github.com/Data-Wise/craft/releases/tag/v4.2.0 · PR: https://github.com/Data-Wise/craft/pull/296. Prior: **v4.1.0 SHIPPED 2026-07-17** — PR #295 (dev→main merge, tag v4.1.0): `/craft:restore` (NEW root command, combines `dev/git` skill's mode-aware git-activity recap with `adhd-workflow`'s `.STATUS`-based session recap into one read-only entry point; replaces the deleted `git-recap`/`recap` commands; no `--sync`; a cross-plugin `/restore` dispatcher to `savant:restore` was investigated and explicitly descoped — router can't dispatch cross-plugin) + `/craft:refine` confirm-flow fix (a 2026-07-01 design — fenced refined-prompt block + 4-way confirm: Execute now/Copy for elsewhere/Edit first/Skip — was locked but never implemented; now shipped across all 5 `--refine` callers + standalone, closing the "asks for acceptance without printing first" bug) + full post-v4.0.0 doc-staleness cleanup (47→48 count-drift sweep across ~35 files, mermaid health score 82.3→100 on new diagrams, `docs-staleness-check.sh` RED→GREEN). New architecture docs: `docs/architecture/craft-restore-pipeline.md`, `docs/architecture/prompt-refiner-pipeline.md`. Homebrew formula PR data-wise/homebrew-tap#153 open (tested locally: `brew style`/`brew audit --strict`/`brew install` all clean, 48 commands). Full suite 2650 passed/0 failed. Release: https://github.com/Data-Wise/craft/releases/tag/v4.1.0 · PR: https://github.com/Data-Wise/craft/pull/295. Prior: **v4.0.0 SHIPPED 2026-07-16** — PR #294 (dev→main merge-commit `678e4d4a`, tag v4.0.0): craft docs-authoring surface extracted to the standalone `folio` plugin (folio v1.0.0 released FIRST, per the rollback runbook's ordering). Counts 94→47 commands / 45→40 skills / 8→2 agents. Homebrew formula regenerated with real values (sha256, command_count=47) via homebrew-release.yml (3/3 jobs green); `brew audit`/`brew upgrade` verified clean for both craft and folio formulas. Post-release docs sweep (same session): commands/hub.md + its docs/ mirror had drifted badly (stale example counts, 2 stray text artifacts, a main-menu ASCII template whose header counts didn't match listed items and referenced commands moved to folio or folded into skills); rewrote to match the real 47-command roster. API-REFERENCE-COMMANDS.md (1850 lines, last synced v1.24.0) converted to a thin index into docs/commands/*.md + docs/REFCARD.md rather than re-syncing a third documentation surface indefinitely; DEPENDENCY-ARCHITECTURE.md left untouched (its craft-vs-folio ownership question is still open, test_integration_dependency_system.py:38). Full pytest suite 2641 passed/0 failed. KNOWN, NOT FIXED: docs/REFCARD.md has the same staleness pattern across multiple sections (Site/Distribution/CI/CD subtotals, 20+ stale `/craft:git:worktree` examples for a command folded into the dev/git skill) — flagged, not yet swept. Folio's Aggregator Sync workflow fails by design (folio is a new plugin, never added to the shared Data-Wise aggregator 's marketplace.json — aggregator-sync.sh deliberately refuses to silently add unknown plugins; adding folio there is a separate, reviewed decision). CP-4 tasks/ cleanup: craft's tasks/ deleted directly on dev (unprotected); folio's tasks/session-plan-T2.4-CP2.md removed via PRs #15 (dev) and #16 (main) rather than a direct-branch delete (blocked by the permission classifier). Release: https://github.com/Data-Wise/craft/releases/tag/v4.0.0 · PR: https://github.com/Data-Wise/craft/pull/294. Prior: **v4 FOLIO SPLIT MERGED to dev 2026-07-16** — PR #290 (merge-commit `4af10f27d`, phase history preserved for the eventual v4 release tag): craft's docs-authoring surface extracted to the standalone `folio` plugin. **Counts 95/45/8 → 47/40/2.** NOT yet released (v2.61.2 still the current tag); `feature/folio-split` worktree + branch PRESERVED — Phase 4 (folio `/folio:do` + coordinated release, folio first / craft tag LAST) rides it. Phase 3.6 (router consolidations) CLOSED at **1 router of 5 planned**: `code:audit` shipped shrunk 5 cmds→2 (the `audit-router` skill, D12); `orch` (D11), `arch` (D6), and `ci` (D13) all DROPPED after reading the command bodies in full; `plan:feature` stayed excluded (pre-existing D2 lock). Root cause of the 4 drops, recorded in `docs/specs/GRILL-phase-3-6-router-consolidation-2026-07-15.md`: **the grill locked load-bearing decisions from line counts and flag names without reading the bodies** — every family that looked cohesive from surface metrics collapsed on a full read. D13 also withdrew its own planned `repo`-flag normalization as a premise artifact (no validator enforces cross-family flag consistency; the distinguishing test is *find the thing that fails if it's wrong*). Real defects fixed: 4 `category:` metadata mismatches (`ci/{fix,local}.md` declared `code`; `orch/{drive,workflow}.md` declared `orchestrate` against a nonexistent dir) — flagged by `help_file_validator._check_category_mismatch`, live-wired via `docs_update_orchestrator` → `/craft:docs:update`; a repo-wide sweep found the `orch` pair (pre-existing on dev). **Repo-wide category mismatches now 0.** T3.4 command floor 60→40 (`ci.yml:92`): the floor is a mass-deletion guardrail and had gone stale TWICE (86→60) while the real count fell 94→70→47, leaving it ABOVE the surface and hard-failing CI every run; rationale now commented in-file. `tests/test_git_shim_correctness.sh` INVERTED (not deleted) — it asserted `commands/git/*.md` were thin shims, but T3.5.2 completed that migration by deleting all 11, making the assertions unsatisfiable (7 FAIL on the branch, 21/21 on dev); it now guards the migration's END STATE (shims stay deleted · Operations 1–12 survive in `skills/dev/git/SKILL.md` · `issue-check` stays live · no unsanctioned command reappears), verified by 3 planted defects. `/craft:git:issue-check` KEPT as a live command (NOT folded into the git skill, reversing the initial plan): `tests/test_issue_check_unit.py` extracts its `classify_issue()` block from `commands/git/issue-check.md` and `exec()`s it, so the command file is the classifier's single source of truth — the same constraint as `commands/ci/triage.md` (D13). Git's directory-rename detection had silently relocated dev's new command into `skills/dev/git/references/` during the merge, which would have broken that test. Merge resolved 34 conflicts (~30 count-cascade, dev 95/45/8 vs branch 46/40/2) — every tracked count recomputed mechanically via `bump-version.sh --counts-only` rather than hand-resolved; the guard-script conflicts expected in `no-switch-guard.sh`/`install-guards.sh`/`version-check.sh` did NOT materialise. Prior: **PR #291 MERGED to dev 2026-07-16** (squash `88b221da9`): `ci-bash-suites` validator (`.claude-plugin/skills/validation/ci-bash-suites.md`) — closes the **gate-divergence** gap that caused two of #290's three CI blockers. `/craft:check` never ran the shell suites CI invokes directly because pytest doesn't collect them, so the local gate reported green for a surface it never touched; T3.5.2 shipped a regression citing "full pytest 2575 passed" while a CI-required bash suite had been red for days. Derives its suite list FROM `.github/workflows/*.yml` rather than hardcoding (craft has 22 `tests/*.sh`, CI runs **6** — the parse corrected a 4-suite assumption held all session); advisory in default mode, blocking in `release`, per the release-gated validator contract (`CRAFT_MODE` can't distinguish `--for pr` from `--for commit`). Verified by replaying the REAL T3.5.2 bug (pre-fix suite restored from `d41de18ee`) → caught. No count cascade (validators sit outside the counted surface). Also 2026-07-16: two stale claims fixed in `docs/commands.md` (`5034aef7d`) — a dry-run tip saying "46 commands" (the old TOTAL pasted into a dry-run claim; now 27) and "46 commands for documentation generation, validation, and site management" (now 2 — that sentence described a surface craft no longer has). Local `~/.claude/hooks/branch-guard.sh` re-synced via `install-guards.sh` (#290 merged the v4 hook text into dev, so the installed hook predated the repo and was advising a DELETED command, `/craft:git:guard disable branch-guard`) — this cleared the last known local test failure; dogfood 51 passed/0 failed. ⚠️ KNOWN, NOT FIXED: `docs/commands.md:10`'s "17 Categories" list is stale (`discovery-usage` demoted, `orchestrate`→`orch`, no `workflow` dir) but "category" has two defensible definitions that disagree — frontmatter-declared gives 10 (…`smart`), directories give a different 10 (…`utils`), and the current list matches neither; left for a decision rather than guessed at. Prior: Guard-hardening arc SHIPPED to dev 2026-07-15 — PR #287 (cumulative-cwd cd-target resolution, both guard hooks), PR #288 (guards.json write-race mkdir-lock, both writers), PR #289 (plan-orchestrator dispatch hardening); full detail in the 2026-07-15 Session Recap below. Counts unchanged 95/45/8; NOT yet released. Prior: PR #283 SHIPPED to dev 2026-07-14 (squash `057ea6785`): `/craft:git:issue-check ` — before implementing a fix requested by an open GitHub issue, checks whether the issue's premise still holds against current code (structured verdict: valid/moot/unclear, always cited evidence, never cached, advisory-only). Also gates `/craft:orch:drive` with a cheap `#NNN`-citation pre-filter and corrects `/recap`'s pointer copy. Built via `/craft:brainstorm` → `/craft:grill` (9 locked decisions in `docs/specs/GRILL-github-attention-triage-2026-07-14.md`; adversarial-review pass caught and corrected a factually-wrong dogfood target — issue #199 assumed moot, actually still open) → `/craft:plan` (plan-orchestrator, `ORCHESTRATE-github-attention-triage.md`) → `orchestrate-dispatch`. Dispatched agent completed all 5 phases correctly but silently failed to commit Phase 5 or update its own checkboxes before stopping mid-sentence — caught by the hang-detection cross-check (notification text vs. ORCHESTRATE file state), not a code failure; recovered by verifying git log + a fresh full suite run, fixing 5 newly-stale "94 commands" doc refs the count-cascade tooling missed, then committing + correcting checkboxes. PR CI initially failed on a genuine regression (broken relative link in the ORCHESTRATE file's own header, fixed same session) plus the 3 already-known `test_v115_adhd_enhancements` pre-existing failures; merged via `--admin` once confirmed unrelated. Counts 94→95/45/8; NOT yet released (v2.61.2 still current tag). Prior: Docs/website audit COMMITTED to dev 2026-07-09 (`d6aaa9096`) — 264 deleted-command refs stripped across 40 files, 5 whole-docs archived to `docs/archive/deleted-commands-v3/`, `mkdocs build --strict` clean. FOLIO SPLIT PLANNED (new initiative, supersedes dropped Phase 3): grill ledger `docs/specs/GRILL-folio-split-2026-07-09.md` (5 locked: tools+docs-standards scope · caller-based cut · drop DOCS from /do+hub · dup count tooling · history-preserving subtree, craft v4/folio v1) + `docs/plans/ORCHESTRATE-folio-split.md` (5 phases, P0 gates). P0 caller-audit RAN via dynamic Workflow (7 agents, adversarially verified, partition_sound): STAYS = docs:update + docs:changelog + site:deploy-as-shell; MOVES = 26; flagged docs:claude-md:* trio as craft-internal governance (likely stays) — partition NOT yet recorded into the plan docs. EXECUTION STARTED 2026-07-09 ("build auto"): worktree ~/.git-worktrees/craft/feature-folio-split live (T1.1 done, ORCHESTRATE + tasks/ moved to branch root); T1.2 folio repo creation gated on ask. ⚠️ ACCOUNT CI: zero-CI mode active across private repos since 2026-07-09 (craft is public = unaffected/free) — see memory `github-actions-zero-ci-strategy-2026-07` + savant .STATUS for the full record. Prior: Phase 1 native-first prune MERGED to dev 2026-07-09 — PR #279 (squash `4fa44fbb`): pruned 21 dead-namespace commands (115→94, −5533 lines) + excluded dead docs (specs/plans/archive) from the built mkdocs site. ⏹ NATIVE-FIRST THINNING REFUTED: a Stage-2 dynamic workflow (3 pilots × 4-axis gate + cross-plugin routing test) returned NO-GO on Phase 2 — the `do`/`hub` router structurally can't dispatch cross-plugin, AND 3/3 pilot replacements (native `/simplify`, `/code-review`, `agent-skills:ci-cd`) aren't ≥ craft on all 4 axes (weaker/auto-apply, third-party dep, or generate nothing). Phases 2 (cascade thinning) + 3 (`folio` docs/publishing split) DROPPED; folio only revisitable on docs-maintenance merits, grilled fresh. ADR-002 rich-body gate held mid-build: 3 of 24 "safe" deletions (`git:docs:refcard`, `check:gen-validator`, `workflow:insights`) carried unique logic → restored, not deleted (caught by `test_skill_referenced_commands_exist`). Verification gap fixed: pytest passed 2676 but CI's Validate-Plugin-Structure bash suite (`test_git_shim_correctness`) asserted deleted `git/{sync,git-recap}.md` exist → shim array updated (see memory `pytest-doesnt-cover-craft-ci-bash-suites`). Counts now 94/45/8 on dev; NOT yet released — v2.61.2 still the current tag. Full record: `docs/specs/SPEC-craft-native-first-breakup-2026-07-09.md` OUTCOME block. Worktree `feature/craft-native-first-breakup` removed + remote branch deleted 2026-07-09; local branch ref left stale (branch-guard blocks force-delete of squash-merged branch — documented deadlock, harmless). Prior: v2.61.2 SHIPPED 2026-07-09 — PR #275 (dev→main merge, tag v2.61.2) + PR #276 (CI fix): docs/NEWS.md release highlights page, CHANGELOG/VERSION-HISTORY v2.61.1 section, REFCARD.md version box corrected (2.61.0→2.61.2, counts 117→115), exclusions.txt archived-doc path fixed. PATCH bump. Docs Staleness + Markdown Linting GREEN; Validate Plugin Structure + Link Validation pending (known hang). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.2. Counts unchanged 115/45/8. Prior: v2.61.0 SHIPPED 2026-07-06 — PR #271 (dev→main merge `6c3e2e95`, tag v2.61.0): do.md dead-agent-dispatch removal + 24-doc sweep, `verify-surfaces.sh --report-only`/`--version` + 2 new legs (GitHub release, docs site) + release-rollback runbook, 4 confirmed branch-guard/no-switch-guard gaps closed, quota tooling removed (all from PR #270, squashed to dev pre-release), plus post-merge docs-audit fixes: 2 dead-agent surface-forms PR #270's hyphenated-only grep missed (`docs/index.md`'s feature grid, `skills/orchestrator-resilience/SKILL.md`'s fallback-dispatch example), a docs-staleness false positive (`orch.md`'s mode-table "4 agents"), and backfilled `CHANGELOG.md`/`docs/CHANGELOG.md` `[Unreleased]` entries missed during #270's own PR prep. MINOR bump. Full suite 2707 passed/1 pre-existing failure (`test_roadmap_orchestrator_enhancements`, stale `ROADMAP.md` path predating this release — root-caused and FIXED post-release in `dc6ee28a`, corrected to `docs/archive/ROADMAP.md`; this exact failure had forced `--admin` overrides on PR #270, PR #271, and `main` directly, three releases running). Merged via `--admin` (same pre-existing failure, confirmed by log each time). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.0 · PR: https://github.com/Data-Wise/craft/pull/271. Downstream: docs site live at v2.61.0 (confirmed via curl), tap formula confirmed v2.61.0 (live raw.githubusercontent fetch, not local clone), Homebrew Release + Aggregator Sync + Craft MCP Release workflows all SUCCESS. `dev` fast-forwarded to `main`'s merge commit, pushed. Counts unchanged 115/45/8. Also this session: grilled and CLOSED the orchestrate token-usage-hooks backlog (captured 2026-07-02) with NO build — two review passes (backend/architecture + interface/CLI-contract lenses) found hook 1 infeasible (no live token-introspection API exists for a prompt-driven orchestrator session), hook 3 structurally uncomputable for an in-progress run; the one surviving feasible piece (`--token-report`) wouldn't have fixed the retroactive-analysis problem that motivated the backlog, and duplicates the accounting-tooling category this same session already removed (quota tooling) — see `docs/specs/GRILL-orchestrate-token-usage-hooks-2026-07-06.md`. Prior: v2.60.0 SHIPPED 2026-07-05 — PR #269 (dev→main merge `763c5e8c`, tag v2.60.0): command-namespace reorganization (full grill-and-implement cycle, `docs/specs/SPEC-command-namespace-reorganization-2026-07-05.md`) — root promotions `/craft:next`/`/craft:done`/`/craft:refine`/`/craft:brief`/`/craft:brainstorm` (heavy real-world use); `orchestrate`→`orch` rename across ~110 files; `ci:` consolidation (`code:ci-fix`/`code:ci-local`→`ci:fix`/`ci:local`); new `docs:generate` router (9 generators, none merged — genuinely distinct logic); new `task:` namespace; `/craft:quota` deleted (folded into `/craft:orch` pre-flight); `code:desktop-watch` deleted (was a pure redirect shim); `adhd-guide` converted to a skill reference. Pre-flight caught and fixed 8 real test failures before release: 6 stale "117 commands" claims (actual 115) across dist/docs files not covered by `bump-version.sh --counts-only`'s file list, one hardcoded pre-rename path in `test_drive_command_in_nav`, and a missing TOC on the newly-moved `adhd-guide.md` (>300 lines, tripped the skill-standards release gate). MINOR bump. Full suite 2707 passed/0 failed/5 skipped; pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.60.0; homebrew-release workflow SUCCESS (tap formula confirmed v2.60.0 via live `gh api`, NOT the stale local `~/projects/dev-tools/homebrew-tap` clone which still read 2.58.0); aggregator PR #19 auto-merged in `Data-Wise/claude-plugins`; post-release sweep ALL CLEAN. Counts 115/45/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.60.0 · PR: https://github.com/Data-Wise/craft/pull/269. Prior: v2.59.0 SHIPPED 2026-07-04 — PR #260 (dev→main merge, tag v2.59.0): `plugin-audit` skill (44→45, cross-plugin command/skill collision detector, schema + false-positive fixes before merge, #237) + docs-site staleness hardening H1/H2/H3 (`docs.yml` live-site post-deploy version poll, stale-count guard extended to all of `docs/**/*.md`, #259). Counts 116/45/8 (later corrected to 115 in v2.60.0 after the `resume.md` deletion cascade settled). Prior: v2.58.0 SHIPPED 2026-07-02 — PR #251 (dev→main merge, tag v2.58.0): distribution-surface hardening — curl-pipe installer fixed to clone current `main` instead of a frozen v1.16.0 mirror (D1, 11 contract tests); homebrew-tap copy-based install (no symlinks); net-new `mcp/` TS MCP server (craft-mcp Desktop bridge, 3 read-only tools, DXT/.mcpb); esbuild audit-clear. Counts 116/44/8. Prior: v2.57.0 SHIPPED 2026-07-01 — PR #242 (dev→main merge `ec5797e6`, tag v2.57.0): grill attack-angles (#238 — 7 project-agnostic adversarial axes, angle 3 reads target project's own CLAUDE.md/ADRs at runtime; skill count 43→44) + orchestrate-family wave-1 (#239 — deleted fictional 523-line `resume.md` session-teleportation command, command count 117→116; thin-shimmed `orchestrate:plan` to `plan-orchestrator` skill) + doc-impact rubric extension (#241 — 4 new scored types Tutorial/API/Cookbook/Architecture-doc with tiered thresholds + arch-doc double-count subtraction rule + advisory Site Consistency checklist) + orchestrate-dispatch mode (#240 — 3rd `--output` value dispatching a background Agent from the live planning session, with confirm-before-dispatch gate, scoped concurrency cap, failure/hang detection at 2×-effort-estimate window, resumability, scoped `.STATUS` auto-write). MINOR bump (4 feat/refactor PRs). #240/#241 were background-agent dispatches using the very pattern #240 formalizes — monitoring protocol caught 1 self-introduced broken-link regression (a cross-tree link into `~/.claude/...` from this session's own BRAINSTORM doc, fixed on dev) + recovered a killed agent's completed-but-uncommitted Phase 3 work. A CHANGELOG merge-order conflict between #240/#241 (both added `[Unreleased]` entries) was resolved by rebase, keeping both. Pre-release-check initially reported doc-coverage as BLOCKING due to a macOS bash-3.2 `mapfile` incompatibility (false positive — confirmed PASS, 0 gaps, via Homebrew bash 5.x/zsh). Full suite 2072 passed/7 failed/13 errors (documented env-caused baseline, not regressions); pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.57.0; homebrew-release workflow SUCCESS (formula confirmed v2.57.0, desc "116 commands"); post-release sweep ALL CLEAN. Counts 116/44/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.57.0 · PR: https://github.com/Data-Wise/craft/pull/242. Prior: v2.56.0 SHIPPED 2026-07-01 — PR #234 (dev→main merge `43beb7b3`, tag v2.56.0): token-usage reduction (PR #232 squash `e6c24a27` — orchestrator model pinning, `/refine` 631→42 lines, `/brainstorm` redesign, new `command-skill-token-efficiency` skill) + release-tracking hardening (namespace-refactor token-cost go/no-go measured 68.3% reduction; broken `/usage` scheduled-trigger replaced with `ccusage`+`claude-monitor`; 2 cross-spec factual bugs fixed via a second grill pass). MINOR bump (feat: #232). All CI checks GREEN on first pass — no `--admin` needed (Validate Plugin Structure resolved cleanly this time). Full suite 2089 passed; pre-release-check PASSED; main CI GREEN; live site confirmed v2.56.0; homebrew-release workflow SUCCESS; post-release sweep ALL CLEAN. Counts 117/43/8. Prior: v2.54.0 SHIPPED 2026-06-27 — `/craft:workflow:brief` (117th command): 3-line action block generator (Next step / Watch out for / Connects to) with `--plan`/`--verbose`/`--show-context` flags; `--brief` integrated into `/craft:do` Step 5.5. 29 e2e + 12 dogfood tests. Main CI GREEN; docs deployed; homebrew tap updated (117 commands); post-release sweep CLEAN (getting-started.md fixed); all BLOCK surfaces aligned. Counts: 117 / 39 / 8. Prior: v2.53.0 SHIPPED 2026-06-26 — multi-surface-aware release (surface registry + aggregator CI propagation + Cowork verify leg + `/craft:dist:surfaces`, #218/#184) + docs-staleness fix (docs/plans/ excluded from count-consistency scan). MINOR bump (feat). All tests pass; main CI GREEN; docs deployed; homebrew tap updated (116 commands); post-release sweep ALL CLEAN; aggregator sync wired (all 4 satellite repos). Counts 116/39/8. Prior: v2.52.0 SHIPPED 2026-06-26 — interactive-by-default commands (#215) + default-on test/docs scaffolding (#216) (PR #217 dev→main merge `c5f704f3`, tag v2.52.0, `--admin` on stuck Validate-Plugin-Structure): grill/orchestrate AskUserQuestion model (Recommended-first + per-option consequence) + `--yes`/`--non-interactive`; refine default-on for brainstorm/do/plan:feature/grill (opt-in elsewhere) + `--yes` cascade + orchestrate Step 0.5 structured model; AND brainstorm/plan:feature/grill emit a tier-inferred test-plan + doc-scorer Documentation section BY DEFAULT (`--no-tests`/`--no-docs`; arch:plan/spec-review opt-in via `--tests`/`--docs`), logic in skills (brainstorm-insights/plan-orchestrator, deprecation-trap-safe + guarded), doc-scorer REUSED, lifecycle-split, count-cascade-excluded. Built spec→grill→plan→subagent-driven-TDD (2 plans, 7+8 tasks, per-task spec/quality + opus whole-branch reviews; gates caught a dropped-phrase regression, a refine over-flip, a decorative deprecation-guard sentinel, and a MEMORY.md over-compaction that dropped 37/102 pointers → all fixed). MINOR bump (4 feat). Full suite 1990 passed; pre-release-check PASSED; main CI GREEN; docs deployed (live site 2.52.0); Homebrew Release auto-updated tap; verify-surfaces 6/6 ALIGNED (refreshed local lag: brew formula 2.51→2.52 via `--formula`, marketplace-update-THEN-plugin-update for the Code pin); post-release sweep ALL CLEAN. Counts 115/39/8. Prior: v2.51.0 SHIPPED 2026-06-25 — governance retro (#205) + skill-standards release gate (PRs #211/#212→dev, #214 dev→main merge `180181f8`, tag v2.51.0; CLOSED #205 + #138 via default-branch auto-close): D1 **R09 status-not-drift** governance check (`governance/checks/status_drift.py`, warn) — audits `.STATUS` `version:` vs manifest + SHIPPED/tagged vX.Y.Z vs git tag, fixture `--selftest` + LIVE in `pre-release-check.sh` (this very release was self-gated by it: ".STATUS: version + release claims match" ✓); D3 ADR-003 (release-drift advisory not hard-gate + P7 freeze); skill-standards validator graduated to a **release-tier gate** (`CRAFT_MODE=release` blocks, advisory below) + fixed a latent CRAFT_MODE no-op (LLM-prose command never exported the gating var → gate+version-check silently dead). MINOR bump (2 feat: #211/#212). Full suite 1970 passed; pre-release-check PASSED; main CI GREEN; **verify-surfaces 6/6 ALIGNED** (after refreshing local lag: brew upgrade 2.50→2.51, `claude plugin update` 2.50→2.51, pulled stale `~/projects/dev-tools/homebrew-tap` checkout — the script reads that local clone FIRST, was the only "blocker"); post-release sweep ALL CLEAN. Counts 115/39/8. Process: merged #211/#212 (conflict-free despite branches behind dev — `--stat` looked conflicted but they authored ZERO changes to `.STATUS`/`CLAUDE.md`/`governance.md`; verified via `merge-tree --write-tree`), pushed a local-only gate branch first (data-loss risk), `git:clean` removed 2 worktrees (11+2 squash-merged branch refs left dangling — branch-guard blocks `-D`, harmless). Prior: v2.50.0 SHIPPED 2026-06-25 — v2.49.x issues sprint (PRs #206/#207/#208→dev, #209 dev→main --admin on phantom Validate Plugin Structure, tag v2.50.0): advisory skill-standards validator in `/craft:check` (hot-reload, never fails); homebrew dist-gates `verify_caveats.py`+`post_install_check.py` wired into `/release` Steps 10b/10c (advisory, strict via HOMEBREW_GATE_STRICT) + 10d (BLOCKING aggregator-sync + Cowork verify) (#200/#199); SessionEnd insights facet hook + idempotent installer, per-session-id dedup (#183); ADR-001 Workflow-vs-worktree (#171); +14 e2e/dogfood tests pinning locked decisions. Built via craft:grill (10 decisions D1–D10) → superpowers writing-plans (4 TDD tracks) → 2 dynamic Workflows (impl+verify, ran from dev session under branch-guard bypass, re-protected after). MINOR bump (3 feat: commits). Full suite 1958 passed; pre-release-check PASSED; main CI GREEN; tap+brew+Code+marketplace all 2.50.0 (verify-surfaces 6/6 ALIGNED); post-release sweep CLEAN. KEY INCIDENTS (recovered): (1) #206 squash folded UNPUSHED local-dev base commits → #207/#208 phantom conflicts → rebased --onto origin/dev to untangle (memory squash-folds-unpushed-dev-base-commits; push dev before PRing off it); (2) GitHub comma-list `Closes #200, #199, #183, #171` only auto-closed #200 (+#171 via Track-0 trailer) — each issue needs its OWN keyword; closed #183/#199 manually. Counts 115/39/8. Prior: v2.49.0 SHIPPED 2026-06-23 — skill-standards auditor + /done memory/settings + ADR-002 + release re-split (PR #203 auditor→dev, #204 release dev→main --admin, tag v2.49.0): `/craft:code:skill-standards` batch-audits `skills/**/SKILL.md` vs a vendored copy of Anthropic's standards (score+exit 0/1/2, `--json`/`--markdown`, `--fix` = version-tag-strip + frontmatter-normalize + TOC-stub, never prose; `--refresh-standards`); `/done` Memory Optimize (Step 1.12) + Settings Sync (Step 1.10.5); ADR-002 consolidated `/done` into the `adhd-workflow` skill (`references/done.md` SoT + thin shim); 4 oversized skills split to `references/` (release re-split loss-gated after a lossy first attempt, 0 lines lost) → 39 skills 100/100. Counts 114→115. Release: dev was unsynced with main (v2.48.0 release merge commits) → dev-wins sync merge resolved the PR #204 conflict; full suite 1929 passed; main CI GREEN; docs deployed; tap synced. POST-RELEASE TOOLING (homebrew-tap + savant): root-caused the local Code-registered lag — the formula `post_install` ran `claude plugin update` WITHOUT first `claude plugin marketplace update local-plugins` (no-op on a stale cache); fixed in `homebrew-tap/generator/generate.py` for ALL claude-plugin formulas; reconciled manifest-drift across 6 formulas (rforge-orchestrator's `deprecate!` was at risk of silent un-deprecation; himalaya-mcp 12 skills/22 MCP tools; scholar 28→34; craft `{command_count}` single-source token) gated by regen==committed; added a Formula Drift Guard CI workflow (regen-vs-committed, first run GREEN); savant `CLAUDE.md` 19→25 skills. Memories: post-install-marketplace-refresh-before-update, deprecated-command-rich-body-trap, skill-authoring-tooling-landscape. Prior: v2.48.0 SHIPPED 2026-06-23 (grill + orchestrate Step 0.5 Clarify). Prior: v2.47.0 SHIPPED 2026-06-21 — Governance roadmap COMPLETE (PR #196/#197→dev, #198→main, tag v2.47.0): R04 automated as content-drift (manual→checks/no_drifted_copy.py — installed SKILL.md must be byte-identical to canon; DISTINCT from R07 version-pin, catches a hand-edited body even when the version matches; gates session, vacuous-skip otherwise) + #184 release pre-flight advisory (pre-release-check.sh runs run_rules.py --json, prints RED count, NEVER blocks — gentle-ramp; TestReleaseGuard184 invariant) + live-flow test coverage (e2e soak lifecycle hook→ledger→promote-check + R04-in-audit; dogfood ledger edges). Governance suite 47→59. KEY FINDING (memory r04-content-drift-not-filetype): naïve "real-dir=copy" heuristic was dead (installs are real dirs, no symlinks) → content-drift; always fire a new WARN checker on the live env first (a false positive poisons the soak ledger). #198 merged CLEAN (no --admin). Homebrew tap synced to v2.47.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Governance now: R01–R04+R06 automated · R05 advisory · R07 external · R08; 5 surfaces (visibility·prevention·promotion·cross-repo·release-advisory). Prior: v2.46.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #3 (PR #194→dev, #195→main, tag v2.46.0): soak-then-flip promotion + cross-repo wrapper, completing Phase 2 (visibility · prevention · promotion · cross-repo). soak.py = LOCAL gitignored STATE.json ledger (first_seen/last_seen/last_red per rule) fed by the SessionStart hook each audit; run_rules.py --promote-check (--window 14d, --state) lists warn rules soaked clean long enough to recommend a human warn→error flip (machinery recommends, human promotes; advisory exit 0). run.sh = cross-repo wrapper (consumers invoke the one installed engine, cwd-portable, no drift, R07). 8 dogfood tests (injected today → no flake), 47 governance suite green. Bundled #193: test_performance_cached_run → xfail(strict=False) (wall-clock microbenchmark flaked on shared CI, forced --admin; now a soft signal) — and #195 MERGED CLEAN with NO --admin (the xfail paid off). Homebrew tap synced to v2.46.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. R04 automation + #184 release-pre-flight guard remain deferred. Prior: v2.45.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #2 (PR #190→dev, #192→main, tag v2.45.0): SessionStart visibility hook (governance/session_hook.py) — audits the live ~/.claude/skills tree at session open and injects a compact RED-only summary into context via SessionStart additionalContext; silent when clean, mtime-cached, no-op where skills tree absent, never breaks a session on error. Visibility not prevention (SessionStart hooks inject context, can't block). Completes the three-surface model: pre-commit + CI (prevention) · SessionStart (visibility). Implemented direct-to-worktree from the dev session (worktree-path-aware). #192 admin-merged (strict up-to-date BEHIND from the v2.44.0 merge-commit divergence; PR-context full suite GREEN). Homebrew tap synced to v2.45.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Global install of the hook DEFERRED to post-v2.45.0 (point at released plugin path; docs in governance/README.md). PR #3 (soak-then-flip + cross-repo wrapper) + R04 deferred. Also: removed leaked ORCHESTRATE-guard-suite.md (PR #191). Prior: v2.44.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #1 (PR #188→dev, #189→main, tag v2.44.0): R03 automated (manual→script — checks/no_private_in_public_marketplace.py scans a marketplace manifest, fails on a public-marketplace ref to a private/PII repo, denylist {savant}, dual-mode file/dir + good/bad fixtures; closes the last error-rule enforcement gap), pre-commit governance-gate hook (^governance/ → selftest + render --check drift) + CI gate in ci.yml (with explicit "CI does NOT evaluate R01/R07 live-env" note), engine {marketplace} substitution through audit()+selftest() + --marketplace. Built from a draft→adversarial-verify→ultra-finalize workflow chain (verified packet); implemented directly into the worktree from the dev session (branch-guard is worktree-path-aware — corrected a stale assumption). Also corrected stale "not yet wired" governance docs. #189 admin-merged (only failure was the flaky test_performance_cached_run timing test, unrelated to the diff; PR-context full suite GREEN). Homebrew tap synced to v2.44.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 PR #2 (SessionStart hook + soak-then-flip + cross-repo wrapper) NEXT; R04 deferred. Prior: v2.43.0 SHIPPED 2026-06-20 — Skill-Ecosystem Governance (Phase 0) (PR #185→dev, #187→main, tag v2.43.0): policy-as-code under governance/ — RULES.yaml (8 skill-location rules, single source of truth), run_rules.py (audit + --selftest meta-validation, fail-closed), render_rules.py (CLAUDE.md block generation + --check drift gate), portable checkers + good/bad fixtures, guide + Plugin Release Runbook, 24 e2e/dogfood tests + governance pytest marker. PR #185 review hardening (all 5 issues): fail-closed gating (broken checker on error rule exits 1), recursive broken-symlink detection, R01 gates session (not ci) + visible canon-skip, --check exit-2, --selftest surfaces external rules, UTF-8. Homebrew tap synced to v2.43.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 enforced-gates roadmap spec open in PR #186. Prior: v2.42.0 SHIPPED 2026-06-19 — /craft:code:fewer-prompts curated read-only Bash allowlist (PR #181). Prior: v2.41.1 SHIPPED 2026-06-19 — Documentation Coverage (PR #180): 25 tutorial stubs for all Phase 8 commands + broken link fix in TUTORIAL-smart-help.md. Prior: v2.41.0 SHIPPED 2026-06-19 — token-efficiency (quota gate, engine flag, parity runbook). v2.40.0 SHIPPED 2026-06-19 — Guard Suite (PR #174): /craft:git:guard + guards.json registry & ADHD toggle UX, no-switch-guard promoted to scripts/, guard-consistency validator, install-guards.sh. Destructive-restore reconciled to no-switch-guard; branch-guard rm-rf-.git/reset--hard softened to confirm (catastrophic ops stay NON-muteable). 111 commands. Review found 1 CRITICAL (catastrophic-bypass) + 6 issues — ALL fixed pre-merge; +5 registry tests; ci.yml now installs both guards + runs the no-switch suite. Prior: v2.39.0 SHIPPED 2026-06-19 — doc-coverage enforcement (PR #172→dev, #173→main). Prior: v2.38.2 SHIPPED 2026-06-15 — multi-surface release arc (3 patches). v2.38.0: multi-surface engine (verify-surfaces Step 13.6, cache-prune Step 13.7, Data-Wise aggregator marketplace) + branch-guard draft=dev (PRs #158/#159/#160/#161). Adversarial review found 3 HIGH + 6 MEDIUM/LOW bugs → v2.38.1 (PR #163: branch-aware dogfood tests fixed RED main CI; cache-prune data-loss; verify-surfaces git-tag no-op) → v2.38.2 (PR #165: corrupt-surface blocks, symlink GC, arg-parse, subshell). All 3 → main, main CI GREEN, tap + aggregator (Data-Wise/claude-plugins) pinned to 2.38.2, post-release sweep CLEAN. Counts: 112 commands / 39 skills / 8 agents. Prior: v2.37.0 SHIPPED 2026-06-13 — drift-prevention chain + CI triage/watch. Bundled brainstorm items 1-7 (PRs #151/#152/#153/#154/#155): #1 strict-markers guard + #4 workflow recipe discoverability; #7 bump-version categorical-subtotal sweep; #2 CI count-drift tripwire (README live counts); #5 /craft:ci:triage + #6 /craft:ci:watch. PR #156 → main 8c21a54d (merge commit), release tag v2.37.0. Downstream all GREEN: main CI verified, homebrew-release.yml updated tap to v2.37.0 (desc "112 commands", url v2.37.0.tar.gz). Post-release sweep ALL CLEAN (drift tooling paid off). Counts: 112 commands / 39 skills / 8 agents. Prior: v2.36.0 SHIPPED 2026-06-13 (PR #150 → main eb7e143d). -last_session: 2026-07-30 +milestone: **v4.5.0 SHIPPED 2026-08-07** — PR #329 (dev→main merge, tag v4.5.0, MINOR): new `repo-triage` skill (41st skill) batch-grounds open GitHub issues and stale worktrees/branches against current repo state, offering confirmed (never automatic) deletion/closure — built via brainstorm→grill (`GRILL-repo-triage-2026-08-07.md`, 2 factual corrections)→dispatched ORCHESTRATE worktree, code-reviewed (8 findings, all fixed pre-merge: jq-null pr_state bug, swallowed git-diff failures, not-merged-branch passthrough, tracked ORCHESTRATE artifact, stale skill-count prose, duplicated evidence-normalization + fence-extraction logic consolidated into `classifier_loader.py`, cross-repo file-check bug), merged PR #328 squash to dev first. Also shipped: 8 new ADHD-friendly ecosystem-tool cheat-sheet tutorials (Codex, Remember, i-have-adhd, Token Optimizer, Security Guidance, Claude HUD, Dropbox, Agent Skills) under a new nested `Ecosystem Tool Cheat Sheets` mkdocs subsection (`SPEC-ecosystem-tool-tutorials-2026-08-07.md`), plus a `docs/commands/hub.md` nav-completeness fix (`TUTORIAL-opencode-plugins.md` was missing from mkdocs.yml). Pre-flight found 2 non-blocking local-only failures (not regressions, CI confirmed green on the same commit): `test_branch_guard_dogfood.py` (the installed hook is a symlink to the sibling `cc-config` repo, out of scope per cross-repo permission boundary) and `test_no_live_legacy_check_skill_references` (was walking gitignored `.remember/` via raw filesystem `rglob()` — fixed to scope to `git ls-files`, genuine bug, fixed pre-release). Full suite 2632 passed/0 failed (post-fix); `pre-release-check.sh 4.5.0` clean; docs-staleness GREEN; live site + Homebrew formula + aggregator sync (claude-plugins PR #46, auto-merged) all confirmed v4.5.0; `verify-surfaces.sh` needed a local `homebrew-tap` `git pull` + `brew upgrade` + `claude plugin update` refresh before reporting ALIGNED (known pattern — the script reads the local tap checkout, not live GitHub). KNOWN, NOT FIXED: `aggregator-sync.yml` returns 404 by that exact workflow name in `claude-plugins` (the release skill's own docs name a workflow that doesn't exist under that filename) — propagation still worked via some other mechanism (PR #46 auto-merged), doc/workflow-name discrepancy left unswept, out of scope for this release. Release: https://github.com/Data-Wise/craft/releases/tag/v4.5.0 · PR: https://github.com/Data-Wise/craft/pull/329, https://github.com/Data-Wise/craft/pull/328. **Post-release same-day (2026-08-07):** doc audit found + fixed 3 stale-prose bugs the count-consistency gate can't catch (`REFCARD.md`'s version-box date/highlight + skill count, `skills-agents.md`'s "8 specialized agents", `docs/index.md`'s 5× "47 commands" + missing real install instructions — Quick Start only had the dev-only local-plugins symlink, added Homebrew + Data-Wise marketplace paths); added a `triage-your-repo` cookbook recipe for the repo-triage skill. Brainstormed → spec'd → adversarially reviewed a gate fix for this exact blind spot (`SPEC-doc-staleness-prose-gaps-2026-08-07.md`: 2 new Phase 7 checks, release-date claims + structured-line-scoped count prose; review killed a 3rd check that was falsified by its own target and caught 90+ false positives in the 2nd, both fixed pre-build; not yet implemented). Cross-repo: `homebrew-tap#211` fixed a stale `/craft:git:unprotect` caveat reference (command removed in v4) — first push tripped Formula Drift Guard (content changed, no revision bump), fixed with `revision: 1`; merged, verified live via `brew upgrade` (4.5.0 → 4.5.0_1, new caveat text confirmed). Follow-up noted, not built: `homebrew-tap`'s own `manifest.json` caveats prose has the identical staleness blind spot, out of scope for the craft-only SPEC. Prior: **v4.4.2 SHIPPED 2026-07-27** — PR #318 (dev→main merge, tag v4.4.2, PATCH): the one fix merged to dev since v4.4.1 (PR #317, closes #316). `preflight-check` skill made reachable by its declared identity — it lived at the pre-rename `skills/check/` location while declaring `name: preflight-check`, so directory-based skill discovery couldn't find it; moved to `skills/preflight-check/`, `/craft:check`'s public argument contract unchanged. New recursive fixture-backed identity tests inventory every command/skill directory-vs-frontmatter mismatch and command/skill name collision plugin-wide, rejecting new drift; 8 pre-existing mismatches + 3 intentional shim collisions ledgered as owned debt through v5.0.0. Test automation now targets stable Python 3.14 (`.python-version` pin, 5 GitHub Actions workflows aligned, drift-guard policy test) — the local-interpreter assertion in that test was found too brittle on review (hard-failed for any contributor not on 3.14) and fixed to skip instead before merge. `docs/NEWS.md`/`docs/index.md`'s own v4.4.2 release-note prose initially contained the literal `skills/check/` string, which the new #317 regression guard (`test_no_live_legacy_check_skill_references`) correctly caught as a repo-wide scan, not scoped to code — rephrased without the literal path, second commit. Full suite 2627 passed/0 failed/52 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.2` clean (10/10); docs-staleness GREEN; live site + Homebrew formula (fresh sha256) + aggregator sync + Craft MCP Release all confirmed v4.4.2; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.2 · PR: https://github.com/Data-Wise/craft/pull/318, https://github.com/Data-Wise/craft/pull/317. Prior: **v4.4.1 SHIPPED 2026-07-26** — PR #315 (dev→main merge, tag v4.4.1, PATCH): post-4.4.0 fixes only, no features. `scripts/branch-guard.sh`'s `2>&1`/`1>&2` false-positive (a command like `git commit -m "...(7 -> 9)" 2>&1` misdetected as file creation) was fixed live on the installed hook earlier this session but never ported to the repo's canonical copy, leaving `test_repo_copy_matches_installed` red and the bug reintroducible on reinstall — ported with 2 regression tests (implements `GRILL-rtk-hook-safety-close-out-2026-07-26.md` decision #3). `docs/help/refine-flag.md` and `docs/reference/COMMAND-PARAMETERS.md` fixed for PR #313's 9-command declarer set (was still 7, missing `/craft:plan`/`/craft:smart-help`) — new dogfood test guards against recurrence. `docs/index.md`'s "Latest" callout and `docs/NEWS.md` synced (both still described v4.3.0's content under a v4.4.0 label; NEWS.md had no v4.4.0 entry at all). Also triaged 7 remaining open issues (#228, #213, #199, #170, #169, #133, #108): closed #108/#213/#133, re-scope comments on #228/#170/#169, confirmed #199 valid. Full suite 2654 passed/0 failed/16 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.1` clean; docs-staleness GREEN; live site + Homebrew formula confirmed v4.4.1; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.1 · PR: https://github.com/Data-Wise/craft/pull/315. Prior: **v4.4.0 SHIPPED 2026-07-26** — PR #314 (dev→main merge, tag v4.4.0, MINOR): the one `feat:` commit since v4.3.1. `smart-help` declares `--refine` (D7, PR #313) — opt-in, default-OFF, closing the last dangling item on #268's prompt-refiner umbrella. `prompt-refiner`'s Default Policy table made exhaustive and re-keyed on command file path — fixed 3 stale display names, added 2 live callers it was silently missing (`commands/plan.md`, `commands/arch/plan.md`); a new dogfood test (`test_refine_default_policy_table_exhaustive`) guards against future drift. #268's other two proposed items (named destinations, `--scope global`) and #266 (multi-round grill mode) were grilled then killed on adversarial review before implementation — see `docs/specs/GRILL-prompt-refiner-remaining-2026-07-26.md`'s amendment; #266 closed pointing at savant's `edit-loop` (shipped 2026-07-13), which already solves the observed pattern with tooling craft structurally lacks. Full suite 2618 passed/0 failed/49 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.0` clean (10/10); docs-staleness GREEN; live site + Homebrew formula + aggregator sync all confirmed v4.4.0. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.0 · PR: https://github.com/Data-Wise/craft/pull/314, https://github.com/Data-Wise/craft/pull/313. Prior: **v4.3.1 SHIPPED 2026-07-25** — PR #312 (dev→main merge, tag v4.3.1, PATCH): two bug fixes merged to dev since v4.3.0, both traced to the same session. `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281) surfaced in the guard-bypass-marker confirms themselves (#309, PR #310) — the documented "Claude writes `.claude/allow-once`" one-shot flow was circular for the one case where the approved action IS creating/editing that marker; added the env-var hint to the three marker-specific confirms, reconciled both branch-guard reference docs. Bare-`vX.Y.Z` version-grep bug class swept repo-wide (PR #311) — the same false positive that broke the Homebrew release gate twice (v4.2.0, v4.3.0; fixed for `docs/index.md` in #308) also affected `docs.yml`'s live-site poll, `verify-surfaces.sh`'s `resolve_docs_site`, `version-sync-precommit.sh`'s blocking `.STATUS` pre-commit check, and manual release-verification docs — all re-anchored to the actual version signal instead of the first bare match, each with a regression test verified via planted-defect positive control. Full suite 2650 passed/0 failed/16 skipped/1 xfailed (tracked)/1 xpassed (known perf flake); `pre-release-check.sh 4.3.1` clean (10/10); docs-staleness GREEN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.1 · PR: https://github.com/Data-Wise/craft/pull/312, https://github.com/Data-Wise/craft/pull/310, https://github.com/Data-Wise/craft/pull/311. Prior: **v4.3.0 SHIPPED 2026-07-25** — PR #307 (dev→main merge, tag v4.3.0): `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281, PR #305) closing the guard-bypass-marker creation deadlock, reusing the #168 env-var pre-authorization pattern; new advisory-only `reference-scope-guard.sh` hook (#286, PR #306) warning on non-conforming filenames under `~/.claude/reference/`, sharing `install-guards.sh`/`guards.json` with `branch-guard`/`no-switch-guard`. Full suite 2648 passed/0 failed. **Homebrew Release recurrence:** the exact same `pre-release-check.sh` false positive that broke v4.2.0's automated tap push (worked around manually then) broke v4.3.0's too — `docs/index.md`'s version check grepped the first bare `vX.Y.Z` string in the file, landing on a historical "since v4.0.0" migration note instead of the actual version badge. Fixed at the source this time (PR #308, fast-tracked dev→main): matched the badge pattern specifically (`version-X.Y.Z`), same as the adjacent README.md check. Re-dispatched `homebrew-release.yml` via `workflow_dispatch --ref main -f version=4.3.0` (the original `release`-event run had checked out the tag commit before the fix existed, so a plain rerun would have failed identically) — all 3 jobs green, tap formula confirmed live (`v4.3.0.tar.gz`, fresh sha256). Craft MCP Release + Aggregator Sync both green from the original release event. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.0 · PR: https://github.com/Data-Wise/craft/pull/307, https://github.com/Data-Wise/craft/pull/308. Prior: **v4.2.0 SHIPPED 2026-07-19** — PR #296 (dev→main merge, tag v4.2.0): **BREAKING:** `/craft:done` renamed to `/craft:finish` (ADR-006) — matches the existing zsh `finish` alias, no back-compat shim, command count unchanged (48, a rename not an addition); `/craft:restore` spec/report status corrected DRAFT→SHIPPED (had already shipped in v4.1.0 when written). Full suite 2649 passed/1 failed (a genuine broken-link regression from the rename sweep, `docs/commands/brief.md` still linked pre-rename `done.md` — caught by the test run itself, fixed same-commit)/10 skipped. Also merged to dev this cycle (not yet in a tagged release): PR #297 (`utils/docs_detector.py`'s `_extract_command_name()` truncated multi-hyphen command names past a 500-char read window, bumped to 2000), PR #298 (extends `/craft:finish` Step 1.10.5 with a `~/.claude/projects//memory/`+`.remember/` staleness check, 14-day threshold, report-only), and a `docs/reports/` folder established as the `/savant:report` output convention (moved `REPORT-craft-restore-2026-07-17.md` there, added to `scripts/config/exclusions.txt` alongside `docs/specs/`). **Homebrew:** v4.2.0's automated tap-formula push failed — `homebrew-tap`'s `main` gained branch protection (PR + 2 required checks) since `homebrew-release.yml` was last touched, so its direct `git push origin main` was rejected (GH006); recovered manually via `homebrew-tap`#164, which surfaced 2 more CI-gate bugs fixed directly in that repo (#165: `check-revision-bump.sh` false-positived on URL-tag-versioned formulas like craft's, no explicit `version` field to read; #170: the ".STATUS conflict markers" required check was path-filtered to only `.STATUS` changes, so it could never fire — and the PR stayed BLOCKED forever — on formula-only PRs). craft's own workflow fixed to match (PR #299: push a bot branch + open/auto-merge a PR instead of pushing directly), landing independently from a background-dispatched session whose worktree had gone stale after only completing 1 of the 2 related craft/homebrew-tap fixes — recovered by inspecting the worktree directly rather than trusting the dispatch's completion state. Release: https://github.com/Data-Wise/craft/releases/tag/v4.2.0 · PR: https://github.com/Data-Wise/craft/pull/296. Prior: **v4.1.0 SHIPPED 2026-07-17** — PR #295 (dev→main merge, tag v4.1.0): `/craft:restore` (NEW root command, combines `dev/git` skill's mode-aware git-activity recap with `adhd-workflow`'s `.STATUS`-based session recap into one read-only entry point; replaces the deleted `git-recap`/`recap` commands; no `--sync`; a cross-plugin `/restore` dispatcher to `savant:restore` was investigated and explicitly descoped — router can't dispatch cross-plugin) + `/craft:refine` confirm-flow fix (a 2026-07-01 design — fenced refined-prompt block + 4-way confirm: Execute now/Copy for elsewhere/Edit first/Skip — was locked but never implemented; now shipped across all 5 `--refine` callers + standalone, closing the "asks for acceptance without printing first" bug) + full post-v4.0.0 doc-staleness cleanup (47→48 count-drift sweep across ~35 files, mermaid health score 82.3→100 on new diagrams, `docs-staleness-check.sh` RED→GREEN). New architecture docs: `docs/architecture/craft-restore-pipeline.md`, `docs/architecture/prompt-refiner-pipeline.md`. Homebrew formula PR data-wise/homebrew-tap#153 open (tested locally: `brew style`/`brew audit --strict`/`brew install` all clean, 48 commands). Full suite 2650 passed/0 failed. Release: https://github.com/Data-Wise/craft/releases/tag/v4.1.0 · PR: https://github.com/Data-Wise/craft/pull/295. Prior: **v4.0.0 SHIPPED 2026-07-16** — PR #294 (dev→main merge-commit `678e4d4a`, tag v4.0.0): craft docs-authoring surface extracted to the standalone `folio` plugin (folio v1.0.0 released FIRST, per the rollback runbook's ordering). Counts 94→47 commands / 45→40 skills / 8→2 agents. Homebrew formula regenerated with real values (sha256, command_count=47) via homebrew-release.yml (3/3 jobs green); `brew audit`/`brew upgrade` verified clean for both craft and folio formulas. Post-release docs sweep (same session): commands/hub.md + its docs/ mirror had drifted badly (stale example counts, 2 stray text artifacts, a main-menu ASCII template whose header counts didn't match listed items and referenced commands moved to folio or folded into skills); rewrote to match the real 47-command roster. API-REFERENCE-COMMANDS.md (1850 lines, last synced v1.24.0) converted to a thin index into docs/commands/*.md + docs/REFCARD.md rather than re-syncing a third documentation surface indefinitely; DEPENDENCY-ARCHITECTURE.md left untouched (its craft-vs-folio ownership question is still open, test_integration_dependency_system.py:38). Full pytest suite 2641 passed/0 failed. KNOWN, NOT FIXED: docs/REFCARD.md has the same staleness pattern across multiple sections (Site/Distribution/CI/CD subtotals, 20+ stale `/craft:git:worktree` examples for a command folded into the dev/git skill) — flagged, not yet swept. Folio's Aggregator Sync workflow fails by design (folio is a new plugin, never added to the shared Data-Wise aggregator 's marketplace.json — aggregator-sync.sh deliberately refuses to silently add unknown plugins; adding folio there is a separate, reviewed decision). CP-4 tasks/ cleanup: craft's tasks/ deleted directly on dev (unprotected); folio's tasks/session-plan-T2.4-CP2.md removed via PRs #15 (dev) and #16 (main) rather than a direct-branch delete (blocked by the permission classifier). Release: https://github.com/Data-Wise/craft/releases/tag/v4.0.0 · PR: https://github.com/Data-Wise/craft/pull/294. Prior: **v4 FOLIO SPLIT MERGED to dev 2026-07-16** — PR #290 (merge-commit `4af10f27d`, phase history preserved for the eventual v4 release tag): craft's docs-authoring surface extracted to the standalone `folio` plugin. **Counts 95/45/8 → 47/40/2.** NOT yet released (v2.61.2 still the current tag); `feature/folio-split` worktree + branch PRESERVED — Phase 4 (folio `/folio:do` + coordinated release, folio first / craft tag LAST) rides it. Phase 3.6 (router consolidations) CLOSED at **1 router of 5 planned**: `code:audit` shipped shrunk 5 cmds→2 (the `audit-router` skill, D12); `orch` (D11), `arch` (D6), and `ci` (D13) all DROPPED after reading the command bodies in full; `plan:feature` stayed excluded (pre-existing D2 lock). Root cause of the 4 drops, recorded in `docs/specs/GRILL-phase-3-6-router-consolidation-2026-07-15.md`: **the grill locked load-bearing decisions from line counts and flag names without reading the bodies** — every family that looked cohesive from surface metrics collapsed on a full read. D13 also withdrew its own planned `repo`-flag normalization as a premise artifact (no validator enforces cross-family flag consistency; the distinguishing test is *find the thing that fails if it's wrong*). Real defects fixed: 4 `category:` metadata mismatches (`ci/{fix,local}.md` declared `code`; `orch/{drive,workflow}.md` declared `orchestrate` against a nonexistent dir) — flagged by `help_file_validator._check_category_mismatch`, live-wired via `docs_update_orchestrator` → `/craft:docs:update`; a repo-wide sweep found the `orch` pair (pre-existing on dev). **Repo-wide category mismatches now 0.** T3.4 command floor 60→40 (`ci.yml:92`): the floor is a mass-deletion guardrail and had gone stale TWICE (86→60) while the real count fell 94→70→47, leaving it ABOVE the surface and hard-failing CI every run; rationale now commented in-file. `tests/test_git_shim_correctness.sh` INVERTED (not deleted) — it asserted `commands/git/*.md` were thin shims, but T3.5.2 completed that migration by deleting all 11, making the assertions unsatisfiable (7 FAIL on the branch, 21/21 on dev); it now guards the migration's END STATE (shims stay deleted · Operations 1–12 survive in `skills/dev/git/SKILL.md` · `issue-check` stays live · no unsanctioned command reappears), verified by 3 planted defects. `/craft:git:issue-check` KEPT as a live command (NOT folded into the git skill, reversing the initial plan): `tests/test_issue_check_unit.py` extracts its `classify_issue()` block from `commands/git/issue-check.md` and `exec()`s it, so the command file is the classifier's single source of truth — the same constraint as `commands/ci/triage.md` (D13). Git's directory-rename detection had silently relocated dev's new command into `skills/dev/git/references/` during the merge, which would have broken that test. Merge resolved 34 conflicts (~30 count-cascade, dev 95/45/8 vs branch 46/40/2) — every tracked count recomputed mechanically via `bump-version.sh --counts-only` rather than hand-resolved; the guard-script conflicts expected in `no-switch-guard.sh`/`install-guards.sh`/`version-check.sh` did NOT materialise. Prior: **PR #291 MERGED to dev 2026-07-16** (squash `88b221da9`): `ci-bash-suites` validator (`.claude-plugin/skills/validation/ci-bash-suites.md`) — closes the **gate-divergence** gap that caused two of #290's three CI blockers. `/craft:check` never ran the shell suites CI invokes directly because pytest doesn't collect them, so the local gate reported green for a surface it never touched; T3.5.2 shipped a regression citing "full pytest 2575 passed" while a CI-required bash suite had been red for days. Derives its suite list FROM `.github/workflows/*.yml` rather than hardcoding (craft has 22 `tests/*.sh`, CI runs **6** — the parse corrected a 4-suite assumption held all session); advisory in default mode, blocking in `release`, per the release-gated validator contract (`CRAFT_MODE` can't distinguish `--for pr` from `--for commit`). Verified by replaying the REAL T3.5.2 bug (pre-fix suite restored from `d41de18ee`) → caught. No count cascade (validators sit outside the counted surface). Also 2026-07-16: two stale claims fixed in `docs/commands.md` (`5034aef7d`) — a dry-run tip saying "46 commands" (the old TOTAL pasted into a dry-run claim; now 27) and "46 commands for documentation generation, validation, and site management" (now 2 — that sentence described a surface craft no longer has). Local `~/.claude/hooks/branch-guard.sh` re-synced via `install-guards.sh` (#290 merged the v4 hook text into dev, so the installed hook predated the repo and was advising a DELETED command, `/craft:git:guard disable branch-guard`) — this cleared the last known local test failure; dogfood 51 passed/0 failed. ⚠️ KNOWN, NOT FIXED: `docs/commands.md:10`'s "17 Categories" list is stale (`discovery-usage` demoted, `orchestrate`→`orch`, no `workflow` dir) but "category" has two defensible definitions that disagree — frontmatter-declared gives 10 (…`smart`), directories give a different 10 (…`utils`), and the current list matches neither; left for a decision rather than guessed at. Prior: Guard-hardening arc SHIPPED to dev 2026-07-15 — PR #287 (cumulative-cwd cd-target resolution, both guard hooks), PR #288 (guards.json write-race mkdir-lock, both writers), PR #289 (plan-orchestrator dispatch hardening); full detail in the 2026-07-15 Session Recap below. Counts unchanged 95/45/8; NOT yet released. Prior: PR #283 SHIPPED to dev 2026-07-14 (squash `057ea6785`): `/craft:git:issue-check ` — before implementing a fix requested by an open GitHub issue, checks whether the issue's premise still holds against current code (structured verdict: valid/moot/unclear, always cited evidence, never cached, advisory-only). Also gates `/craft:orch:drive` with a cheap `#NNN`-citation pre-filter and corrects `/recap`'s pointer copy. Built via `/craft:brainstorm` → `/craft:grill` (9 locked decisions in `docs/specs/GRILL-github-attention-triage-2026-07-14.md`; adversarial-review pass caught and corrected a factually-wrong dogfood target — issue #199 assumed moot, actually still open) → `/craft:plan` (plan-orchestrator, `ORCHESTRATE-github-attention-triage.md`) → `orchestrate-dispatch`. Dispatched agent completed all 5 phases correctly but silently failed to commit Phase 5 or update its own checkboxes before stopping mid-sentence — caught by the hang-detection cross-check (notification text vs. ORCHESTRATE file state), not a code failure; recovered by verifying git log + a fresh full suite run, fixing 5 newly-stale "94 commands" doc refs the count-cascade tooling missed, then committing + correcting checkboxes. PR CI initially failed on a genuine regression (broken relative link in the ORCHESTRATE file's own header, fixed same session) plus the 3 already-known `test_v115_adhd_enhancements` pre-existing failures; merged via `--admin` once confirmed unrelated. Counts 94→95/45/8; NOT yet released (v2.61.2 still current tag). Prior: Docs/website audit COMMITTED to dev 2026-07-09 (`d6aaa9096`) — 264 deleted-command refs stripped across 40 files, 5 whole-docs archived to `docs/archive/deleted-commands-v3/`, `mkdocs build --strict` clean. FOLIO SPLIT PLANNED (new initiative, supersedes dropped Phase 3): grill ledger `docs/specs/GRILL-folio-split-2026-07-09.md` (5 locked: tools+docs-standards scope · caller-based cut · drop DOCS from /do+hub · dup count tooling · history-preserving subtree, craft v4/folio v1) + `docs/plans/ORCHESTRATE-folio-split.md` (5 phases, P0 gates). P0 caller-audit RAN via dynamic Workflow (7 agents, adversarially verified, partition_sound): STAYS = docs:update + docs:changelog + site:deploy-as-shell; MOVES = 26; flagged docs:claude-md:* trio as craft-internal governance (likely stays) — partition NOT yet recorded into the plan docs. EXECUTION STARTED 2026-07-09 ("build auto"): worktree ~/.git-worktrees/craft/feature-folio-split live (T1.1 done, ORCHESTRATE + tasks/ moved to branch root); T1.2 folio repo creation gated on ask. ⚠️ ACCOUNT CI: zero-CI mode active across private repos since 2026-07-09 (craft is public = unaffected/free) — see memory `github-actions-zero-ci-strategy-2026-07` + savant .STATUS for the full record. Prior: Phase 1 native-first prune MERGED to dev 2026-07-09 — PR #279 (squash `4fa44fbb`): pruned 21 dead-namespace commands (115→94, −5533 lines) + excluded dead docs (specs/plans/archive) from the built mkdocs site. ⏹ NATIVE-FIRST THINNING REFUTED: a Stage-2 dynamic workflow (3 pilots × 4-axis gate + cross-plugin routing test) returned NO-GO on Phase 2 — the `do`/`hub` router structurally can't dispatch cross-plugin, AND 3/3 pilot replacements (native `/simplify`, `/code-review`, `agent-skills:ci-cd`) aren't ≥ craft on all 4 axes (weaker/auto-apply, third-party dep, or generate nothing). Phases 2 (cascade thinning) + 3 (`folio` docs/publishing split) DROPPED; folio only revisitable on docs-maintenance merits, grilled fresh. ADR-002 rich-body gate held mid-build: 3 of 24 "safe" deletions (`git:docs:refcard`, `check:gen-validator`, `workflow:insights`) carried unique logic → restored, not deleted (caught by `test_skill_referenced_commands_exist`). Verification gap fixed: pytest passed 2676 but CI's Validate-Plugin-Structure bash suite (`test_git_shim_correctness`) asserted deleted `git/{sync,git-recap}.md` exist → shim array updated (see memory `pytest-doesnt-cover-craft-ci-bash-suites`). Counts now 94/45/8 on dev; NOT yet released — v2.61.2 still the current tag. Full record: `docs/specs/SPEC-craft-native-first-breakup-2026-07-09.md` OUTCOME block. Worktree `feature/craft-native-first-breakup` removed + remote branch deleted 2026-07-09; local branch ref left stale (branch-guard blocks force-delete of squash-merged branch — documented deadlock, harmless). Prior: v2.61.2 SHIPPED 2026-07-09 — PR #275 (dev→main merge, tag v2.61.2) + PR #276 (CI fix): docs/NEWS.md release highlights page, CHANGELOG/VERSION-HISTORY v2.61.1 section, REFCARD.md version box corrected (2.61.0→2.61.2, counts 117→115), exclusions.txt archived-doc path fixed. PATCH bump. Docs Staleness + Markdown Linting GREEN; Validate Plugin Structure + Link Validation pending (known hang). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.2. Counts unchanged 115/45/8. Prior: v2.61.0 SHIPPED 2026-07-06 — PR #271 (dev→main merge `6c3e2e95`, tag v2.61.0): do.md dead-agent-dispatch removal + 24-doc sweep, `verify-surfaces.sh --report-only`/`--version` + 2 new legs (GitHub release, docs site) + release-rollback runbook, 4 confirmed branch-guard/no-switch-guard gaps closed, quota tooling removed (all from PR #270, squashed to dev pre-release), plus post-merge docs-audit fixes: 2 dead-agent surface-forms PR #270's hyphenated-only grep missed (`docs/index.md`'s feature grid, `skills/orchestrator-resilience/SKILL.md`'s fallback-dispatch example), a docs-staleness false positive (`orch.md`'s mode-table "4 agents"), and backfilled `CHANGELOG.md`/`docs/CHANGELOG.md` `[Unreleased]` entries missed during #270's own PR prep. MINOR bump. Full suite 2707 passed/1 pre-existing failure (`test_roadmap_orchestrator_enhancements`, stale `ROADMAP.md` path predating this release — root-caused and FIXED post-release in `dc6ee28a`, corrected to `docs/archive/ROADMAP.md`; this exact failure had forced `--admin` overrides on PR #270, PR #271, and `main` directly, three releases running). Merged via `--admin` (same pre-existing failure, confirmed by log each time). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.0 · PR: https://github.com/Data-Wise/craft/pull/271. Downstream: docs site live at v2.61.0 (confirmed via curl), tap formula confirmed v2.61.0 (live raw.githubusercontent fetch, not local clone), Homebrew Release + Aggregator Sync + Craft MCP Release workflows all SUCCESS. `dev` fast-forwarded to `main`'s merge commit, pushed. Counts unchanged 115/45/8. Also this session: grilled and CLOSED the orchestrate token-usage-hooks backlog (captured 2026-07-02) with NO build — two review passes (backend/architecture + interface/CLI-contract lenses) found hook 1 infeasible (no live token-introspection API exists for a prompt-driven orchestrator session), hook 3 structurally uncomputable for an in-progress run; the one surviving feasible piece (`--token-report`) wouldn't have fixed the retroactive-analysis problem that motivated the backlog, and duplicates the accounting-tooling category this same session already removed (quota tooling) — see `docs/specs/GRILL-orchestrate-token-usage-hooks-2026-07-06.md`. Prior: v2.60.0 SHIPPED 2026-07-05 — PR #269 (dev→main merge `763c5e8c`, tag v2.60.0): command-namespace reorganization (full grill-and-implement cycle, `docs/specs/SPEC-command-namespace-reorganization-2026-07-05.md`) — root promotions `/craft:next`/`/craft:done`/`/craft:refine`/`/craft:brief`/`/craft:brainstorm` (heavy real-world use); `orchestrate`→`orch` rename across ~110 files; `ci:` consolidation (`code:ci-fix`/`code:ci-local`→`ci:fix`/`ci:local`); new `docs:generate` router (9 generators, none merged — genuinely distinct logic); new `task:` namespace; `/craft:quota` deleted (folded into `/craft:orch` pre-flight); `code:desktop-watch` deleted (was a pure redirect shim); `adhd-guide` converted to a skill reference. Pre-flight caught and fixed 8 real test failures before release: 6 stale "117 commands" claims (actual 115) across dist/docs files not covered by `bump-version.sh --counts-only`'s file list, one hardcoded pre-rename path in `test_drive_command_in_nav`, and a missing TOC on the newly-moved `adhd-guide.md` (>300 lines, tripped the skill-standards release gate). MINOR bump. Full suite 2707 passed/0 failed/5 skipped; pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.60.0; homebrew-release workflow SUCCESS (tap formula confirmed v2.60.0 via live `gh api`, NOT the stale local `~/projects/dev-tools/homebrew-tap` clone which still read 2.58.0); aggregator PR #19 auto-merged in `Data-Wise/claude-plugins`; post-release sweep ALL CLEAN. Counts 115/45/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.60.0 · PR: https://github.com/Data-Wise/craft/pull/269. Prior: v2.59.0 SHIPPED 2026-07-04 — PR #260 (dev→main merge, tag v2.59.0): `plugin-audit` skill (44→45, cross-plugin command/skill collision detector, schema + false-positive fixes before merge, #237) + docs-site staleness hardening H1/H2/H3 (`docs.yml` live-site post-deploy version poll, stale-count guard extended to all of `docs/**/*.md`, #259). Counts 116/45/8 (later corrected to 115 in v2.60.0 after the `resume.md` deletion cascade settled). Prior: v2.58.0 SHIPPED 2026-07-02 — PR #251 (dev→main merge, tag v2.58.0): distribution-surface hardening — curl-pipe installer fixed to clone current `main` instead of a frozen v1.16.0 mirror (D1, 11 contract tests); homebrew-tap copy-based install (no symlinks); net-new `mcp/` TS MCP server (craft-mcp Desktop bridge, 3 read-only tools, DXT/.mcpb); esbuild audit-clear. Counts 116/44/8. Prior: v2.57.0 SHIPPED 2026-07-01 — PR #242 (dev→main merge `ec5797e6`, tag v2.57.0): grill attack-angles (#238 — 7 project-agnostic adversarial axes, angle 3 reads target project's own CLAUDE.md/ADRs at runtime; skill count 43→44) + orchestrate-family wave-1 (#239 — deleted fictional 523-line `resume.md` session-teleportation command, command count 117→116; thin-shimmed `orchestrate:plan` to `plan-orchestrator` skill) + doc-impact rubric extension (#241 — 4 new scored types Tutorial/API/Cookbook/Architecture-doc with tiered thresholds + arch-doc double-count subtraction rule + advisory Site Consistency checklist) + orchestrate-dispatch mode (#240 — 3rd `--output` value dispatching a background Agent from the live planning session, with confirm-before-dispatch gate, scoped concurrency cap, failure/hang detection at 2×-effort-estimate window, resumability, scoped `.STATUS` auto-write). MINOR bump (4 feat/refactor PRs). #240/#241 were background-agent dispatches using the very pattern #240 formalizes — monitoring protocol caught 1 self-introduced broken-link regression (a cross-tree link into `~/.claude/...` from this session's own BRAINSTORM doc, fixed on dev) + recovered a killed agent's completed-but-uncommitted Phase 3 work. A CHANGELOG merge-order conflict between #240/#241 (both added `[Unreleased]` entries) was resolved by rebase, keeping both. Pre-release-check initially reported doc-coverage as BLOCKING due to a macOS bash-3.2 `mapfile` incompatibility (false positive — confirmed PASS, 0 gaps, via Homebrew bash 5.x/zsh). Full suite 2072 passed/7 failed/13 errors (documented env-caused baseline, not regressions); pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.57.0; homebrew-release workflow SUCCESS (formula confirmed v2.57.0, desc "116 commands"); post-release sweep ALL CLEAN. Counts 116/44/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.57.0 · PR: https://github.com/Data-Wise/craft/pull/242. Prior: v2.56.0 SHIPPED 2026-07-01 — PR #234 (dev→main merge `43beb7b3`, tag v2.56.0): token-usage reduction (PR #232 squash `e6c24a27` — orchestrator model pinning, `/refine` 631→42 lines, `/brainstorm` redesign, new `command-skill-token-efficiency` skill) + release-tracking hardening (namespace-refactor token-cost go/no-go measured 68.3% reduction; broken `/usage` scheduled-trigger replaced with `ccusage`+`claude-monitor`; 2 cross-spec factual bugs fixed via a second grill pass). MINOR bump (feat: #232). All CI checks GREEN on first pass — no `--admin` needed (Validate Plugin Structure resolved cleanly this time). Full suite 2089 passed; pre-release-check PASSED; main CI GREEN; live site confirmed v2.56.0; homebrew-release workflow SUCCESS; post-release sweep ALL CLEAN. Counts 117/43/8. Prior: v2.54.0 SHIPPED 2026-06-27 — `/craft:workflow:brief` (117th command): 3-line action block generator (Next step / Watch out for / Connects to) with `--plan`/`--verbose`/`--show-context` flags; `--brief` integrated into `/craft:do` Step 5.5. 29 e2e + 12 dogfood tests. Main CI GREEN; docs deployed; homebrew tap updated (117 commands); post-release sweep CLEAN (getting-started.md fixed); all BLOCK surfaces aligned. Counts: 117 / 39 / 8. Prior: v2.53.0 SHIPPED 2026-06-26 — multi-surface-aware release (surface registry + aggregator CI propagation + Cowork verify leg + `/craft:dist:surfaces`, #218/#184) + docs-staleness fix (docs/plans/ excluded from count-consistency scan). MINOR bump (feat). All tests pass; main CI GREEN; docs deployed; homebrew tap updated (116 commands); post-release sweep ALL CLEAN; aggregator sync wired (all 4 satellite repos). Counts 116/39/8. Prior: v2.52.0 SHIPPED 2026-06-26 — interactive-by-default commands (#215) + default-on test/docs scaffolding (#216) (PR #217 dev→main merge `c5f704f3`, tag v2.52.0, `--admin` on stuck Validate-Plugin-Structure): grill/orchestrate AskUserQuestion model (Recommended-first + per-option consequence) + `--yes`/`--non-interactive`; refine default-on for brainstorm/do/plan:feature/grill (opt-in elsewhere) + `--yes` cascade + orchestrate Step 0.5 structured model; AND brainstorm/plan:feature/grill emit a tier-inferred test-plan + doc-scorer Documentation section BY DEFAULT (`--no-tests`/`--no-docs`; arch:plan/spec-review opt-in via `--tests`/`--docs`), logic in skills (brainstorm-insights/plan-orchestrator, deprecation-trap-safe + guarded), doc-scorer REUSED, lifecycle-split, count-cascade-excluded. Built spec→grill→plan→subagent-driven-TDD (2 plans, 7+8 tasks, per-task spec/quality + opus whole-branch reviews; gates caught a dropped-phrase regression, a refine over-flip, a decorative deprecation-guard sentinel, and a MEMORY.md over-compaction that dropped 37/102 pointers → all fixed). MINOR bump (4 feat). Full suite 1990 passed; pre-release-check PASSED; main CI GREEN; docs deployed (live site 2.52.0); Homebrew Release auto-updated tap; verify-surfaces 6/6 ALIGNED (refreshed local lag: brew formula 2.51→2.52 via `--formula`, marketplace-update-THEN-plugin-update for the Code pin); post-release sweep ALL CLEAN. Counts 115/39/8. Prior: v2.51.0 SHIPPED 2026-06-25 — governance retro (#205) + skill-standards release gate (PRs #211/#212→dev, #214 dev→main merge `180181f8`, tag v2.51.0; CLOSED #205 + #138 via default-branch auto-close): D1 **R09 status-not-drift** governance check (`governance/checks/status_drift.py`, warn) — audits `.STATUS` `version:` vs manifest + SHIPPED/tagged vX.Y.Z vs git tag, fixture `--selftest` + LIVE in `pre-release-check.sh` (this very release was self-gated by it: ".STATUS: version + release claims match" ✓); D3 ADR-003 (release-drift advisory not hard-gate + P7 freeze); skill-standards validator graduated to a **release-tier gate** (`CRAFT_MODE=release` blocks, advisory below) + fixed a latent CRAFT_MODE no-op (LLM-prose command never exported the gating var → gate+version-check silently dead). MINOR bump (2 feat: #211/#212). Full suite 1970 passed; pre-release-check PASSED; main CI GREEN; **verify-surfaces 6/6 ALIGNED** (after refreshing local lag: brew upgrade 2.50→2.51, `claude plugin update` 2.50→2.51, pulled stale `~/projects/dev-tools/homebrew-tap` checkout — the script reads that local clone FIRST, was the only "blocker"); post-release sweep ALL CLEAN. Counts 115/39/8. Process: merged #211/#212 (conflict-free despite branches behind dev — `--stat` looked conflicted but they authored ZERO changes to `.STATUS`/`CLAUDE.md`/`governance.md`; verified via `merge-tree --write-tree`), pushed a local-only gate branch first (data-loss risk), `git:clean` removed 2 worktrees (11+2 squash-merged branch refs left dangling — branch-guard blocks `-D`, harmless). Prior: v2.50.0 SHIPPED 2026-06-25 — v2.49.x issues sprint (PRs #206/#207/#208→dev, #209 dev→main --admin on phantom Validate Plugin Structure, tag v2.50.0): advisory skill-standards validator in `/craft:check` (hot-reload, never fails); homebrew dist-gates `verify_caveats.py`+`post_install_check.py` wired into `/release` Steps 10b/10c (advisory, strict via HOMEBREW_GATE_STRICT) + 10d (BLOCKING aggregator-sync + Cowork verify) (#200/#199); SessionEnd insights facet hook + idempotent installer, per-session-id dedup (#183); ADR-001 Workflow-vs-worktree (#171); +14 e2e/dogfood tests pinning locked decisions. Built via craft:grill (10 decisions D1–D10) → superpowers writing-plans (4 TDD tracks) → 2 dynamic Workflows (impl+verify, ran from dev session under branch-guard bypass, re-protected after). MINOR bump (3 feat: commits). Full suite 1958 passed; pre-release-check PASSED; main CI GREEN; tap+brew+Code+marketplace all 2.50.0 (verify-surfaces 6/6 ALIGNED); post-release sweep CLEAN. KEY INCIDENTS (recovered): (1) #206 squash folded UNPUSHED local-dev base commits → #207/#208 phantom conflicts → rebased --onto origin/dev to untangle (memory squash-folds-unpushed-dev-base-commits; push dev before PRing off it); (2) GitHub comma-list `Closes #200, #199, #183, #171` only auto-closed #200 (+#171 via Track-0 trailer) — each issue needs its OWN keyword; closed #183/#199 manually. Counts 115/39/8. Prior: v2.49.0 SHIPPED 2026-06-23 — skill-standards auditor + /done memory/settings + ADR-002 + release re-split (PR #203 auditor→dev, #204 release dev→main --admin, tag v2.49.0): `/craft:code:skill-standards` batch-audits `skills/**/SKILL.md` vs a vendored copy of Anthropic's standards (score+exit 0/1/2, `--json`/`--markdown`, `--fix` = version-tag-strip + frontmatter-normalize + TOC-stub, never prose; `--refresh-standards`); `/done` Memory Optimize (Step 1.12) + Settings Sync (Step 1.10.5); ADR-002 consolidated `/done` into the `adhd-workflow` skill (`references/done.md` SoT + thin shim); 4 oversized skills split to `references/` (release re-split loss-gated after a lossy first attempt, 0 lines lost) → 39 skills 100/100. Counts 114→115. Release: dev was unsynced with main (v2.48.0 release merge commits) → dev-wins sync merge resolved the PR #204 conflict; full suite 1929 passed; main CI GREEN; docs deployed; tap synced. POST-RELEASE TOOLING (homebrew-tap + savant): root-caused the local Code-registered lag — the formula `post_install` ran `claude plugin update` WITHOUT first `claude plugin marketplace update local-plugins` (no-op on a stale cache); fixed in `homebrew-tap/generator/generate.py` for ALL claude-plugin formulas; reconciled manifest-drift across 6 formulas (rforge-orchestrator's `deprecate!` was at risk of silent un-deprecation; himalaya-mcp 12 skills/22 MCP tools; scholar 28→34; craft `{command_count}` single-source token) gated by regen==committed; added a Formula Drift Guard CI workflow (regen-vs-committed, first run GREEN); savant `CLAUDE.md` 19→25 skills. Memories: post-install-marketplace-refresh-before-update, deprecated-command-rich-body-trap, skill-authoring-tooling-landscape. Prior: v2.48.0 SHIPPED 2026-06-23 (grill + orchestrate Step 0.5 Clarify). Prior: v2.47.0 SHIPPED 2026-06-21 — Governance roadmap COMPLETE (PR #196/#197→dev, #198→main, tag v2.47.0): R04 automated as content-drift (manual→checks/no_drifted_copy.py — installed SKILL.md must be byte-identical to canon; DISTINCT from R07 version-pin, catches a hand-edited body even when the version matches; gates session, vacuous-skip otherwise) + #184 release pre-flight advisory (pre-release-check.sh runs run_rules.py --json, prints RED count, NEVER blocks — gentle-ramp; TestReleaseGuard184 invariant) + live-flow test coverage (e2e soak lifecycle hook→ledger→promote-check + R04-in-audit; dogfood ledger edges). Governance suite 47→59. KEY FINDING (memory r04-content-drift-not-filetype): naïve "real-dir=copy" heuristic was dead (installs are real dirs, no symlinks) → content-drift; always fire a new WARN checker on the live env first (a false positive poisons the soak ledger). #198 merged CLEAN (no --admin). Homebrew tap synced to v2.47.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Governance now: R01–R04+R06 automated · R05 advisory · R07 external · R08; 5 surfaces (visibility·prevention·promotion·cross-repo·release-advisory). Prior: v2.46.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #3 (PR #194→dev, #195→main, tag v2.46.0): soak-then-flip promotion + cross-repo wrapper, completing Phase 2 (visibility · prevention · promotion · cross-repo). soak.py = LOCAL gitignored STATE.json ledger (first_seen/last_seen/last_red per rule) fed by the SessionStart hook each audit; run_rules.py --promote-check (--window 14d, --state) lists warn rules soaked clean long enough to recommend a human warn→error flip (machinery recommends, human promotes; advisory exit 0). run.sh = cross-repo wrapper (consumers invoke the one installed engine, cwd-portable, no drift, R07). 8 dogfood tests (injected today → no flake), 47 governance suite green. Bundled #193: test_performance_cached_run → xfail(strict=False) (wall-clock microbenchmark flaked on shared CI, forced --admin; now a soft signal) — and #195 MERGED CLEAN with NO --admin (the xfail paid off). Homebrew tap synced to v2.46.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. R04 automation + #184 release-pre-flight guard remain deferred. Prior: v2.45.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #2 (PR #190→dev, #192→main, tag v2.45.0): SessionStart visibility hook (governance/session_hook.py) — audits the live ~/.claude/skills tree at session open and injects a compact RED-only summary into context via SessionStart additionalContext; silent when clean, mtime-cached, no-op where skills tree absent, never breaks a session on error. Visibility not prevention (SessionStart hooks inject context, can't block). Completes the three-surface model: pre-commit + CI (prevention) · SessionStart (visibility). Implemented direct-to-worktree from the dev session (worktree-path-aware). #192 admin-merged (strict up-to-date BEHIND from the v2.44.0 merge-commit divergence; PR-context full suite GREEN). Homebrew tap synced to v2.45.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Global install of the hook DEFERRED to post-v2.45.0 (point at released plugin path; docs in governance/README.md). PR #3 (soak-then-flip + cross-repo wrapper) + R04 deferred. Also: removed leaked ORCHESTRATE-guard-suite.md (PR #191). Prior: v2.44.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #1 (PR #188→dev, #189→main, tag v2.44.0): R03 automated (manual→script — checks/no_private_in_public_marketplace.py scans a marketplace manifest, fails on a public-marketplace ref to a private/PII repo, denylist {savant}, dual-mode file/dir + good/bad fixtures; closes the last error-rule enforcement gap), pre-commit governance-gate hook (^governance/ → selftest + render --check drift) + CI gate in ci.yml (with explicit "CI does NOT evaluate R01/R07 live-env" note), engine {marketplace} substitution through audit()+selftest() + --marketplace. Built from a draft→adversarial-verify→ultra-finalize workflow chain (verified packet); implemented directly into the worktree from the dev session (branch-guard is worktree-path-aware — corrected a stale assumption). Also corrected stale "not yet wired" governance docs. #189 admin-merged (only failure was the flaky test_performance_cached_run timing test, unrelated to the diff; PR-context full suite GREEN). Homebrew tap synced to v2.44.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 PR #2 (SessionStart hook + soak-then-flip + cross-repo wrapper) NEXT; R04 deferred. Prior: v2.43.0 SHIPPED 2026-06-20 — Skill-Ecosystem Governance (Phase 0) (PR #185→dev, #187→main, tag v2.43.0): policy-as-code under governance/ — RULES.yaml (8 skill-location rules, single source of truth), run_rules.py (audit + --selftest meta-validation, fail-closed), render_rules.py (CLAUDE.md block generation + --check drift gate), portable checkers + good/bad fixtures, guide + Plugin Release Runbook, 24 e2e/dogfood tests + governance pytest marker. PR #185 review hardening (all 5 issues): fail-closed gating (broken checker on error rule exits 1), recursive broken-symlink detection, R01 gates session (not ci) + visible canon-skip, --check exit-2, --selftest surfaces external rules, UTF-8. Homebrew tap synced to v2.43.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 enforced-gates roadmap spec open in PR #186. Prior: v2.42.0 SHIPPED 2026-06-19 — /craft:code:fewer-prompts curated read-only Bash allowlist (PR #181). Prior: v2.41.1 SHIPPED 2026-06-19 — Documentation Coverage (PR #180): 25 tutorial stubs for all Phase 8 commands + broken link fix in TUTORIAL-smart-help.md. Prior: v2.41.0 SHIPPED 2026-06-19 — token-efficiency (quota gate, engine flag, parity runbook). v2.40.0 SHIPPED 2026-06-19 — Guard Suite (PR #174): /craft:git:guard + guards.json registry & ADHD toggle UX, no-switch-guard promoted to scripts/, guard-consistency validator, install-guards.sh. Destructive-restore reconciled to no-switch-guard; branch-guard rm-rf-.git/reset--hard softened to confirm (catastrophic ops stay NON-muteable). 111 commands. Review found 1 CRITICAL (catastrophic-bypass) + 6 issues — ALL fixed pre-merge; +5 registry tests; ci.yml now installs both guards + runs the no-switch suite. Prior: v2.39.0 SHIPPED 2026-06-19 — doc-coverage enforcement (PR #172→dev, #173→main). Prior: v2.38.2 SHIPPED 2026-06-15 — multi-surface release arc (3 patches). v2.38.0: multi-surface engine (verify-surfaces Step 13.6, cache-prune Step 13.7, Data-Wise aggregator marketplace) + branch-guard draft=dev (PRs #158/#159/#160/#161). Adversarial review found 3 HIGH + 6 MEDIUM/LOW bugs → v2.38.1 (PR #163: branch-aware dogfood tests fixed RED main CI; cache-prune data-loss; verify-surfaces git-tag no-op) → v2.38.2 (PR #165: corrupt-surface blocks, symlink GC, arg-parse, subshell). All 3 → main, main CI GREEN, tap + aggregator (Data-Wise/claude-plugins) pinned to 2.38.2, post-release sweep CLEAN. Counts: 112 commands / 39 skills / 8 agents. Prior: v2.37.0 SHIPPED 2026-06-13 — drift-prevention chain + CI triage/watch. Bundled brainstorm items 1-7 (PRs #151/#152/#153/#154/#155): #1 strict-markers guard + #4 workflow recipe discoverability; #7 bump-version categorical-subtotal sweep; #2 CI count-drift tripwire (README live counts); #5 /craft:ci:triage + #6 /craft:ci:watch. PR #156 → main 8c21a54d (merge commit), release tag v2.37.0. Downstream all GREEN: main CI verified, homebrew-release.yml updated tap to v2.37.0 (desc "112 commands", url v2.37.0.tar.gz). Post-release sweep ALL CLEAN (drift tooling paid off). Counts: 112 commands / 39 skills / 8 agents. Prior: v2.36.0 SHIPPED 2026-06-13 (PR #150 → main eb7e143d). +last_session: 2026-08-07 ## Session Recap — 2026-08-01 — craft-mcp bundling committed; cwd-marker bug found From 9bd5744ffdc5861f0812e975de961e2f1fab260b Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Sat, 8 Aug 2026 23:00:58 -0600 Subject: [PATCH 11/24] docs: OpenCode MCP bridge tutorial + codex/opencode delegation spec New OpenCode MCP bridge cheat sheet (opencode + opencode-async), routing table and mandatory-monitor guidance in both delegation tutorials, and a brainstorm -> dual-review -> grill -> spec chain scoping delegation to two use cases (arch:review adversarial review, brainstorm research escalation) with the rest parked. Both docs were adversarially reviewed by codex and opencode/deepseek-v4-pro themselves before this commit. Co-Authored-By: Claude Sonnet 5 --- ...RM-codex-opencode-delegation-2026-08-08.md | 271 ++++++++++++++++++ ...LL-codex-opencode-delegation-2026-08-08.md | 41 +++ ...EC-codex-opencode-delegation-2026-08-08.md | 137 +++++++++ docs/tutorials/TUTORIAL-codex-plugin.md | 26 ++ .../tutorials/TUTORIAL-opencode-mcp-plugin.md | 127 ++++++++ mkdocs.yml | 1 + 6 files changed, 603 insertions(+) create mode 100644 docs/specs/BRAINSTORM-codex-opencode-delegation-2026-08-08.md create mode 100644 docs/specs/GRILL-codex-opencode-delegation-2026-08-08.md create mode 100644 docs/specs/SPEC-codex-opencode-delegation-2026-08-08.md create mode 100644 docs/tutorials/TUTORIAL-opencode-mcp-plugin.md diff --git a/docs/specs/BRAINSTORM-codex-opencode-delegation-2026-08-08.md b/docs/specs/BRAINSTORM-codex-opencode-delegation-2026-08-08.md new file mode 100644 index 000000000..a2af57b02 --- /dev/null +++ b/docs/specs/BRAINSTORM-codex-opencode-delegation-2026-08-08.md @@ -0,0 +1,271 @@ +# BRAINSTORM: Delegating to Codex and OpenCode + +**Date:** 2026-08-08 · **Depth:** deep · **Focus:** ops +**Branch:** dev · **Categories:** tech, risks, timeline + +## Origin + +Seed request (refined via `--refine`): research (web) best practices for +delegating coding tasks from Claude Code to OpenAI Codex (via the `codex` +plugin) and to OpenCode (via the `opencode`/`opencode-async` MCP bridges +just installed this session — both promoted to `user` scope, tested working +over stdio JSON-RPC: `opencode` exposes ~80 tiered tools, `opencode-async` +exposes 7 fire-and-forget tools). Not scoping a new feature — scoping how to +*use* three delegation surfaces craft's user now has installed: + +| Surface | Style | +|---|---| +| `codex` plugin (`codex:codex-rescue` agent) | Synchronous hand-off — Claude drives `codex exec` as a subprocess, one broker per cwd | +| `opencode` MCP bridge | Synchronous-first, tiered (`opencode_setup` → `opencode_ask`/`opencode_run` → fine-grained session control) | +| `opencode-async` MCP bridge | Fire-and-forget — submit, poll `opencode_sessions`, `opencode_respond` on `input_required` | + +## Context Scan + +- **No existing craft spec covers delegation strategy.** `grep -ril + "codex\|opencode" docs/specs/` (excluding `_archive/`) found nothing — + this is genuinely new ground for craft's own docs, even though the tools + themselves were just wired up this session. +- **`docs/tutorials/TUTORIAL-opencode-mcp-plugin.md`** (added this session) + covers *setup and mechanics* of the two OpenCode bridges — scope, gotchas, + smoke-test method. It does not cover *when/how to use them well*, which is + this brainstorm's subject. +- **`agents/codex-rescue`** (via the `codex` plugin) already encodes one + delegation trigger informally: "proactively use when Claude Code is + stuck, wants a second implementation or diagnosis pass." No equivalent + trigger doc exists for either OpenCode bridge yet. + +## Research Findings (web) + +**Task selection — when delegation is worth it at all.** The dominant +framing across sources: delegate when *describing* the task is cheaper than +*doing* it. A one-line rename isn't worth a delegation round-trip; a +codebase-wide migration is. Good delegation candidates: test generation, +boilerplate, documentation, simple refactors, well-scoped SWE tasks. Poor +candidates: security logic, architectural decisions, unfamiliar stacks, +anything the delegator can't verify afterward. Subagents/delegated calls +are "not free" — startup cost, coordination overhead, and failure modes +that don't exist when you just do the work yourself. + +**Model/tier selection.** Not every delegated task needs the strongest +model. A repeated pattern: cheap/fast models for narrow workers (routing, +formatting, simple extraction), stronger reasoning models reserved for the +orchestrator or for genuinely hard sub-tasks. `opencode`'s own tool +description matches this — it explicitly warns against assuming a provider +is available and tells the caller to discover providers/models before +picking one per task. + +**Structuring the delegated prompt.** Practical guidance converges on: +single-line messages (no embedded newlines — when driving an interactive +TUI, a newline can act as Enter and fragment the prompt; this does not +apply to `codex exec`, which takes the prompt as a plain argument/stdin +string, not keystrokes into a TUI), explicit scope statements, and — for +OpenCode specifically — picking the right agent mode (`explore` for +investigation, `plan` for structured analysis, `build` for direct +execution) rather than defaulting to one mode for everything. + +**Orchestration pattern.** The Manager-Worker / hierarchical pattern shows +up repeatedly as the best fit for coding delegation: one agent plans and +assigns, worker agents execute narrow scoped pieces, the manager reviews. +Each worker gets a fresh context and a clean failure boundary, which +contains cascading failures rather than propagating them back through the +whole session. + +**Token cost — directly answers the "token usage" risk flagged below.** +Every hop in a delegation chain that invokes an LLM is a billed API call — +a topology that looks cheap in-process can multiply spend once each +specialist is a real request. The mechanism to watch: the orchestrator's +own context grows with every round-trip result that flows back to it — +sources report a 5-10 sub-agent pipeline can push the orchestrator itself +to roughly 30-50k tokens by the end, independent of what the workers +spent (single-source estimate, not independently measured here). Two +mitigations, with tradeoffs reported the same way: (1) pass a small +structured context object instead of full conversation history (roughly +200-500 tokens vs. 5,000-20,000), or (2) summarize at the handoff (roughly +a 70-90% reduction) at the cost of ~500ms-1.5s added latency and some +information loss. Execution budgeting (max tokens, max steps, max time per +delegated call, with alerts on threshold breach) is the other lever named +repeatedly. + +**Failure handling.** Consistent theme: isolate the blast radius. A failed +worker should fail cleanly within its own context rather than corrupting +the manager's state. The two OpenCode bridges use *different* status +vocabularies, not a shared one: `opencode-async`'s task status is +`working` / `input_required` / `completed` / `failed` / `cancelled` +(`opencode_cancel` reaches the last one); the npm `opencode` bridge's +`opencode_check` reports `running` / `completed` / `error` instead — do +not assume interchangeability between the two when writing status-handling +code against either. + +Sources: + +- [GitHub - eddiearc/codex-delegator](https://github.com/eddiearc/codex-delegator) +- [Codex Task Dispatcher - Claude Code Skill for OpenAI](https://mcpmarket.com/tools/skills/codex-task-dispatcher) +- [Claude codex-cli skill: 10 ways to bridge Claude Code and OpenAI Codex CLI](https://mcp.directory/blog/claude-codex-cli-skill-guide) +- [GitHub - Traves-Theberge/opencode-mcp](https://github.com/Traves-Theberge/opencode-mcp) +- [Best practices for Mastering AI Agents, Subagents, Skills & MCP](https://foojay.io/today/best-practices-for-working-with-ai-agents-subagents-skills-and-mcp/) +- [Cost Management for LLM Agents](https://apxml.com/courses/multi-agent-llm-systems-design-implementation/chapter-6-system-evaluation-debugging-tuning/managing-llm-agent-costs) +- [Multi-Agent Orchestration Patterns: A Practical Guide](https://www.glukhov.org/ai-systems/architecture/multi-agent-orchestration-patterns/) +- [AI Agent Subagent Orchestration: When to Spawn vs When to Do It Yourself](https://dev.to/bobrenze/ai-agent-subagent-orchestration-when-to-spawn-vs-when-to-do-it-yourself-4opg) +- [The Delegation Decision: When to Use an Agent vs Do It Yourself](https://agentpatterns.ai/agent-design/delegation-decision/) + +## Expert Questions & Answers + +**Technical constraints/preferences** (multi-select): Use existing stack +(research the 3 tools already installed, don't evaluate new ones) + New +tooling needed (stay open to surfacing other options if research turns them +up) + Architectural pattern (define routing rules for which task type goes +where). → Net: research the installed stack deeply, but the output should +still propose a routing rule, and flag credible alternatives if the +research surfaces them unprompted. + +**Integration target** (multi-select): craft's own commands/skills + +Ad-hoc manual use only. → Net: findings should be usable standalone today +(manual delegation decisions), but written so a future craft skill/command +could consume the routing rule directly without a rewrite. + +**Timeline:** Flexible. **First usable outcome:** Flexible. No deadline +pressure — quality over speed on this one. + +**Biggest risks** (multi-select): Technical complexity (routing logic) + +Integration issues (auth/model availability) + Performance concerns +(latency/cost) + **token usage for codex and opencode models** (user +addition, not from the question bank — see Token Cost research above, +directly responsive). + +**Edge cases** (multi-select): Failure scenarios (delegated agent +hangs/errors/garbage output) + Concurrent access (codex's per-cwd broker + +opencode-async's session model, both need to survive multiple in-flight +delegations) + Empty/invalid input (vague delegated prompts producing +useless output). + +## Proposed Routing Rule (ops output) + +Based on research + the answers above, a first-cut decision rule — not yet +adopted as policy, offered for the follow-up spec if this gets built out: + +1. **Do it yourself** when: the task is smaller to describe than to do, it + touches security/architecture/unfamiliar-stack decisions, or you can't + independently verify the result. +2. **Delegate to `codex`** when: it's a genuinely hard, well-scoped SWE + task (complex refactor, deep debugging) and you want a second + implementation/diagnosis pass — matches the existing `codex-rescue` + agent's own trigger condition, no new logic needed there. +3. **Delegate to `opencode` (sync)** when: the task is a one-shot + question/lookup that benefits from a different model's perspective, and + you want the answer inline, in this turn — `opencode_ask` after + `opencode_setup`/`opencode_provider_models` discovery. +4. **Delegate to `opencode-async` (fire-and-forget)** when: the task is + long-running and you don't want to block this session on it — submit, + keep working, poll `opencode_sessions` later. +5. **Always budget before firing:** state an explicit stop condition (max + turns/time) in the delegated prompt itself where the target tool + supports it: uncontrolled delegation chains are the direct cause of the + token-cost risk flagged above. + +## Risks & Edge Cases (recorded, not yet mitigated in code) + +- Token spend compounding across delegation hops — no craft-side budget + enforcement exists yet for either bridge. +- No craft-side timeout/hang detection wired to either `opencode` bridge + today (craft's own `orchestrate-dispatch` mode has this for its own + subagents — worth reusing the pattern rather than inventing a new one, + per the "reuse, don't duplicate" instinct that shaped the `codex` plugin + integration). +- Concurrent delegation to the same cwd via `codex` (single broker per + directory) vs. multiple parallel `opencode-async` sessions — not + stress-tested. + +## Test Plan + +| Tier | Status | +|---|---| +| unit | N/A — no new parser/script proposed by this brainstorm | +| integration | N/A — no cross-command data flow proposed yet | +| e2e / dogfood | N/A — no craft command/skill change proposed yet; this is a research artifact, not an implementation | +| dependency | N/A — no external dependency change | +| count-cascade | N/A — no new command/skill/agent | + +Nothing here is code-shaped yet — the routing rule above is a candidate for +a future skill, not a shipped one. Re-run test-plan inference once/if this +becomes a `/craft:plan` implementation. + +## Documentation + +Doc-impact scorer (threshold ≥3) applied against the routing rule above, +should it ship as a skill later: + +- [ ] Guide — N/A, no implementation yet +- [ ] REFCARD entry — N/A, no implementation yet +- [ ] Demo — N/A, no implementation yet +- [ ] Mermaid diagram — N/A, no implementation yet + +This BRAINSTORM itself is the documentation artifact for now. + +## Adversarial Review (dogfooding the routing rule on itself) + +Both delegation surfaces this brainstorm is about were used to review the +uncommitted diff that produced it (this file + the tutorial + the mkdocs +nav line) — the routing rule ate its own dog food same-session. + +| Reviewer | Model | Cost | Tokens | Findings | +|---|---|---|---|---| +| `codex` (`codex exec review --uncommitted`) | GPT-5-based (Codex default) | not reported by this CLI | 1,010,356 total (1,002,654 in @ 91% cache hit, 7,702 out) | 2 (both real: markdownlint gap, wrong tool count) | +| `opencode` (`opencode_run`, provider `opencode-go`) | `deepseek-v4-pro` | $0.0003 | 1,555 total (267 in, 1,288 out) | 6 (see below) | + +**Cost delta is ~650× by token count** for this task — not a general claim +about the models' relative capability, just this one review of a +~250-line diff. Codex's number is inflated by a large, mostly-cached +system-prompt/tool context (skills + instructions bundle), not by doing +more review work; `opencode`'s dispatch had no comparable fixed overhead +in this setup. **Not a dollar comparison** (added on adversarial +re-review, GRILL Branch 4): codex ran on a flat subscription (sunk cost, +no marginal $ per call), while `opencode-go` bills per call — token count +is still a real signal (context-window pressure, rate limits apply under +a subscription too), just don't read "650×" as a cash-cost claim. + +**opencode/deepseek-v4-pro findings** (both fixed in this doc + the +tutorial as part of this same session): + +1. **F1 (High):** this brainstorm's own Origin section still said "6 + fire-and-forget tools" after the tutorial's copy had already been fixed + to 7 — the two docs contradicted each other. Real bug: codex's review + never caught this because it only reviewed the diff, and this line + hadn't changed in the diff it saw (it was wrong from the first version). +2. **F2 (High):** the Failure Handling section asserted `opencode` and + `opencode-async` share one status vocabulary + (`working`/`input_required`/`completed`/`failed`). False — that set is + `opencode-async`-only; `opencode`'s `opencode_check` reports + `running`/`completed`/`error`. Also caught a missing `cancelled` status. +3. **F3 (Medium):** tutorial said the usage guide lives at + `serverInfo.instructions` — it's actually a top-level `instructions` + field, sibling to `serverInfo`, not nested in it. +4. **F4 (Medium):** the newline-as-Enter caveat (from web research) was + misattributed to `codex exec` specifically — that mode takes the prompt + as a plain argument, not TUI keystrokes, so the hazard doesn't apply + there. Softened to the general interactive-TUI case. +5. **F5 (Low):** missing caveats — `OPENCODE_AUTO_SERVE=false` disables + auto-start; `cancelled` status wasn't surfaced (see F2); the two docs + should cross-link each other. Not individually actioned (low value, + noted here for completeness). +6. **F6 (Low):** several web-research numbers (30-50k token orchestrator + growth, 70-90% summarization reduction, etc.) were stated as fact + without a confidence hedge or citation in the Sources list — corrected + to "sources report" / "roughly" framing. + +**codex/GPT-5 findings** (both fixed earlier in this session, see the +commit history): missing blank line before the Sources markdown list +(markdownlint), and the same wrong tool-count bug F1 re-caught in a +different file. + +**Takeaway for the routing rule:** two independent reviewers with +different strengths caught non-overlapping bugs — codex caught a lint gate +failure (mechanical, would have failed CI); opencode caught two factual +inconsistencies a lint gate can't see (contradicting docs, a misattributed +technical claim). Neither was redundant with the other. This is itself +evidence for the "use both, not just one" instinct in the routing rule +below rather than a full head-to-head model comparison (n=1 diff, not a +benchmark). + +## Next + +- **Now spec'd.** See `docs/specs/SPEC-codex-opencode-delegation-2026-08-08.md`. diff --git a/docs/specs/GRILL-codex-opencode-delegation-2026-08-08.md b/docs/specs/GRILL-codex-opencode-delegation-2026-08-08.md new file mode 100644 index 000000000..3a0e788b2 --- /dev/null +++ b/docs/specs/GRILL-codex-opencode-delegation-2026-08-08.md @@ -0,0 +1,41 @@ +# GRILL: Codex/OpenCode Delegation Routing — Adversarial Review + +**Date:** 2026-08-08 · **Target:** [SPEC-codex-opencode-delegation-2026-08-08.md](SPEC-codex-opencode-delegation-2026-08-08.md) +**Branches interrogated:** 5 (riskiest assumption, weakest recommendation ×2, benefit honesty, workflow discipline/default policy) + +Codebase-first sweep found the load-bearing issue before any question was asked: `codex-rescue` +is owned by the separately-installed `codex` plugin, not craft — no cross-plugin agent +invocation exists in this repo (established by craft's own prior router-consolidation audit). +Three of the SPEC's five candidate rows cited it as reuse precedent; Branch 1 corrects this. + +## Decision Ledger + +| # | Branch | Decision | +|---|---|---| +| 1 | delegation mechanism | Craft's `--delegate` flag shells to `codex exec` directly (same mechanism as the tutorial's Monitor template), never through the `codex-rescue` agent — that agent belongs to a different plugin and can't be invoked cross-plugin. SPEC's "why" column for `code:debug`/`code:refactor`/`ci:triage` needs rewording to drop the implied reuse; the underlying candidate choices stand, only the mechanism description was wrong. | +| 2 | monitoring enforcement | The "mandatory monitor" step is documentation-only today (copy-paste templates, nothing enforces use). New acceptance criterion: any future command implementing `--delegate` must ship with a test asserting its dispatch path includes a status/token check — same enforcement pattern as `--refine`'s `test_refine_default_policy_table_exhaustive`. Craft's own hooks-as-defense-in-depth precedent says prompts/docs alone get silently skipped; a doc-only requirement here would regress to fire-and-forget exactly the way the SPEC's own Problem statement complains about. | +| 3 | ci:fix vs ci:triage | Only `ci:triage` gets `--delegate`, not `ci:fix`. Triage is diagnosis-only (advisory output, human decides) — safe to delegate. `ci:fix` already mutates the repo; delegating a write-capable command's decision-making to an external model raises the blast radius with no safeguard proposed in this pass. Candidate list is now 4 distinct commands, not 5 (`code:debug`, `code:refactor`, `code:test-gen`, `arch:review`, `ci:triage` — `ci:fix` dropped). | +| 4 | cost-comparison honesty | The "650× cheaper" figure compares codex's raw token count against opencode's metered dollar cost — not like-for-like, since codex ran on a flat Plus subscription (sunk cost) while `opencode-go` bills per call. Token count is still a valid signal (context-window pressure, rate limits are real under a subscription too), but the SPEC/BRAINSTORM must state plainly that "650× cheaper" is a token-count fact, not necessarily a cash-cost fact, so a future reader doesn't misapply it as a dollar comparison. | +| 5 | default policy | `--delegate` defaults **OFF** on all 4 candidate commands — matches `--refine`'s existing OFF-default precedent for "execution engine" commands (`orch`/`orch:workflow`). Delegation costs real money/tokens and hits an external network dependency, unlike `--refine` (free, local, no network) — it should never fire silently. This also makes Branch 2's monitoring requirement easier to guarantee: an explicit, deliberate flag is easier to gate a test on than a heuristic auto-trigger. | + +## Open Questions (not locked, hand to `/craft:plan`) + +- Exact wording/placement of the corrected "why" column text for Branch 1 (mechanical doc fix, + not a design decision — small enough to do inline when Task 1 is picked up). +- Whether the dogfood-test requirement from Branch 2 should live in a shared test helper (one + test parametrized over all 4 commands) or 4 separate per-command tests — implementation + detail, not blocking. +- Whether `code:test-gen`'s and `arch:review`'s delegation targets should also default to a + specific `providerId`/`modelId` (e.g. always `deepseek-v4-pro` for review-shaped delegation, + matching tonight's dogfood run) or require the user to specify one each time — not raised + during this grill, worth a quick decision at implementation time. + +## Documentation Plan + +- `docs/specs/SPEC-codex-opencode-delegation-2026-08-08.md` — apply Branches 1, 3, 4, 5 (reword + candidate table, drop `ci:fix` row, add cost-honesty caveat, add default-OFF decision + new + acceptance criterion for the dogfood-test requirement). +- `docs/specs/BRAINSTORM-codex-opencode-delegation-2026-08-08.md` — Branch 4's cost-honesty + caveat applies to its "Cost delta is ~650×" line too (same claim, same fix). +- No `commands/*.md` changes yet — `--delegate` itself is still unbuilt (SPEC D2/out-of-scope); + this ledger only corrects and locks the design it will follow when built. diff --git a/docs/specs/SPEC-codex-opencode-delegation-2026-08-08.md b/docs/specs/SPEC-codex-opencode-delegation-2026-08-08.md new file mode 100644 index 000000000..0000643d7 --- /dev/null +++ b/docs/specs/SPEC-codex-opencode-delegation-2026-08-08.md @@ -0,0 +1,137 @@ +# Codex/OpenCode Delegation Routing — Spec + +**Generated:** 2026-08-08 · **Status:** draft — reviewed +**Sources:** [`BRAINSTORM-codex-opencode-delegation-2026-08-08.md`](BRAINSTORM-codex-opencode-delegation-2026-08-08.md) · +[`GRILL-codex-opencode-delegation-2026-08-08.md`](GRILL-codex-opencode-delegation-2026-08-08.md) (5 branches locked) + +--- + +## TL;DR + +3 delegation tools installed (`codex`, `opencode`, `opencode-async`), zero guidance on which +one to use, no way to watch cost or status while a delegated task runs. This spec adds both: +a short routing table + a mandatory "always monitor" step. + +## Problem + +3 delegation surfaces, no documented rule for which to use, no budget/failure guardrails. +Each tool's own docs cover *how* to call it — none cover *when*, or how to watch it while it +runs (tonight's session had to hand-build a token/status monitor from scratch each time). + +## Routing Table (the core deliverable) + +| Situation | Use | Why | +|---|---|---| +| Faster to just do it yourself | **Don't delegate** | Round-trip costs more than the task | +| Security / architecture / can't verify the result | **Don't delegate** | Not verifiable after the fact | +| Hard, well-scoped SWE task (deep bug, tricky refactor) | `codex` | Same shape of task the `codex` plugin's own `codex-rescue` agent proactively targets — craft calls `codex exec` directly, not that agent (different plugin, no cross-plugin dispatch) | +| One-shot question, need the answer this turn | `opencode` (`opencode_ask`/`opencode_run`) | Sync tier, blocks until done | +| Long task, don't want to block the session | `opencode-async` | Fire-and-forget, poll later | + +## Mandatory: Monitor Every Delegation (new — the ADHD-friendly part) + +Never fire-and-forget silently. Every delegation gets a live status+token watch, using the +`Monitor` pattern already proven tonight (codex + opencode reviews were both watched this way): + +1. **Fire the call in the background** — never block the session on a delegated task. +2. **Watch for 3 things only** — status change, token/cost usage, error/failure. Nothing else + is worth a notification (matches ADHD rule: filter to signal, not raw logs). +3. **One summary line per event** — what happened, in plain language, not raw JSON. +4. **Report cost + findings together at the end** — see tonight's side-by-side table + (codex: 1.01M tokens; opencode/deepseek-v4-pro: 1,555 tokens, ~650× cheaper **by token + count** on the same task) as the model for what "done" looks like. Caveat: that 650× + figure compares tokens, not dollars — codex ran on a flat subscription (sunk cost), + `opencode-go` bills per call. Token count is still a real signal (context pressure, rate + limits), just don't read it as "codex costs 650× more in cash." + +This closes the gap the Problem section names — "no way to watch cost while it runs" — as a +required step, not an afterthought. + +## Decisions (from BRAINSTORM answers + this review) + +| # | Decision | +|---|---| +| D1 | Only the 3 installed tools — no new tooling evaluation this pass | +| D2 | Must work standalone today AND be structured for a future skill to consume | +| D3 | Routing table above (corrected post-review — brainstorm had 2 factual bugs, both fixed) | +| D4 | Flexible timeline, quality over speed | +| D5 | **Monitoring is mandatory**, not optional — see section above (elevated from "budget practice" to a concrete required step this rewrite) | +| D6 | OpenCode's two bridges use **different** status words — don't conflate them: `opencode-async` = `working`/`input_required`/`completed`/`failed`/`cancelled`; `opencode` = `running`/`completed`/`error` | +| D7 | `--delegate` defaults **OFF** wherever it ships — matches `--refine`'s OFF-default precedent for execution-engine commands. Delegation costs real money/tokens and hits a network dependency; it must never fire silently. | +| D8 | Any future command implementing `--delegate` **must** ship a test asserting its dispatch path includes a status/token check — same enforcement pattern as `--refine`'s `test_refine_default_policy_table_exhaustive`. Doc-only guidance isn't enough (craft's own hooks-as-defense-in-depth precedent). | +| D9 | **Scope narrowed (2026-08-08, post-grill):** only 2 use cases move forward — adversarial review and brainstorm research. The 4-command `--delegate` rollout below is parked, not cancelled. | + +## Use Cases (narrowed scope — 2 in, 4 parked) + +### 1. Adversarial review — `arch:review` + +Already dogfooded tonight (both reviewers, on this exact spec's source docs). `arch:review` +is the one command that gets `--delegate` now: routes to `opencode` (sync tier, +`opencode_ask`/`opencode_run`) for a cheap second opinion, `codex` available as an explicit +override for a deeper pass. Same routing-table row and monitoring requirement (D5/D8) as +before — narrowing is about *which commands*, not the routing/monitoring design itself. + +### 2. Brainstorm research + +The `brainstorm` skill's existing "Research Findings (web)" step (used tonight via +`WebSearch`) gets an optional escalation: when a topic needs deeper investigation than a web +search provides, delegate to `opencode`/`codex` the same way, still monitored per D5. Not a +new command — an enhancement to `skills/workflow/brainstorm/SKILL.md`'s existing research +step. + +### Parked (not built, not cancelled) + +`code:debug`, `code:refactor`, `code:test-gen`, `ci:triage` — all 4 were scoped and grilled +(GRILL Branches 1 and 3 apply to them), but user chose to limit rollout to the 2 use cases +above for now. Revisit this list before building any of them; nothing here expires. + +**Explicitly excluded (not just parked):** `orch`, `orch:drive`, `orch:workflow`. These already +run their own native multi-agent delegation (wave checkpoints, model routing, +confirm-before-spawn). Adding external delegation here would recreate the exact anti-pattern +the `--refine`/brainstorm redesign already removed once — "one delegation mechanism, not two" +(see `skills/workflow/brainstorm/SKILL.md`'s "Going Deeper" section). + +## Scope + +**In:** + +- One guidance doc: routing table + monitor-every-delegation step + the two-vocabulary note. +- Placement: fold into the existing 2 tutorials (`TUTORIAL-opencode-mcp-plugin.md`, + `TUTORIAL-codex-plugin.md`), cross-linked — cheapest option, no new skill needed for a doc + this short. +- A copy-paste `Monitor` command template for each of the 3 tools (codex, opencode sync, + opencode-async), so "watch this delegation" isn't rebuilt from scratch again. + +**Out:** + +- The 4 parked commands (`code:debug`, `code:refactor`, `code:test-gen`, `ci:triage`) — scoped + and grilled, not built (D9). +- A working `arch:review --delegate` / brainstorm-escalation dispatch implementation — D2 only + needs the rule structured for future use; this pass is docs + design, not code. +- Evaluating tools beyond the 3 installed. +- Wiring `orchestrate-dispatch`'s existing hang/budget detection onto these external tools — + real gap, real follow-up, not this spec. + +## Acceptance Criteria + +- [ ] Routing table + monitor-every-delegation step live in both tutorials (or one shared + location both link to) +- [ ] A ready-to-copy `Monitor` template exists for all 3 tools +- [ ] Two-vocabulary note present and correct (guards against reintroducing finding F2) +- [ ] `arch:review`'s eventual `--delegate` flag defaults OFF (D7), enforced by a test once + built (D8) — the brainstorm-research escalation (use case 2) has no flag to default, + it's a skill-internal choice already gated by the skill's own interactive flow +- [ ] `markdownlint` + `mkdocs build --strict` clean + +## Test Plan + +| Tier | Status | +|---|---| +| e2e / dogfood | N/A — doc-only change, no new command/skill | +| everything else | N/A — no code, no parser, no data flow | + +## Next + +1. Implement into the 2 tutorials + Monitor templates. +2. Verify acceptance criteria. +3. `/craft:finish` — closes tonight's delegation-tooling thread. diff --git a/docs/tutorials/TUTORIAL-codex-plugin.md b/docs/tutorials/TUTORIAL-codex-plugin.md index 5afcf4e88..43eb126d1 100644 --- a/docs/tutorials/TUTORIAL-codex-plugin.md +++ b/docs/tutorials/TUTORIAL-codex-plugin.md @@ -18,6 +18,32 @@ claude plugin install codex@openai-codex No further config needed — the plugin starts a broker process per working directory the first time it's used in that directory. +## When to Use This vs OpenCode + +See the routing table in +[`TUTORIAL-opencode-mcp-plugin.md`](TUTORIAL-opencode-mcp-plugin.md#when-to-use-which-routing-table) +and [`SPEC-codex-opencode-delegation-2026-08-08.md`](../specs/SPEC-codex-opencode-delegation-2026-08-08.md) +for the full rationale — short version: Codex for hard, well-scoped SWE tasks (deep bugs, +tricky refactors); OpenCode for one-shot questions or long background tasks. + +## Always Monitor a Delegation (mandatory, not optional) + +Never fire `codex exec`/`codex review` and silently wait. `--json` emits JSONL events — +filter for the terminal event + token usage only, not raw logs: + +```bash +codex exec review --json --uncommitted 2>&1 | while IFS= read -r line; do + case "$line" in + *'"type":"turn.completed"'*|*'"type":"turn.failed"'*|*'"type":"error"'*|*token*|*usage* ) + echo "[codex] $(echo "$line" | cut -c1-400)" ;; + esac +done +``` + +If the inline `usage` numbers read all-zeros, the real total is in a *child* rollout file +under `~/.codex/sessions/**/rollout-*.jsonl` — find the one whose `parent_thread_id` matches +the run's own `thread_id` and grep for `"type":"token_count"`. + ## Gotchas 1. **One broker per cwd, not global.** Each project directory that uses diff --git a/docs/tutorials/TUTORIAL-opencode-mcp-plugin.md b/docs/tutorials/TUTORIAL-opencode-mcp-plugin.md new file mode 100644 index 000000000..b359c2442 --- /dev/null +++ b/docs/tutorials/TUTORIAL-opencode-mcp-plugin.md @@ -0,0 +1,127 @@ +# OpenCode MCP Bridge Cheat Sheet + +Two separate MCP servers let Claude Code delegate work to OpenCode. Ask +Claude to "hand this to OpenCode" or "delegate this to opencode" once +either is installed. + +## The two bridges + +| Name | Package | Style | +|------|---------|-------| +| `opencode` | `opencode-mcp` (npm) | Synchronous-first — ~80 tools, tiered (`opencode_setup` → `opencode_ask`/`opencode_run` → fine-grained session/message control) | +| `opencode-async` | local build, `~/tools/better-opencode-mcp` | Fire-and-forget — 7 tools (`opencode`, `opencode_sessions`, `opencode_respond`, `opencode_cancel`, `opencode_health`, `ping`, `Help`), built for long-running background tasks | + +They are not duplicates: `opencode` covers everything from a one-shot +question to full session management; `opencode-async` is a thin +task-delegation layer (submit → poll `opencode_sessions` → `opencode_respond` +if it asks for input). + +## Setup + +```bash +# opencode — published npm package +claude mcp add opencode -s user -- npx -y opencode-mcp + +# opencode-async — local build +claude mcp add opencode-async -s user -- node ~/tools/better-opencode-mcp/dist/index.js +``` + +`-s user` registers the server globally (every project, every session). +Drop it (or use `-s project`) to scope a bridge to one repo instead — see +`claude mcp add --help` for the full scope table. + +## When to Use Which (routing table) + +See [`SPEC-codex-opencode-delegation-2026-08-08.md`](../specs/SPEC-codex-opencode-delegation-2026-08-08.md) +for the full rationale. Quick version: + +| Situation | Use | Why | +|---|---|---| +| Faster to just do it yourself | Don't delegate | Round-trip costs more than the task | +| Security / architecture / can't verify the result | Don't delegate | Not verifiable after the fact | +| Hard, well-scoped SWE task | [`codex`](TUTORIAL-codex-plugin.md) | Matches the existing `codex-rescue` trigger | +| One-shot question, need the answer this turn | `opencode` (`opencode_ask`/`opencode_run`) | Sync tier, blocks until done | +| Long task, don't want to block the session | `opencode-async` | Fire-and-forget, poll later | + +**Status vocabularies are different between the two bridges — don't conflate them:** +`opencode-async` reports `working` / `input_required` / `completed` / `failed` / `cancelled`; +`opencode`'s `opencode_check` reports `running` / `completed` / `error`. + +## Always Monitor a Delegation (mandatory, not optional) + +Never fire a delegated task and silently wait. Use a background `Monitor` watching for status +changes, token/cost usage, and errors only — three signals, not raw logs. Copy-paste starting +points: + +```bash +# opencode (sync tier) — wrap opencode_ask/opencode_run in a background call, +# then watch its stdout for the terminal "result" line + any error. +# (Called via the MCP tool directly in a live session — this is the CLI-probe +# equivalent for scripting/testing outside a session.) +``` + +```bash +# opencode-async — poll opencode_sessions for status until terminal: +# working -> completed | failed | cancelled (input_required needs opencode_respond) +``` + +```bash +# codex — codex exec (--json) already emits JSONL events; filter for the +# terminal event and any "token_count"/usage payloads, e.g.: +codex exec review --json --uncommitted 2>&1 | while IFS= read -r line; do + case "$line" in + *'"type":"turn.completed"'*|*'"type":"turn.failed"'*|*'"type":"error"'*|*token*|*usage* ) + echo "[codex] $(echo "$line" | cut -c1-400)" ;; + esac +done +``` + +Real token-usage numbers aren't always in the CLI's own event stream — `codex`'s per-turn +usage can read all-zeros while the real total lives in a *child* rollout session file under +`~/.codex/sessions/**/rollout-*.jsonl` (`"type":"token_count"`, look for +`parent_thread_id` matching the review's own `thread_id`). Check there if the inline events +look empty. + +## Gotchas + +1. **New tool registrations need a session restart.** `claude mcp add` + writes to `~/.claude.json` immediately, but a *running* Claude Code + session doesn't pick up the new MCP server until it restarts — the + tools won't appear via `ToolSearch` mid-session even though `claude mcp + list` already shows them connected. +2. **`opencode` auto-starts an OpenCode SDK server.** First call spawns + `OpenCode server ... on 127.0.0.1:4096` if one isn't already running — + expect a few seconds of latency on cold start. +3. **Always discover providers/models first.** `opencode`'s own + instructions warn against assuming a provider is available — call + `opencode_setup` then `opencode_provider_models` before passing + `providerID`/`modelID` to `opencode_ask`/`opencode_run`, or you may get + empty responses. +4. **`opencode-async` tasks don't block.** `opencode` (the tool, not the + server) returns a `taskId` immediately with status `"working"` — poll + `opencode_sessions` to see completion, and watch for status + `"input_required"`, which needs `opencode_respond` before the task can + finish. +5. **Scope check before assuming a bridge is available.** Both were + originally `local`-scoped to specific project directories (`~/`, + `savant-openai` for `opencode`; `cc-config`, `better-opencode-mcp` for + `opencode-async`) before being promoted to `user` scope — if either + stops showing up, check `claude mcp list` and the project's + `mcpServers` block in `~/.claude.json` before assuming it's broken. + +## Verifying it works + +Both servers were smoke-tested directly over stdio (JSON-RPC +`initialize` + `tools/list`, no live session needed): + +- `opencode` — responds with 80 tools + a tiered usage guide in the + initialize response's top-level `instructions` field (a sibling of + `serverInfo`, not nested inside it); auto-starts its SDK server on port + 4096. +- `opencode-async` — responds with 7 tools (`Help`, `ping`, + `opencode_health`, `opencode`, `opencode_sessions`, `opencode_respond`, + `opencode_cancel`). + +If you want to re-run the same check: spawn the server's command, write +newline-delimited JSON-RPC to stdin (not LSP-style `Content-Length` +framing — these two don't use it), and read the response from stdout. diff --git a/mkdocs.yml b/mkdocs.yml index bd46b2f64..ddcb4ef09 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -223,6 +223,7 @@ nav: - Claude HUD Plugin: tutorials/TUTORIAL-claude-hud-plugin.md - Dropbox Plugin: tutorials/TUTORIAL-dropbox-plugin.md - Agent Skills Plugin: tutorials/TUTORIAL-agent-skills-plugin.md + - OpenCode MCP Bridge: tutorials/TUTORIAL-opencode-mcp-plugin.md - Command Tutorials: - arch:analyze Tutorial: tutorials/TUTORIAL-arch-analyze.md - arch:plan Tutorial: tutorials/TUTORIAL-arch-plan.md From 9b53e387e07a5f598dd6716d8dcf0b7a3779e977 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Sat, 8 Aug 2026 23:31:03 -0600 Subject: [PATCH 12/24] docs: remove stale Teaching Mode feature (dead since v4 command-prune) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 📚 Teaching mkdocs nav section, README's Teaching Mode section, and the underlying commands/utils/{teach_config,teaching_validation, semester_progress}.py + utils/detect_teaching_mode.py all documented /craft:site:{progress,validate,publish} — deleted in the v4 command-prune (commands/site/ now has only deploy.md). A prior SPEC's "keep the utils, they're consumed by 4 files" rationale is dead: all 4 consuming files are gone. Confirmed via background audit this session; verdict was REMOVE. Removed: 7 docs/teaching-* pages, 4 dead utils, 10 dead test files (including tests testing modules that no longer import), 1 pytest marker, 1 test.md domain row. Fixed 6 resulting dead links across REFCARD.md, tutorials/index.md, getting-started/choose-path.md, guide/ claude-code-2.1-integration.md, guide/integration-testing.md (trimmed its whole "Category 3: Teaching Workflow" section + renumbered), and cookbook/common/deploy-course-website.md (kept — it's actually about folio's live site:build/check commands, only its teaching cross-links were dead). Verified: full suite 2462 passed / 20 failed (both failure classes pre-existing and unrelated — test_branch_guard_dogfood.py's documented cross-repo symlink baseline, and a pre-existing broken link in REVIEW-repo-triage-2026-08-07.md dated 2026-08-07, untouched tonight). markdownlint + mkdocs build --strict clean. Command/skill/agent counts unaffected (48/41/2) — teaching was never a real command/skill/agent. Co-Authored-By: Claude Sonnet 5 --- README.md | 35 - commands/test.md | 1 - commands/utils/semester_progress.py | 387 -------- commands/utils/teach_config.py | 476 ---------- commands/utils/teaching_validation.py | 379 -------- docs/REFCARD.md | 3 - docs/TEACHING-DOCS-INDEX.md | 320 ------- docs/cookbook/common/deploy-course-website.md | 9 +- docs/getting-started/choose-path.md | 3 - docs/guide/claude-code-2.1-integration.md | 1 - docs/guide/integration-testing.md | 86 +- docs/guide/teaching-workflow.md | 845 ------------------ docs/reference/REFCARD-DOCS-UPDATE.md | 1 - docs/reference/REFCARD-TEACHING.md | 247 ----- docs/teaching-config-schema.md | 523 ----------- docs/teaching-migration.md | 735 --------------- docs/teaching/index.md | 139 --- docs/tutorials/index.md | 1 - docs/tutorials/teaching-mode-setup.md | 567 ------------ examples/test_parser.py | 104 --- mkdocs.yml | 14 +- pyproject.toml | 1 - tests/MANUAL-TESTING-CHECKLIST.md | 362 -------- tests/demo_teaching_validation.py | 183 ---- tests/test_coverage_gaps.py | 213 ----- tests/test_integration_teaching_workflow.py | 353 -------- tests/test_semester_progress.py | 504 ----------- tests/test_site_publish.py | 463 ---------- tests/test_teach_config.py | 810 ----------------- tests/test_teaching_documentation.py | 288 ------ tests/test_teaching_integration.py | 332 ------- tests/test_teaching_mode.py | 354 -------- tests/test_teaching_validation.py | 666 -------------- utils/detect_teaching_mode.py | 167 ---- 34 files changed, 5 insertions(+), 9567 deletions(-) delete mode 100644 commands/utils/semester_progress.py delete mode 100644 commands/utils/teach_config.py delete mode 100644 commands/utils/teaching_validation.py delete mode 100644 docs/TEACHING-DOCS-INDEX.md delete mode 100644 docs/guide/teaching-workflow.md delete mode 100644 docs/reference/REFCARD-TEACHING.md delete mode 100644 docs/teaching-config-schema.md delete mode 100644 docs/teaching-migration.md delete mode 100644 docs/teaching/index.md delete mode 100644 docs/tutorials/teaching-mode-setup.md delete mode 100644 examples/test_parser.py delete mode 100644 tests/MANUAL-TESTING-CHECKLIST.md delete mode 100644 tests/demo_teaching_validation.py delete mode 100644 tests/test_integration_teaching_workflow.py delete mode 100644 tests/test_semester_progress.py delete mode 100644 tests/test_site_publish.py delete mode 100644 tests/test_teach_config.py delete mode 100644 tests/test_teaching_documentation.py delete mode 100644 tests/test_teaching_integration.py delete mode 100644 tests/test_teaching_mode.py delete mode 100644 tests/test_teaching_validation.py delete mode 100644 utils/detect_teaching_mode.py diff --git a/README.md b/README.md index 554596ee7..d07ed76f8 100644 --- a/README.md +++ b/README.md @@ -196,41 +196,6 @@ Commands support execution modes for different use cases: /craft:arch:analyze optimize # Performance analysis ``` -## Teaching Mode - -Specialized workflows for teaching projects with automated validation, semester tracking, and safe publishing. - -**Key Features:** - -- Auto-detection via `.flow/teach-config.yml` configuration -- Content validation (syllabus sections, schedule completeness, assignment files) -- Preview-before-publish workflow (draft → production branches) -- Semester progress tracking with automatic week calculation -- ADHD-friendly dashboards and output - -![Teaching Workflow Demo](docs/demos/teaching-workflow.gif) - -**Quick Start:** - -1. Create `.flow/teach-config.yml` with course info and semester dates -2. Run `/craft:site:validate` to check content completeness -3. Publish with confidence using `/craft:site:publish` - -**Example workflow:** - -```bash -# Check semester progress -/craft:site:progress - -# Validate content before publishing -/craft:site:validate - -# Publish draft → production (with preview) -/craft:site:publish -``` - -**[Full Tutorial](https://data-wise.github.io/craft/tutorials/teaching-mode-setup/)** • **[Config Schema](https://data-wise.github.io/craft/teaching-config-schema/)** - ## Commands (110 total) ### Workflow Commands (12) diff --git a/commands/test.md b/commands/test.md index 6f5eb9ea1..1ecbbe7d6 100644 --- a/commands/test.md +++ b/commands/test.md @@ -69,7 +69,6 @@ Filter tests by tier or domain using pytest markers defined in `pyproject.toml`: | `claude_md` | CLAUDE.md sync, audit, fix | | `branch_guard` | Branch protection hooks | | `orchestrator` | Orchestrator workflows | -| `teaching` | Teaching mode and config | | `commands` | Command parsing, discovery | | `structure` | Plugin structure validation | | `docs` | Documentation link checking | diff --git a/commands/utils/semester_progress.py b/commands/utils/semester_progress.py deleted file mode 100644 index 7c35ba75b..000000000 --- a/commands/utils/semester_progress.py +++ /dev/null @@ -1,387 +0,0 @@ -""" -Semester Progress Calculation Utilities - -Calculates current week, progress percentage, and milestone information -for teaching projects based on semester dates and break schedules. - -Author: Craft Plugin Teaching Workflow -Created: 2026-01-16 -""" - -from __future__ import annotations - -from datetime import datetime, timedelta -from typing import Any - - -def calculate_current_week( - config: dict[str, Any], - current_date: str | None = None -) -> dict[str, Any]: - """ - Calculate current week and semester progress. - - Args: - config: Teaching configuration dictionary with dates structure: - { - "dates": { - "start": "YYYY-MM-DD", - "end": "YYYY-MM-DD", - "breaks": [ - {"name": str, "start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}, - ... - ] - }, - "progress": { - "current_week": "auto" | int # Optional manual override - } - } - current_date: ISO date string (YYYY-MM-DD). If None, uses today. - - Returns: - Dictionary with progress information: - { - "current_week": int, # 1-indexed week number (0 if before start) - "total_weeks": int, # Total weeks in semester - "percent_complete": float, # 0-100 - "on_break": bool, # True if currently on break - "break_name": str | None, # Name of current break or None - "days_elapsed": int, # Days since start (excluding breaks) - "days_remaining": int, # Days until end (excluding future breaks) - "semester_start": str, # ISO date - "semester_end": str, # ISO date - "week_start": str, # ISO date (start of current week) - "week_end": str # ISO date (end of current week) - } - - Examples: - >>> config = { - ... "dates": { - ... "start": "2026-01-19", - ... "end": "2026-05-08", - ... "breaks": [ - ... {"name": "Spring Break", "start": "2026-03-16", "end": "2026-03-20"} - ... ] - ... } - ... } - >>> result = calculate_current_week(config, "2026-02-10") - >>> result["current_week"] - 4 - >>> result["on_break"] - False - """ - # Parse dates - dates = config.get("dates", {}) - semester_start = datetime.fromisoformat(dates["start"]).date() - semester_end = datetime.fromisoformat(dates["end"]).date() - breaks = dates.get("breaks", []) - - # Parse current date - if current_date is None: - current = datetime.now().date() - else: - current = datetime.fromisoformat(current_date).date() - - # Check for manual override - progress = config.get("progress", {}) - manual_week = progress.get("current_week") - if isinstance(manual_week, int): - # Manual override - calculate other fields but use manual week - total_weeks = _calculate_total_weeks(semester_start, semester_end, breaks) - on_break, break_name = is_on_break(current.isoformat(), breaks) - - # Calculate week boundaries for manual week - week_start, week_end = get_week_boundaries(manual_week, semester_start.isoformat(), breaks) - - # Calculate days - days_elapsed = _count_days_excluding_breaks(semester_start, current, breaks) - days_remaining = _count_days_excluding_breaks(current, semester_end, breaks) - - return { - "current_week": manual_week, - "total_weeks": total_weeks, - "percent_complete": min(100.0, (manual_week / total_weeks * 100) if total_weeks > 0 else 0), - "on_break": on_break, - "break_name": break_name, - "days_elapsed": max(0, days_elapsed), - "days_remaining": max(0, days_remaining), - "semester_start": semester_start.isoformat(), - "semester_end": semester_end.isoformat(), - "week_start": week_start, - "week_end": week_end - } - - # Before semester starts - if current < semester_start: - total_weeks = _calculate_total_weeks(semester_start, semester_end, breaks) - return { - "current_week": 0, - "total_weeks": total_weeks, - "percent_complete": 0.0, - "on_break": False, - "break_name": None, - "days_elapsed": 0, - "days_remaining": _count_days_excluding_breaks(semester_start, semester_end, breaks), - "semester_start": semester_start.isoformat(), - "semester_end": semester_end.isoformat(), - "week_start": semester_start.isoformat(), - "week_end": (semester_start + timedelta(days=6)).isoformat() - } - - # After semester ends - if current > semester_end: - total_weeks = _calculate_total_weeks(semester_start, semester_end, breaks) - final_week_start, final_week_end = get_week_boundaries(total_weeks, semester_start.isoformat(), breaks) - return { - "current_week": total_weeks, - "total_weeks": total_weeks, - "percent_complete": 100.0, - "on_break": False, - "break_name": None, - "days_elapsed": _count_days_excluding_breaks(semester_start, semester_end, breaks), - "days_remaining": 0, - "semester_start": semester_start.isoformat(), - "semester_end": semester_end.isoformat(), - "week_start": final_week_start, - "week_end": final_week_end - } - - # During semester - calculate current week - on_break, break_name = is_on_break(current.isoformat(), breaks) - - # Count days elapsed excluding breaks - days_elapsed = _count_days_excluding_breaks(semester_start, current, breaks) - - # Calculate week number (1-indexed) - current_week = (days_elapsed // 7) + 1 - - # Calculate total weeks - total_weeks = _calculate_total_weeks(semester_start, semester_end, breaks) - - # Cap at total weeks - current_week = min(current_week, total_weeks) - - # Calculate week boundaries - week_start, week_end = get_week_boundaries(current_week, semester_start.isoformat(), breaks) - - # Calculate remaining days - days_remaining = _count_days_excluding_breaks(current, semester_end, breaks) - - # Calculate percent complete - total_days = _count_days_excluding_breaks(semester_start, semester_end, breaks) - percent_complete = (days_elapsed / total_days * 100) if total_days > 0 else 0 - percent_complete = min(100.0, percent_complete) - - return { - "current_week": current_week, - "total_weeks": total_weeks, - "percent_complete": round(percent_complete, 2), - "on_break": on_break, - "break_name": break_name, - "days_elapsed": days_elapsed, - "days_remaining": days_remaining, - "semester_start": semester_start.isoformat(), - "semester_end": semester_end.isoformat(), - "week_start": week_start, - "week_end": week_end - } - - -def _calculate_total_weeks( - semester_start: datetime.date, - semester_end: datetime.date, - breaks: list[dict[str, str]] -) -> int: - """Calculate total weeks in semester excluding breaks.""" - total_days = _count_days_excluding_breaks(semester_start, semester_end, breaks) - # Round up to include partial weeks - return (total_days + 6) // 7 - - -def _count_days_excluding_breaks( - start_date: datetime.date, - end_date: datetime.date, - breaks: list[dict[str, str]] -) -> int: - """ - Count days between start and end, excluding break days. - - Args: - start_date: Start date (inclusive) - end_date: End date (exclusive) - breaks: List of break dictionaries with "start" and "end" keys - - Returns: - Number of non-break days - """ - if start_date >= end_date: - return 0 - - total_days = (end_date - start_date).days - - # Subtract break days that fall within the range - break_days = 0 - for brk in breaks: - break_start = datetime.fromisoformat(brk["start"]).date() - break_end = datetime.fromisoformat(brk["end"]).date() - - # Check if break overlaps with range - overlap_start = max(start_date, break_start) - overlap_end = min(end_date, break_end + timedelta(days=1)) # +1 because end is inclusive - - if overlap_start < overlap_end: - break_days += (overlap_end - overlap_start).days - - return max(0, total_days - break_days) - - -def count_break_days( - start_date: str, - end_date: str, - breaks: list[dict[str, str]] -) -> int: - """ - Count total break days between two dates. - - Args: - start_date: Start date in YYYY-MM-DD format - end_date: End date in YYYY-MM-DD format - breaks: List of break dictionaries with "start" and "end" keys - - Returns: - Number of break days in the range - - Examples: - >>> breaks = [{"name": "Spring Break", "start": "2026-03-16", "end": "2026-03-20"}] - >>> count_break_days("2026-01-01", "2026-12-31", breaks) - 5 - """ - start = datetime.fromisoformat(start_date).date() - end = datetime.fromisoformat(end_date).date() - - total_days = (end - start).days - days_excluding_breaks = _count_days_excluding_breaks(start, end, breaks) - - return total_days - days_excluding_breaks - - -def is_on_break( - current_date: str, - breaks: list[dict[str, str]] -) -> tuple[bool, str | None]: - """ - Check if a date falls during a break period. - - Args: - current_date: Date to check in YYYY-MM-DD format - breaks: List of break dictionaries with "name", "start", and "end" keys - - Returns: - Tuple of (is_on_break, break_name). break_name is None if not on break. - - Examples: - >>> breaks = [{"name": "Spring Break", "start": "2026-03-16", "end": "2026-03-20"}] - >>> is_on_break("2026-03-18", breaks) - (True, 'Spring Break') - >>> is_on_break("2026-02-10", breaks) - (False, None) - """ - current = datetime.fromisoformat(current_date).date() - - for brk in breaks: - break_start = datetime.fromisoformat(brk["start"]).date() - break_end = datetime.fromisoformat(brk["end"]).date() - - if break_start <= current <= break_end: - return (True, brk["name"]) - - return (False, None) - - -def get_week_boundaries( - week_num: int, - start_date: str, - breaks: list[dict[str, str]] -) -> tuple[str, str]: - """ - Get start and end dates for a given week number. - - Week 1 starts on semester start date. Each week is 7 days long. - Break weeks are skipped - days during breaks don't count toward week progression. - - Args: - week_num: Week number (1-indexed) - start_date: Semester start date in YYYY-MM-DD format - breaks: List of break dictionaries - - Returns: - Tuple of (week_start_date, week_end_date) in YYYY-MM-DD format - - Examples: - >>> get_week_boundaries(1, "2026-01-19", []) - ('2026-01-19', '2026-01-25') - >>> get_week_boundaries(2, "2026-01-19", []) - ('2026-01-26', '2026-02-01') - """ - semester_start = datetime.fromisoformat(start_date).date() - - if week_num <= 0: - week_num = 1 - - # Calculate how many days to advance (accounting for breaks) - days_to_advance = (week_num - 1) * 7 - - # Start from semester start and advance day by day, skipping breaks - current = semester_start - days_advanced = 0 - - while days_advanced < days_to_advance: - current += timedelta(days=1) - # Check if this day is in a break - on_break, _ = is_on_break(current.isoformat(), breaks) - if not on_break: - days_advanced += 1 - - week_start = current - - # Find week end (6 days after start, skipping breaks) - days_in_week = 0 - current = week_start - - while days_in_week < 6: - current += timedelta(days=1) - on_break, _ = is_on_break(current.isoformat(), breaks) - if not on_break: - days_in_week += 1 - - week_end = current - - return (week_start.isoformat(), week_end.isoformat()) - - -def format_date_range(start: str, end: str) -> str: - """ - Format date range as 'Jan 27 - Feb 2' (ADHD-friendly). - - Args: - start: Start date in YYYY-MM-DD format - end: End date in YYYY-MM-DD format - - Returns: - Formatted date range string - - Examples: - >>> format_date_range("2026-01-27", "2026-02-02") - 'Jan 27 - Feb 2' - >>> format_date_range("2026-03-16", "2026-03-20") - 'Mar 16-20' - """ - start_date = datetime.fromisoformat(start).date() - end_date = datetime.fromisoformat(end).date() - - # Same month - use compact format - if start_date.month == end_date.month: - return f"{start_date.strftime('%b')} {start_date.day}-{end_date.day}" - - # Different months - return f"{start_date.strftime('%b')} {start_date.day} - {end_date.strftime('%b')} {end_date.day}" diff --git a/commands/utils/teach_config.py b/commands/utils/teach_config.py deleted file mode 100644 index 19ec3ce4d..000000000 --- a/commands/utils/teach_config.py +++ /dev/null @@ -1,476 +0,0 @@ -#!/usr/bin/env python3 -""" -Teaching Configuration Parser - -Parses and validates .flow/teach-config.yml files for teaching projects. -Provides robust error handling and comprehensive validation of dates, breaks, -and required fields. - -Usage: - from commands.utils.teach_config import load_teach_config - - config = load_teach_config() - if config: - print(f"Course: {config['course']['number']}") - print(f"Current week: {config['progress']['current_week']}") -""" - -import os -import sys -from datetime import datetime, timedelta -from pathlib import Path -from typing import Dict, List, Optional, Any - -try: - import yaml -except ImportError: - print("Error: PyYAML not installed. Run: pip install pyyaml", file=sys.stderr) - sys.exit(1) - - -# Default values for optional fields -DEFAULTS = { - "deployment": { - "production_branch": "production", - "draft_branch": "draft", - }, - "progress": { - "current_week": "auto", - }, - "validation": { - "required_sections": ["grading", "policies", "objectives", "schedule"], - "strict_mode": True, - }, - "dates": { - "breaks": [], - }, -} - - -# Valid semester values -VALID_SEMESTERS = ["Spring", "Fall", "Winter", "Summer"] - - -def _normalize_config(raw_config: Dict[str, Any]) -> Dict[str, Any]: - """ - Normalize flow-cli schema to craft-native format. - - Non-destructive, idempotent, silent. Uses setdefault() merge pattern: - only fills gaps — never overwrites existing craft-native keys. - Original flow-cli keys stay in dict (other tools may read them). - - Pipeline: load YAML -> _normalize_config() -> apply_defaults() -> validate() - - Field mappings: - semester_info.start_date -> dates.start (if dates.start missing) - semester_info.end_date -> dates.end (if dates.end missing) - semester_info.breaks -> dates.breaks (if dates.breaks missing) - course.name -> course.number (if number missing) - course.full_name -> course.title (if title missing) - course.semester -> capitalize (spring -> Spring) - branches.production -> deployment.production_branch (if missing) - branches.draft -> deployment.draft_branch (if missing) - """ - # --- Course normalization --- - course = raw_config.setdefault("course", {}) - if "number" not in course and "name" in course: - course["number"] = course["name"] - if "title" not in course and "full_name" in course: - course["title"] = course["full_name"] - if "semester" in course and isinstance(course["semester"], str): - sem_word = course["semester"].split()[0] if " " in course["semester"] else course["semester"] - capitalized = sem_word.capitalize() - if capitalized in VALID_SEMESTERS: - course["semester"] = capitalized - - # --- Dates normalization (merge strategy: fill gaps only) --- - si = raw_config.get("semester_info", {}) - if si: - dates = raw_config.setdefault("dates", {}) - if "start" not in dates and "start_date" in si: - dates["start"] = si["start_date"] - if "end" not in dates and "end_date" in si: - dates["end"] = si["end_date"] - if "breaks" not in dates and "breaks" in si: - dates["breaks"] = si["breaks"] - - # --- Deployment normalization --- - branches = raw_config.get("branches", {}) - if branches: - deploy = raw_config.setdefault("deployment", {}) - if "production_branch" not in deploy and "production" in branches: - deploy["production_branch"] = branches["production"] - if "draft_branch" not in deploy and "draft" in branches: - deploy["draft_branch"] = branches["draft"] - - return raw_config - - -def get_config_path(cwd: str = ".") -> Optional[str]: - """ - Find the teaching config file. - - Priority: - 1. .flow/teach-config.yml - 2. teach-config.yml (root) - - Args: - cwd: Current working directory (default: ".") - - Returns: - Absolute path to config file, or None if not found - """ - cwd_path = Path(cwd).resolve() - - # Priority 1: .flow/teach-config.yml - flow_config = cwd_path / ".flow" / "teach-config.yml" - if flow_config.exists(): - return str(flow_config) - - # Priority 2: teach-config.yml (root) - root_config = cwd_path / "teach-config.yml" - if root_config.exists(): - return str(root_config) - - return None - - -def validate_date(date_str: str) -> bool: - """ - Validate date string is in YYYY-MM-DD format with zero padding. - - Args: - date_str: Date string to validate - - Returns: - True if valid, False otherwise - """ - if not isinstance(date_str, str): - return False - - # Check exact format (YYYY-MM-DD with zero padding) - if len(date_str) != 10: - return False - - parts = date_str.split("-") - if len(parts) != 3: - return False - - # Check each part has correct length - if len(parts[0]) != 4 or len(parts[1]) != 2 or len(parts[2]) != 2: - return False - - # Now validate it's a real date - try: - datetime.strptime(date_str, "%Y-%m-%d") - return True - except (ValueError, TypeError): - return False - - -def parse_date(date_str: str) -> Optional[datetime]: - """ - Parse date string to datetime object. - - Args: - date_str: Date string in YYYY-MM-DD format - - Returns: - datetime object or None if invalid - """ - try: - return datetime.strptime(date_str, "%Y-%m-%d") - except (ValueError, TypeError): - return None - - -def validate_breaks(breaks: List[Dict[str, str]], start: str, end: str) -> List[str]: - """ - Validate break periods. - - Checks: - - Each break has required fields (name, start, end) - - Break dates are valid YYYY-MM-DD format - - Break start < break end - - Breaks fall within semester dates - - Breaks don't overlap - - Args: - breaks: List of break dictionaries - start: Semester start date (YYYY-MM-DD) - end: Semester end date (YYYY-MM-DD) - - Returns: - List of error messages (empty if valid) - """ - errors = [] - - if not breaks: - return errors - - semester_start = parse_date(start) - semester_end = parse_date(end) - - if not semester_start or not semester_end: - errors.append("Cannot validate breaks: invalid semester dates") - return errors - - parsed_breaks = [] - - for i, break_period in enumerate(breaks, 1): - # Check required fields - if not isinstance(break_period, dict): - errors.append(f"Break {i}: must be a dictionary") - continue - - if "name" not in break_period: - errors.append(f"Break {i}: missing 'name' field") - if "start" not in break_period: - errors.append(f"Break {i}: missing 'start' field") - if "end" not in break_period: - errors.append(f"Break {i}: missing 'end' field") - - if len(errors) > 0 and f"Break {i}:" in errors[-1]: - continue # Skip further validation if missing required fields - - name = break_period["name"] - break_start_str = break_period["start"] - break_end_str = break_period["end"] - - # Validate date formats - if not validate_date(break_start_str): - errors.append(f"Break '{name}': invalid start date format (expected YYYY-MM-DD)") - continue - if not validate_date(break_end_str): - errors.append(f"Break '{name}': invalid end date format (expected YYYY-MM-DD)") - continue - - break_start = parse_date(break_start_str) - break_end = parse_date(break_end_str) - - # Validate break start <= end (single-day breaks like holidays are valid) - if break_start > break_end: - errors.append(f"Break '{name}': start date must be before or equal to end date") - continue - - # Validate break falls within semester - if break_start < semester_start: - errors.append(f"Break '{name}': starts before semester begins ({start})") - if break_end > semester_end: - errors.append(f"Break '{name}': ends after semester ends ({end})") - - parsed_breaks.append({ - "name": name, - "start": break_start, - "end": break_end, - }) - - # Check for overlapping breaks - parsed_breaks.sort(key=lambda x: x["start"]) - for i in range(len(parsed_breaks) - 1): - current = parsed_breaks[i] - next_break = parsed_breaks[i + 1] - - if current["end"] >= next_break["start"]: - errors.append( - f"Breaks '{current['name']}' and '{next_break['name']}' overlap" - ) - - return errors - - -def apply_defaults(config: Dict[str, Any]) -> Dict[str, Any]: - """ - Apply default values for optional fields. - - Args: - config: Parsed configuration dictionary - - Returns: - Configuration with defaults applied - """ - # Apply deployment defaults - if "deployment" not in config: - config["deployment"] = {} - for key, value in DEFAULTS["deployment"].items(): - if key not in config["deployment"]: - config["deployment"][key] = value - - # Apply progress defaults - if "progress" not in config: - config["progress"] = {} - for key, value in DEFAULTS["progress"].items(): - if key not in config["progress"]: - config["progress"][key] = value - - # Apply validation defaults - if "validation" not in config: - config["validation"] = {} - for key, value in DEFAULTS["validation"].items(): - if key not in config["validation"]: - config["validation"][key] = value - - # Apply dates defaults - if "dates" in config and "breaks" not in config["dates"]: - config["dates"]["breaks"] = DEFAULTS["dates"]["breaks"] - - return config - - -def validate_config(config: Dict[str, Any]) -> List[str]: - """ - Validate configuration structure and values. - - Args: - config: Parsed configuration dictionary - - Returns: - List of error messages (empty if valid) - """ - errors = [] - - # Check required top-level sections - required_sections = ["course", "dates"] - for section in required_sections: - if section not in config: - errors.append(f"Missing required section: '{section}'") - - if errors: - return errors # Can't continue without required sections - - # Validate course section - course = config.get("course", {}) - required_course_fields = ["number", "title", "semester", "year"] - for field in required_course_fields: - if field not in course: - errors.append(f"Missing required field: 'course.{field}'") - - # Validate semester value - if "semester" in course: - if course["semester"] not in VALID_SEMESTERS: - errors.append( - f"Invalid semester: '{course['semester']}' " - f"(must be one of: {', '.join(VALID_SEMESTERS)})" - ) - - # Validate year - if "year" in course: - year = course["year"] - if not isinstance(year, int): - errors.append(f"Invalid year: must be an integer (got {type(year).__name__})") - elif year < 2000 or year > 2100: - errors.append(f"Invalid year: {year} (must be between 2000 and 2100)") - - # Validate dates section - dates = config.get("dates", {}) - required_date_fields = ["start", "end"] - for field in required_date_fields: - if field not in dates: - errors.append(f"Missing required field: 'dates.{field}'") - - if "start" in dates and "end" in dates: - start = dates["start"] - end = dates["end"] - - # Validate date formats - if not validate_date(start): - errors.append(f"Invalid start date format: '{start}' (expected YYYY-MM-DD)") - if not validate_date(end): - errors.append(f"Invalid end date format: '{end}' (expected YYYY-MM-DD)") - - # Validate date order - if validate_date(start) and validate_date(end): - start_dt = parse_date(start) - end_dt = parse_date(end) - if start_dt >= end_dt: - errors.append(f"Semester end date must be after start date") - - # Validate breaks if present - if "breaks" in dates: - break_errors = validate_breaks(dates["breaks"], start, end) - errors.extend(break_errors) - - # Validate progress.current_week - if "progress" in config and "current_week" in config["progress"]: - current_week = config["progress"]["current_week"] - if current_week != "auto" and not isinstance(current_week, int): - errors.append( - f"Invalid current_week: must be 'auto' or integer " - f"(got {type(current_week).__name__})" - ) - if isinstance(current_week, int) and (current_week < 1 or current_week > 52): - errors.append(f"Invalid current_week: {current_week} (must be between 1 and 52)") - - # Validate validation.strict_mode - if "validation" in config and "strict_mode" in config["validation"]: - strict_mode = config["validation"]["strict_mode"] - if not isinstance(strict_mode, bool): - errors.append( - f"Invalid strict_mode: must be boolean " - f"(got {type(strict_mode).__name__})" - ) - - return errors - - -def load_teach_config(cwd: str = ".") -> Optional[Dict[str, Any]]: - """ - Load and validate teaching configuration. - - Finds config file, parses YAML, validates required fields and dates, - and applies defaults for optional fields. - - Args: - cwd: Current working directory (default: ".") - - Returns: - Configuration dictionary with defaults applied, or None if not found/invalid - - Raises: - ValueError: For critical validation failures (invalid dates, etc.) - """ - # Find config file - config_path = get_config_path(cwd) - if not config_path: - return None # No config file found (not an error) - - # Parse YAML - try: - with open(config_path, "r") as f: - config = yaml.safe_load(f) - except yaml.YAMLError as e: - print(f"Warning: Malformed YAML in {config_path}: {e}", file=sys.stderr) - return None - except Exception as e: - print(f"Warning: Failed to read {config_path}: {e}", file=sys.stderr) - return None - - if not isinstance(config, dict): - print(f"Warning: Config must be a YAML dictionary (got {type(config).__name__})", file=sys.stderr) - return None - - # Normalize flow-cli schema to craft-native format - config = _normalize_config(config) - - # Apply defaults - config = apply_defaults(config) - - # Validate configuration - errors = validate_config(config) - if errors: - error_msg = "\n".join(f" - {error}" for error in errors) - raise ValueError(f"Configuration validation failed:\n{error_msg}") - - return config - - -if __name__ == "__main__": - """CLI for testing config parser""" - import json - - config = load_teach_config() - if config: - print(json.dumps(config, indent=2, default=str)) - else: - print("No teaching config found") - sys.exit(1) diff --git a/commands/utils/teaching_validation.py b/commands/utils/teaching_validation.py deleted file mode 100644 index b4cbf7fb4..000000000 --- a/commands/utils/teaching_validation.py +++ /dev/null @@ -1,379 +0,0 @@ -"""Teaching content validation utilities. - -Validates course content for completeness and readiness to publish: -- Syllabus required sections -- Schedule completeness -- Assignment file existence - -Designed for ADHD-friendly, scannable output. -""" - -import os -import re -from dataclasses import dataclass, field -from pathlib import Path -from typing import Optional - - -@dataclass -class ValidationResult: - """Result of teaching content validation. - - Attributes: - valid: Overall validation status (no errors) - errors: Blocking issues that prevent publishing - warnings: Non-blocking issues to review - checks: Detailed check results (name → passed) - """ - valid: bool - errors: list[str] = field(default_factory=list) - warnings: list[str] = field(default_factory=list) - checks: dict[str, bool] = field(default_factory=dict) - - def can_publish(self) -> bool: - """Returns True if no errors (warnings are OK).""" - return len(self.errors) == 0 - - def format_report(self) -> str: - """Format ADHD-friendly validation report.""" - lines = [] - - # Header - status = "✅ READY TO PUBLISH" if self.can_publish() else "❌ BLOCKED" - lines.append(f"\n{'=' * 60}") - lines.append(f"TEACHING CONTENT VALIDATION: {status}") - lines.append('=' * 60) - - # Errors (critical) - if self.errors: - lines.append("\n🚫 ERRORS (must fix before publishing):") - for i, error in enumerate(self.errors, 1): - lines.append(f" {i}. {error}") - - # Warnings (recommended) - if self.warnings: - lines.append("\n⚠️ WARNINGS (recommended to fix):") - for i, warning in enumerate(self.warnings, 1): - lines.append(f" {i}. {warning}") - - # Detailed checks - if self.checks: - lines.append("\n📋 DETAILED CHECKS:") - for name, passed in self.checks.items(): - icon = "✓" if passed else "✗" - lines.append(f" [{icon}] {name}") - - # Summary - lines.append(f"\n{'=' * 60}") - total_checks = len(self.checks) - passed_checks = sum(1 for v in self.checks.values() if v) - lines.append(f"Summary: {passed_checks}/{total_checks} checks passed") - - if self.can_publish(): - lines.append("Status: Ready to publish ✅") - else: - lines.append(f"Status: {len(self.errors)} error(s) blocking publish ❌") - - lines.append('=' * 60 + '\n') - - return '\n'.join(lines) - - -def find_file(cwd: str, patterns: list[str]) -> Optional[Path]: - """Find first matching file from pattern list. - - Args: - cwd: Working directory - patterns: File path patterns to search for - - Returns: - Path to first found file, or None - """ - base = Path(cwd) - for pattern in patterns: - path = base / pattern - if path.exists() and path.is_file(): - return path - return None - - -def validate_syllabus(cwd: str) -> dict[str, bool]: - """Validate syllabus contains required sections. - - Checks for essential syllabus sections using flexible heuristics: - - Grading/assessment information - - Course policies - - Learning objectives/outcomes - - Schedule/calendar - - Args: - cwd: Course directory path - - Returns: - Dict mapping section name → found (bool) - """ - # Find syllabus file - syllabus_patterns = [ - 'syllabus/index.qmd', - 'syllabus.qmd', - 'syllabus/syllabus.qmd', - 'index.qmd', # Sometimes syllabus is the main page - ] - - syllabus_path = find_file(cwd, syllabus_patterns) - - # Default result: all sections missing - sections = { - 'grading': False, - 'policies': False, - 'objectives': False, - 'schedule': False, - } - - if not syllabus_path: - return sections - - # Read syllabus content - try: - content = syllabus_path.read_text(encoding='utf-8').lower() - except Exception: - return sections - - # Check for section patterns (case-insensitive) - # Use word boundaries and context to avoid false positives - # For policies: must be a header starting with "polic" or "course polic" - # Not just any mention of "policy" (like "grading policy") - patterns = { - 'grading': [r'\bgrading\b', r'\bgrade\b', r'\bassessment\b'], - 'policies': [r'^#{1,4}\s+(course\s+)?polic(y|ies)\b'], # Header starting with policy/policies - 'objectives': [r'\bobjective', r'\blearning outcome', r'\bgoal'], - 'schedule': [r'\bschedule\b', r'\bcalendar\b', r'\btimeline\b'], - } - - for section, section_patterns in patterns.items(): - for pattern in section_patterns: - # For policies, use MULTILINE to match line starts - flags = re.MULTILINE if section == 'policies' else 0 - if re.search(pattern, content, flags): - sections[section] = True - break - - return sections - - -def validate_schedule(cwd: str) -> dict: - """Validate schedule completeness. - - Checks that: - - Schedule file exists - - All weeks are defined - - Each week has meaningful content (>20 chars) - - Args: - cwd: Course directory path - - Returns: - Dict with keys: - - total_weeks: Number of weeks found - - complete_weeks: Number of weeks with content - - gaps: List of week numbers missing content - """ - result = { - 'total_weeks': 0, - 'complete_weeks': 0, - 'gaps': [], - } - - # Find schedule file - schedule_patterns = [ - 'schedule.qmd', - 'schedule/index.qmd', - 'calendar.qmd', - ] - - schedule_path = find_file(cwd, schedule_patterns) - - if not schedule_path: - return result - - try: - content = schedule_path.read_text(encoding='utf-8') - except Exception: - return result - - # Find all week sections (flexible patterns) - # Matches: "## Week 1", "### Week 1:", "Week 1 -", etc. - week_pattern = re.compile( - r'^#{1,4}\s*Week\s+(\d+)', - re.MULTILINE | re.IGNORECASE - ) - - matches = list(week_pattern.finditer(content)) - - if not matches: - return result - - result['total_weeks'] = len(matches) - - # Check content for each week - for i, match in enumerate(matches): - week_num = int(match.group(1)) - start_pos = match.end() - - # Find content until next week or end - if i + 1 < len(matches): - end_pos = matches[i + 1].start() - else: - end_pos = len(content) - - week_content = content[start_pos:end_pos].strip() - - # Week is complete if it has meaningful content (>20 chars) - if len(week_content) > 20: - result['complete_weeks'] += 1 - else: - result['gaps'].append(week_num) - - return result - - -def validate_assignments(cwd: str) -> dict: - """Validate assignment files exist. - - Finds assignment references in schedule and checks if files exist. - - Args: - cwd: Course directory path - - Returns: - Dict with keys: - - referenced: List of assignment names found in schedule - - missing: List of assignment files that don't exist - - found: List of assignment files that exist - """ - result = { - 'referenced': [], - 'missing': [], - 'found': [], - } - - # Find schedule file - schedule_patterns = [ - 'schedule.qmd', - 'schedule/index.qmd', - 'calendar.qmd', - ] - - schedule_path = find_file(cwd, schedule_patterns) - - if not schedule_path: - return result - - try: - content = schedule_path.read_text(encoding='utf-8') - except Exception: - return result - - # Find assignment references - # Matches: "HW 1", "HW1", "Assignment 1", "Homework 1", etc. - assignment_patterns = [ - re.compile(r'\bHW\s*(\d+)\b', re.IGNORECASE), - re.compile(r'\bAssignment\s+(\d+)\b', re.IGNORECASE), - re.compile(r'\bHomework\s+(\d+)\b', re.IGNORECASE), - ] - - assignment_numbers = set() - for pattern in assignment_patterns: - for match in pattern.finditer(content): - assignment_numbers.add(int(match.group(1))) - - # Check if assignment files exist - base = Path(cwd) - assignments_dir = base / 'assignments' - - for num in sorted(assignment_numbers): - name = f"HW {num}" - result['referenced'].append(name) - - # Check multiple possible locations - possible_paths = [ - assignments_dir / f'hw-{num}.qmd', - assignments_dir / f'hw{num}.qmd', - assignments_dir / f'hw-{num}' / 'index.qmd', - assignments_dir / f'assignment-{num}.qmd', - assignments_dir / f'homework-{num}.qmd', - ] - - exists = any(p.exists() for p in possible_paths) - - if exists: - result['found'].append(name) - else: - result['missing'].append(name) - - return result - - -def validate_teaching_content(cwd: str) -> ValidationResult: - """Run all teaching content validations. - - Combines syllabus, schedule, and assignment validations into - a single comprehensive result. - - Args: - cwd: Course directory path - - Returns: - ValidationResult with errors, warnings, and detailed checks - """ - result = ValidationResult(valid=True) - - # Validate syllabus - syllabus_sections = validate_syllabus(cwd) - - required_sections = ['grading', 'policies', 'objectives', 'schedule'] - missing_sections = [s for s in required_sections if not syllabus_sections[s]] - - if missing_sections: - result.errors.append( - f"Syllabus missing required sections: {', '.join(missing_sections)}" - ) - result.valid = False - - for section, found in syllabus_sections.items(): - result.checks[f'Syllabus: {section}'] = found - - # Validate schedule - schedule_info = validate_schedule(cwd) - - if schedule_info['total_weeks'] == 0: - result.errors.append("No schedule file found or no weeks defined") - result.valid = False - result.checks['Schedule: exists'] = False - else: - result.checks['Schedule: exists'] = True - - if schedule_info['gaps']: - gap_list = ', '.join(str(w) for w in schedule_info['gaps']) - result.errors.append( - f"Schedule has incomplete weeks (no content): Week {gap_list}" - ) - result.valid = False - - result.checks[f"Schedule: {schedule_info['complete_weeks']}/{schedule_info['total_weeks']} weeks complete"] = len(schedule_info['gaps']) == 0 - - # Validate assignments - assignment_info = validate_assignments(cwd) - - if assignment_info['missing']: - missing_list = ', '.join(assignment_info['missing']) - result.warnings.append( - f"Missing assignment files: {missing_list}" - ) - - if assignment_info['referenced']: - result.checks[f"Assignments: {len(assignment_info['found'])}/{len(assignment_info['referenced'])} found"] = len(assignment_info['missing']) == 0 - else: - result.checks['Assignments: referenced in schedule'] = False - - return result diff --git a/docs/REFCARD.md b/docs/REFCARD.md index 7c7c98e80..b74d749f9 100644 --- a/docs/REFCARD.md +++ b/docs/REFCARD.md @@ -779,8 +779,6 @@ main (production branch) 3. Branch: `gh-pages` / `/ (root)` 4. Save -**See:** [Teaching Workflow Guide](guide/teaching-workflow.md) | [REFCARD-TEACHING.md](reference/REFCARD-TEACHING.md) - ## Check Commands (Pre-Flight Validation) **Main Command:** @@ -1499,7 +1497,6 @@ Comprehensive guides and references: | **[Claude Code 2.1](reference/REFCARD-claude-code-2.1-enhancements.md)** | Smart routing, orchestration hooks (341 lines) | | **[Docs Update](reference/REFCARD-DOCS-UPDATE.md)** | Documentation update workflows (330 lines) | | **[Interactive Commands](reference/REFCARD-INTERACTIVE-COMMANDS.md)** | "Show Steps First" pattern (261 lines) | -| **[Teaching](reference/REFCARD-TEACHING.md)** | Teaching mode workflows (247 lines) | | **[Branch Guard](reference/REFCARD-BRANCH-GUARD.md)** | Branch protection rules and bypass (231 lines) | | **[Release](reference/REFCARD-RELEASE.md)** | Release pipeline reference (211 lines) | | **[Testing](reference/REFCARD-TESTING.md)** | Unified test system, markers, templates (183 lines) | diff --git a/docs/TEACHING-DOCS-INDEX.md b/docs/TEACHING-DOCS-INDEX.md deleted file mode 100644 index 738006514..000000000 --- a/docs/TEACHING-DOCS-INDEX.md +++ /dev/null @@ -1,320 +0,0 @@ -# Teaching Workflow Documentation Index - -Complete documentation for Craft's teaching mode feature. - -![Teaching Workflow Demo](demos/teaching-workflow.gif) - -*Preview → Validate → Publish: Safe course site deployment in 3 minutes (was 15 minutes manually)* - -## Quick Start - -**New to teaching mode?** Start here: - -1. **[Teaching Mode Setup Tutorial](tutorials/teaching-mode-setup.md)** (25 min) - - First-time setup guide - - Step-by-step configuration - - Complete with examples and troubleshooting - -2. **[Teaching Config Schema](teaching-config-schema.md)** (5 min) - - YAML reference guide - - All configuration fields - - Validation rules - -3. **[Migration Guide](teaching-migration.md)** (15 min) - - Migrate from manual workflows - - Before/after patterns - - Team migration checklist - -## Documentation Structure - -### Tutorials (Step-by-Step Guides) - -| Document | Time | Level | Purpose | -|----------|------|-------|---------| -| [Teaching Mode Setup](tutorials/teaching-mode-setup.md) | 25 min | Beginner | First-time setup and configuration | - -**What you'll learn:** - -- Create `.flow/teach-config.yml` configuration -- Enable teaching mode auto-detection -- Run content validation -- Execute first publish workflow -- Track semester progress - -### Reference Guides - -| Document | Time | Level | Purpose | -|----------|------|-------|---------| -| [Teaching Config Schema](teaching-config-schema.md) | 5 min | Beginner | Complete YAML specification | - -**Complete reference for:** - -- Course information (number, title, semester, year) -- Semester dates (start, end, breaks) -- Instructor information (name, email, office hours) -- Deployment configuration (branches, GitHub Pages) -- Progress tracking (current week, auto-calculation) -- Validation rules (required sections, strict mode) - -### Migration Guides - -| Document | Time | Level | Purpose | -|----------|------|-------|---------| -| [Teaching Migration](teaching-migration.md) | 15 min | Intermediate | Transition from manual to automated workflows | - -**Covers:** - -- Before/after workflow comparison -- Step-by-step migration process -- Common migration patterns -- Troubleshooting migration issues -- Team migration checklist - -### Command Reference - -> All 4 below moved to the `folio` plugin as part of the v4.0.0 docs/publishing split — none -> remain in craft. - -| Command | Description | Link | -|---------|-------------|------| -| `/folio:site:publish` | Publish draft → production with preview | [Documentation](commands/site.md) | -| `/folio:site:progress` | Semester progress dashboard | [Documentation](commands/site.md) | -| `/folio:site:check` | Content/config validation (links, deployment readiness) | [Documentation](commands/site.md#foliositecheck) | -| `/folio:site:build` | Build site with teaching mode support | [Documentation](commands/site.md#foliositebuild) | - -## Documentation by Use Case - -### I'm Setting Up for the First Time - -**Start here:** - -1. Read [README Teaching Mode section](https://github.com/Data-Wise/craft#teaching-mode-new-in-v1180) (2 min) -2. Follow [Teaching Mode Setup Tutorial](tutorials/teaching-mode-setup.md) (25 min) -3. Bookmark [Config Schema](teaching-config-schema.md) for reference - -**Key commands:** - -```bash -# Setup -/folio:site:status # Verify detection - -# Validate -/folio:site:check # Check content - -# Publish -/folio:site:publish # Deploy to production -``` - -### I'm Migrating from Manual Workflows - -**Start here:** - -1. Review [Migration Guide](teaching-migration.md) (15 min) -2. Create config file following [Config Schema](teaching-config-schema.md) -3. Test workflows before retiring old scripts - -**Migration checklist:** - -- [ ] Create `.flow/teach-config.yml` -- [ ] Test detection with `/folio:site:status` -- [ ] Run validation and fix errors -- [ ] Test publish workflow on draft -- [ ] Archive old deployment scripts -- [ ] Update team documentation - -### I Need to Configure Something - -**Reference guides:** - -- [Config Schema](teaching-config-schema.md) - All YAML fields and options -- [Validation Rules](teaching-config-schema.md#validation-rules) - Required sections, strict mode -- [Deployment Config](teaching-config-schema.md#deployment-configuration) - Branches, GitHub Pages - -**Common configurations:** - -```yaml -# Basic configuration -course: - number: "STAT 440" - title: "Regression Analysis" - semester: "Spring" - year: 2026 - -dates: - start: "2026-01-19" - end: "2026-05-08" - -# Add breaks -dates: - breaks: - - name: "Spring Break" - start: "2026-03-16" - end: "2026-03-20" - -# Configure validation -validation: - required_sections: - - grading - - policies - strict_mode: true -``` - -### I'm Having a Problem - -**Troubleshooting resources:** - -1. **Teaching mode not detected** - - See: [Setup Tutorial - Step 2](tutorials/teaching-mode-setup.md#step-2-test-detection-2-min) - - Check file location and YAML syntax - -2. **Validation errors** - - See: [Setup Tutorial - Step 3](tutorials/teaching-mode-setup.md#step-3-validate-content-3-min) - - Fix missing sections or incomplete content - -3. **Publish failures** - - See: [Publish Command - Error Handling](commands/site.md) - - Review merge conflicts, network errors - -4. **Progress calculation wrong** - - See: [Progress Command - Troubleshooting](commands/site.md) - - Verify dates and breaks in config - -5. **Migration issues** - - See: [Migration Guide - Troubleshooting](teaching-migration.md#troubleshooting-migration-issues) - - Handle existing content, branch names, team setup - -### I Want to Understand How It Works - -**Architecture and implementation:** - -**Teaching mode detection:** - -- Checks for `.flow/teach-config.yml` in project root -- Validates YAML schema -- Activates specialized workflows - -**Validation system:** - -- Parses syllabus for required sections -- Checks schedule completeness (all weeks have content) -- Verifies assignment files exist -- Reports errors (block publish) vs warnings (recommend fix) - -**Publish workflow:** - -1. Pre-publish validation -2. Categorized diff preview (critical, content, other) -3. User confirmation (yes, preview diff, cancel) -4. Backup branch creation -5. Fast-forward merge with rollback -6. Deployment verification - -**Progress tracking:** - -- Auto-calculates current week from semester dates -- Accounts for break periods -- Manual override with `--week` flag -- JSON output for scripting - -## Cross-Reference Matrix - -| From | To | Relationship | -|------|----|----| -| README | Setup Tutorial | Quick start → Full guide | -| README | Config Schema | Quick start → Reference | -| Setup Tutorial | Config Schema | Configuration → Specification | -| Setup Tutorial | Publish Command | Workflow → Command details | -| Setup Tutorial | Progress Command | Workflow → Dashboard | -| Setup Tutorial | Migration Guide | First-time → Advanced | -| Migration Guide | Setup Tutorial | Prerequisites | -| Migration Guide | Config Schema | Configuration | -| Config Schema | Setup Tutorial | Examples → Usage | -| Publish Command | Config Schema | Configuration reference | -| Progress Command | Config Schema | Configuration reference | - -## Completeness Checklist - -### Documentation Files - -- [x] README section (Teaching Mode) -- [x] Setup tutorial (tutorials/teaching-mode-setup.md) -- [x] Config schema (teaching-config-schema.md) -- [x] Migration guide (teaching-migration.md) -- [x] Command updates (publish.md, progress.md, build.md) -- [x] Documentation index (this file) - -### Content Coverage - -- [x] Quick start (README) -- [x] First-time setup (tutorial) -- [x] Configuration reference (schema) -- [x] Migration from manual (migration guide) -- [x] Common workflows (tutorial + migration) -- [x] Troubleshooting (all documents) -- [x] Error messages (command docs) -- [x] Examples (all documents) - -### Style Consistency - -- [x] TL;DR sections -- [x] Time estimates -- [x] Difficulty levels -- [x] ADHD-friendly formatting -- [x] Code examples -- [x] Visual hierarchy -- [x] Scannable layout -- [x] Cross-references - -### Cross-References - -- [x] README → Tutorial -- [x] README → Schema -- [x] Tutorial → Schema -- [x] Tutorial → Commands -- [x] Tutorial → Migration -- [x] Migration → Tutorial -- [x] Migration → Schema -- [x] Commands → Tutorial -- [x] Commands → Schema -- [x] Commands → Migration - -## Future Enhancements - -**Not in v1.0, but planned:** - -- [ ] LMS integration (Canvas, Moodle) -- [ ] Assignment due date parsing from schedule -- [ ] Lecture completion tracking -- [ ] Student progress analytics -- [ ] Email digest automation -- [ ] Multi-instructor workflows -- [ ] Course template library -- [ ] Semester rollover automation - -## Feedback - -**Help improve these docs:** - -- [Open an issue](https://github.com/Data-Wise/craft/issues) with suggestions -- Share what worked well -- Report confusing sections -- Suggest additional examples - -## Related Documentation - -**General Craft documentation:** - -- [Craft README](https://github.com/Data-Wise/craft#readme) - Full feature list -- [Quick Start](QUICK-START.md) - General Craft usage -- [ADHD Guide](ADHD-QUICK-START.md) - Neurodivergent-friendly workflows -- [Command Reference](REFCARD.md) - All 48 commands - -**Site commands:** - -- [Site Deploy](commands/site.md#craftsitedeploy) - Direct GitHub Pages deployment -- [Site Check](commands/site.md#foliositecheck) - Health validation - -**Git workflows:** - -- [Git Worktree](guide/worktree-advanced-patterns.md) - Parallel development -- [Git Branch](commands/git.md) - Branch management diff --git a/docs/cookbook/common/deploy-course-website.md b/docs/cookbook/common/deploy-course-website.md index bf5705d59..3bb6e1dd0 100644 --- a/docs/cookbook/common/deploy-course-website.md +++ b/docs/cookbook/common/deploy-course-website.md @@ -6,15 +6,13 @@ level: "beginner" time_estimate: "3 minutes" related: - ../../commands/site.md - - ../../guide/teaching-workflow.md - - ../../TEACHING-DOCS-INDEX.md --- # Recipe: Deploy Course Website **Time:** 3 minutes **Level:** Beginner -**Prerequisites:** Teaching mode detected (course site with Quarto/MkDocs/pkgdown) +**Prerequisites:** Course/docs site (Quarto, MkDocs, or pkgdown) ## Problem @@ -51,9 +49,6 @@ I want to safely publish course website updates to GitHub Pages without breaking - Verify all pages load correctly - Test navigation and links -!!! tip "Flow-CLI Config Compatibility" - If your project uses flow-cli's `teach-config.yml` (with `semester_info`, `course.name`, `branches`), Craft reads it natively — no separate config needed. See [Config Schema](../../teaching-config-schema.md#flow-cli-config-compatibility) for details. - ## Explanation `/craft:site:deploy` implements a safe deployment workflow: @@ -129,5 +124,3 @@ I want to safely publish course website updates to GitHub Pages without breaking ## Related - [Site Commands](../../commands/site.md) — Deploy, build, check, and more -- [Teaching Workflow Guide](../../guide/teaching-workflow.md) — Complete teaching mode features -- [Teaching Docs Index](../../TEACHING-DOCS-INDEX.md) — All teaching documentation diff --git a/docs/getting-started/choose-path.md b/docs/getting-started/choose-path.md index 779e5857a..d2fd898a9 100644 --- a/docs/getting-started/choose-path.md +++ b/docs/getting-started/choose-path.md @@ -57,9 +57,6 @@ Welcome to Craft! Find the path that matches your experience level and goals. - **[Core Workflows](../guide/getting-started.md)** Best practices for documentation, site creation, releases, and development -- **[Teaching Mode](../TEACHING-DOCS-INDEX.md)** - Specialized workflow for managing course websites (80% time reduction!) - ### Common Tasks - **Check code quality:** `/craft:code:lint` or `/craft:ci:local` diff --git a/docs/guide/claude-code-2.1-integration.md b/docs/guide/claude-code-2.1-integration.md index a988e6628..b6327ace9 100644 --- a/docs/guide/claude-code-2.1-integration.md +++ b/docs/guide/claude-code-2.1-integration.md @@ -546,7 +546,6 @@ Result aggregation: < 1 minute ## See Also - [Complexity Scoring Algorithm](complexity-scoring-algorithm.md) - Deep dive into 7 factors -- [Teaching Workflow Guide](teaching-workflow.md) - Specialized workflow for courses - [Orchestrator Documentation](orchestrator.md) - Advanced multi-agent features - [Commands Reference](../commands.md) - All 48 commands diff --git a/docs/guide/integration-testing.md b/docs/guide/integration-testing.md index 173e1de6d..14e86d650 100644 --- a/docs/guide/integration-testing.md +++ b/docs/guide/integration-testing.md @@ -5,7 +5,7 @@ > **TL;DR** (30 seconds) > -> - **What:** Craft includes 44 integration tests across 4 categories (dependency system, orchestrator workflows, teaching workflow, branch guard) +> - **What:** Craft includes 28 integration tests across 3 categories (dependency system, orchestrator workflows, branch guard) > - **Why:** Ensure new features work end-to-end with all dependencies > - **How:** Run `python3 tests/test_integration_*.py` to test each category > - **Next:** Read about [Dependency Management](dependency-management-advanced.md) or [Claude Code 2.1.0 Integration](claude-code-2.1-guide.md) @@ -22,7 +22,6 @@ Craft has comprehensive integration tests that validate the entire system end-to # Run specific category python3 tests/test_integration_dependency_system.py python3 tests/test_integration_orchestrator_workflows.py - python3 tests/test_integration_teaching_workflow.py python3 tests/test_integration_branch_guard.py ``` @@ -133,61 +132,7 @@ test_13_agent_coordination ... ok Ran 13 tests in ~3-8 seconds ``` -### Category 3: Teaching Workflow (16 tests, 2 skipped) - -**File:** `tests/test_integration_teaching_workflow.py` - -**Purpose:** Validates the end-to-end teaching workflow including detection, config normalization, validation, and publish cycle. - -**What It Tests:** - -- ✅ Teaching mode detection (`.flow/teach-config.yml` presence) -- ✅ Configuration parsing and validation -- ✅ Flow-CLI config normalization (`semester_info` → `dates`, `course.name` → `course.number`) -- ✅ Mixed schema merge (craft-native keys win, gaps filled from flow-cli) -- ✅ Semester capitalization (`spring` → `Spring`) -- ✅ Single-day break validation (start == end accepted) -- ✅ Deployment branch normalization (`branches` → `deployment`) -- ✅ Config structure validation and edge cases -- ⏳ Teaching detection module (skipped - requires detect_teaching_mode.py) - -**Components Tested:** - -- `commands/utils/teach_config.py` - Config parsing + `_normalize_config()` -- `commands/utils/detect_teaching_mode.py` - Teaching detection -- `commands/utils/teaching_validation.py` - Content validation -- `commands/site/publish.md` - Publish workflow - -**Run It:** - -```bash -python3 tests/test_integration_teaching_workflow.py -``` - -**Expected Output:** - -``` -test_01_detection_with_config ... skipped (detection module) -test_02_detection_without_config ... skipped (detection module) -test_03_config_parsing ... ok -test_04_teaching_commands_exist ... ok -test_05_teaching_utilities_exist ... ok -test_06_config_structure_validation ... ok -test_branches_to_deployment ... ok -test_craft_native_unchanged ... ok -test_flowcli_schema_loads ... ok -test_full_name_to_title ... ok -test_mixed_schema_merge ... ok -test_name_to_number_mapping ... ok -test_semester_capitalization ... ok -test_single_day_break_valid ... ok -test_invalid_yaml_handling ... ok -test_missing_required_fields ... ok - -Ran 16 tests in ~0.07 seconds (14 passed, 2 skipped) -``` - -### Category 4: Branch Guard (6 tests) +### Category 3: Branch Guard (6 tests) **File:** `tests/test_integration_branch_guard.py` @@ -247,9 +192,6 @@ python3 tests/test_integration_dependency_system.py # Orchestrator tests only python3 tests/test_integration_orchestrator_workflows.py -# Teaching tests only -python3 tests/test_integration_teaching_workflow.py - # Branch guard tests only python3 tests/test_integration_branch_guard.py ``` @@ -323,18 +265,6 @@ The orchestration system enables smart task routing and agent delegation. Tests 9. **Parallelism** - Multiple agents run simultaneously 10. **Coordination** - Agents synchronize and aggregate results -### Teaching Tests Validate - -The teaching workflow system enables course management and publishing. Tests verify: - -1. **Detection** - Finding teaching projects automatically -2. **Config** - Parsing teaching configuration correctly -3. **Progress** - Calculating semester progress accurately -4. **Schedule** - Aligning course schedule with calendar -5. **Content** - Validating syllabus, assignments, schedule -6. **Safety** - Publish workflow prevents mistakes -7. **Deploy** - GitHub Pages integration works - ## Troubleshooting Integration Tests | Issue | Solution | @@ -342,7 +272,6 @@ The teaching workflow system enables course management and publishing. Tests ver | Tests fail with "module not found" | `python3 -m pip install -e .` in craft root | | Permission denied on scripts | `chmod +x scripts/*.sh` | | Tests timeout | Some dependency tests may take 5-10 seconds if tools need checking | -| Skipped tests | Expected for teaching workflow (3 modules not yet implemented) | | Dependency detection fails | Make sure tools being detected are in PATH or installed via homebrew/cargo | ## Key Files Reference @@ -366,13 +295,6 @@ The teaching workflow system enables course management and publishing. Tests ver | Validators | `.claude-plugin/skills/validation/` | Hot-reload validators | | Hooks | `.claude-plugin/hooks/orchestrate-hooks.sh` | Lifecycle hooks | | Orchestrator v2 | `agents/orchestrator-v2.md` | v2.3.0 agent | -| **Teaching** | | | -| Detection | `commands/utils/detect_teaching_mode.py` | Detects teaching projects | -| Config | `commands/utils/teach_config.py` | Parses configuration | -| Validation | `commands/utils/teaching_validation.py` | Validates content | -| Build | `commands/site/build.md` | Build command | -| Publish | `commands/site/publish.md` | Publish workflow | -| Progress | `commands/site/progress.md` | Progress tracking | | **Branch Guard** | | | | Hook script | `scripts/branch-guard.sh` | PreToolUse hook with jq parsing | | Config | `.claude/branch-guard.json` | Per-project branch protection config | @@ -388,15 +310,13 @@ The teaching workflow system enables course management and publishing. Tests ver 3. **Learn related features:** - [Dependency Management Advanced Guide](dependency-management-advanced.md) - Detailed workflow - [Claude Code 2.1.0 Integration Guide](claude-code-2.1-guide.md) - Complexity scoring details - - [Teaching Workflow Guide](teaching-workflow.md) - Course management details ## Summary -Craft's 44 integration tests validate four critical systems: +Craft's 28 integration tests validate three critical systems: - **Dependency System (9 tests)** - Tool detection, installation, and repair - **Orchestrator (13 tests)** - Smart routing, complexity scoring, agent delegation -- **Teaching (16 tests)** - Config normalization, detection, validation, and publishing - **Branch Guard (6 tests)** - Branch protection hooks, config loading, JSON parsing Run them regularly to ensure features continue working end-to-end. All tests pass with 100% success rate. diff --git a/docs/guide/teaching-workflow.md b/docs/guide/teaching-workflow.md deleted file mode 100644 index e8c740920..000000000 --- a/docs/guide/teaching-workflow.md +++ /dev/null @@ -1,845 +0,0 @@ -# Teaching Workflow System - -> **TL;DR**: Preview course changes before publishing, track semester progress, validate content automatically. - -The Teaching Workflow System provides specialized tools for managing course websites with safety, validation, and semester tracking. - -## Teaching Ecosystem - -The teaching workflow spans three tools. Each has clear ownership: - -| Capability | Tool | Command/Skill | -|---|---|---| -| Config & setup | flow-cli | `teach init`, `teach config`, `teach doctor` | -| Content generation | Scholar | `teach lecture`, `teach exam`, etc. (9 commands) | -| Content validation | All three | `teach validate` / `/scholar:validate` / `/folio:site:check` (moved to folio) | -| Deployment | flow-cli | `teach deploy` (history, rollback) | -| Semester tracking | flow-cli | `teach status`, `teach week` | -| Site management | folio (moved from Craft) | `/folio:site:publish`, `/folio:site:progress` | -| Shell speed | flow-cli | All `teach *` commands (<10ms dispatch) | - -### Which tool do I use? - -- **"Generate a lecture/exam/quiz"** — `teach lecture` (routes to Scholar) -- **"Deploy the course site"** — `teach deploy` (flow-cli handles rollback) -- **"Check if my content is ready"** — `teach check` (runs all validators) -- **"What week is it?"** — `teach status` or `tst` alias -- **"Publish with CI safety"** — `/folio:site:publish` (5-step workflow) -- **"See all available commands"** — `teach map` (ecosystem overview) - -### Config normalization - -Flow-cli is the canonical config owner. Its schema uses `semester_info.start_date`/`end_date` and `course.name`. Craft normalizes this silently to its internal format (`dates.start`/`end`, `course.number`) via `_normalize_config()` in `teach_config.py`. No warnings, no migration pressure — craft adapts to flow-cli, not the other way around. - -## Overview - -Teaching mode enables: - -- **Preview-before-publish workflow** - See changes before they go live -- **Branch-aware builds** - Automatically switch branches for preview vs publish -- **Content validation** - Check schedule, syllabus, assignments before deploy -- **Semester progress tracking** - Visual dashboard of course completion -- **Safe deployments** - Prevent publishing broken or incomplete content - -![Teaching Workflow Demo](../demos/teaching-workflow.gif) - -*Demo: Complete teaching workflow from status check to publish (3 minutes vs 15 minutes manually)* - -## Quick Start - -### 1. Enable Teaching Mode - -Create `.flow/teach-config.yml`: - -```yaml -teaching: - enabled: true - - branches: - preview: dev # Branch for testing changes - production: main # Branch for live site - - semester: - name: "Fall 2024" - start_date: "2024-08-26" - end_date: "2024-12-13" - - content: - schedule: "schedule.qmd" - syllabus: "syllabus.qmd" -``` - -### 2. Test Your Changes - -```bash -# Build preview (on dev branch) -/folio:site:build -``` - -### 3. Publish to Production - -```bash -# Preview → Validate → Switch to main → Build → Deploy -/folio:site:publish -``` - -## Core Commands - -| Command | Purpose | Teaching Mode Behavior | -|---------|---------|------------------------| -| `/folio:site:build` | Build site | Branch validation, teaching-aware output | -| `/folio:site:publish` | Publish to production | Preview → Validate → Switch → Deploy | -| `/folio:site:progress` | Semester dashboard | Week-by-week completion tracking | -| ask "git status" (dev/git skill) | Git status | Shows deployment context, branch safety | - -## How It Works - -### Teaching Mode Detection - -The system auto-detects teaching mode by checking for: - -1. `.flow/teach-config.yml` exists -2. `teaching.enabled: true` in config -3. Required files exist (schedule, syllabus) - -```python -from utils.detect_teaching_mode import detect_teaching_mode - -mode = detect_teaching_mode() -if mode.is_teaching: - print(f"Teaching: {mode.semester_name}") - print(f"Preview branch: {mode.branches.preview}") - print(f"Production branch: {mode.branches.production}") -``` - -### Preview-Before-Publish Workflow - -```mermaid -flowchart TD - A[Working on dev] -->|/folio:site:build| B[Preview Build] - B --> C{Looks good?} - C -->|No| A - C -->|Yes| D["/folio:site:publish"] - D --> E[Run Validation] - E --> F{Valid?} - F -->|No| G[Show Errors] - G --> A - F -->|Yes| H[Switch to main] - H --> I[Build Production] - I --> J[Deploy to GitHub Pages] - J --> K[Auto-switch back to dev] -``` - -**Key Safety Features:** - -1. **Always preview first** - See changes before publish -2. **Validation checkpoint** - Blocks bad content -3. **Auto-branch management** - Handles git switching -4. **Rollback safety** - Returns to dev after publish - -### Content Validation - -Before publishing, the system checks: - -#### Schedule Validation - -```yaml -# Required fields in schedule.qmd -- week number -- dates (start/end) -- topics -- assignments (if applicable) -``` - -#### Syllabus Validation - -```yaml -# Required sections -- Course description -- Instructor info -- Grading policy -- Schedule overview -``` - -#### Assignment Validation - -```yaml -# For each assignment -- Due date (within semester) -- Points/weight -- Instructions -- Submission method -``` - -**Example validation output:** - -``` -╭─ Content Validation ────────────────────────────────────╮ -│ │ -│ ✓ Schedule: 15 weeks validated │ -│ - Week 1-8: Complete │ -│ - Week 9: Missing assignment due date │ -│ - Week 10-15: Complete │ -│ │ -│ ✓ Syllabus: All required sections present │ -│ │ -│ ✗ Assignments: 1 issue found │ -│ - Assignment 3: Due date (2024-12-20) after semester │ -│ │ -│ ❌ VALIDATION FAILED - Fix issues before publishing │ -│ │ -╰─────────────────────────────────────────────────────────╯ -``` - -### Semester Progress Tracking - -```bash -/folio:site:progress -``` - -Shows: - -- Current week in semester -- Weeks completed vs remaining -- Content status per week -- Upcoming deadlines -- Overall completion percentage - -``` -╭─ Semester Progress: Fall 2024 ──────────────────────────╮ -│ │ -│ Week 8 of 15 (53% complete) │ -│ │ -│ ████████████░░░░░░░░░░░░░░░ │ -│ │ -│ Status by Week: │ -│ Weeks 1-7: ✅ Complete & Published │ -│ Week 8: 🔄 Current (Oct 14-20) │ -│ Weeks 9-15: 📝 Planned │ -│ │ -│ Upcoming: │ -│ - Oct 18: Assignment 2 due │ -│ - Oct 20: Midterm exam │ -│ - Oct 25: Week 9 content publish │ -│ │ -│ Content Health: │ -│ Schedule: ✅ 15/15 weeks defined │ -│ Syllabus: ✅ Complete │ -│ Assignments: ⚠️ 1 needs review │ -│ │ -╰─────────────────────────────────────────────────────────╯ -``` - -## Configuration - -### Full Config Schema - -See [`docs/teaching-config-schema.md`](../teaching-config-schema.md) for complete reference. - -### Common Configurations - -#### Minimal (Required) - -```yaml -teaching: - enabled: true - branches: - preview: dev - production: main - semester: - name: "Fall 2024" - start_date: "2024-08-26" - end_date: "2024-12-13" - content: - schedule: "schedule.qmd" - syllabus: "syllabus.qmd" -``` - -#### Standard (Recommended) - -```yaml -teaching: - enabled: true - - branches: - preview: dev - production: main - - semester: - name: "Fall 2024" - start_date: "2024-08-26" - end_date: "2024-12-13" - weeks: 15 - - content: - schedule: "schedule.qmd" - syllabus: "syllabus.qmd" - assignments_dir: "assignments/" - - validation: - strict: true - check_dates: true - check_links: true - - publishing: - auto_nav_update: true - auto_changelog: true -``` - -#### Advanced (Full Features) - -```yaml -teaching: - enabled: true - - branches: - preview: dev - production: main - - semester: - name: "Fall 2024" - start_date: "2024-08-26" - end_date: "2024-12-13" - weeks: 15 - - breaks: - - name: "Thanksgiving" - start: "2024-11-25" - end: "2024-11-29" - - content: - schedule: "schedule.qmd" - syllabus: "syllabus.qmd" - assignments_dir: "assignments/" - lectures_dir: "lectures/" - - validation: - strict: true - check_dates: true - check_links: true - require_all_weeks: true - - publishing: - auto_nav_update: true - auto_changelog: true - create_backup: true - - progress: - show_percentage: true - show_upcoming: 3 - highlight_current: true -``` - -## Common Workflows - -### Weekly Content Update - -```bash -# 1. Work on dev branch -git checkout dev - -# 2. Update week 8 content -# ... edit schedule.qmd, add lecture notes ... - -# 3. Preview changes -/folio:site:build - -# 4. Check if it looks good -# Review in browser at localhost:8000 - -# 5. Publish to production -/folio:site:publish -``` - -**What happens:** - -1. Validates schedule has Week 8 complete -2. Checks syllabus references Week 8 -3. Switches to `main` branch -4. Builds production site -5. Deploys to GitHub Pages -6. Switches back to `dev` - -### Mid-Semester Review - -```bash -# Check overall progress -/folio:site:progress - -# Validate all content -/folio:site:publish --dry-run --validate-only -``` - -### Semester Setup (First Time) - -```bash -# 1. Create config -cat > .flow/teach-config.yml << 'EOF' -teaching: - enabled: true - branches: - preview: dev - production: main - semester: - name: "Spring 2025" - start_date: "2025-01-13" - end_date: "2025-05-09" - weeks: 15 - content: - schedule: "schedule.qmd" - syllabus: "syllabus.qmd" -EOF - -# 2. Test detection -# ask "git status" — folded into the dev/git skill, 2026-07 v4 consolidation - -# 3. Build preview -/folio:site:build -``` - -## Teaching-Aware Commands - -### `/folio:site:build` - -**Standard mode:** - -```bash -/folio:site:build -# Builds MkDocs site -``` - -**Teaching mode:** - -```bash -/folio:site:build -# - Detects current branch (dev/main) -# - Shows teaching context -# - Validates if on production branch -# - Builds with semester metadata -``` - -**Output difference:** - -``` -╭─ Build Site (Teaching Mode) ────────────────────────────╮ -│ │ -│ Course: STAT 545 - Fall 2024 │ -│ Branch: dev (Preview) │ -│ Week: 8 of 15 │ -│ │ -│ Status: Safe to experiment ✅ │ -│ │ -│ Building preview site... │ -│ ✓ Built successfully │ -│ │ -│ Publish: /folio:site:publish │ -│ │ -╰─────────────────────────────────────────────────────────╯ -``` - -### Git status (dev/git skill) - -Folded into the `dev/git` skill (2026-07 v4 consolidation) — ask "git status" -naturally instead of `/craft:git:status`. In teaching mode it still shows -deployment context (production branch, what students see, what you're -editing), safety assessment, and modified files. See -[`skills/dev/git/SKILL.md`](https://github.com/Data-Wise/craft/blob/dev/skills/dev/git/SKILL.md). - -## Troubleshooting - -### Validation Failing - -**Problem:** `/folio:site:publish` fails validation - -**Solutions:** - -1. **Check what's wrong:** - - ```bash - /folio:site:publish --dry-run --validate-only - ``` - -2. **Common issues:** - - - **Missing week**: Add week to schedule.qmd - - **Date out of range**: Check assignment due dates - - **Broken links**: Run `/folio:docs:check-links` - - **Malformed YAML**: Validate teach-config.yml syntax - -3. **Skip validation (emergency only):** - - ```bash - /folio:site:publish --skip-validation - ``` - -### Branch Confusion - -**Problem:** Not sure which branch you're on - -**Solution:** - -```bash -# Teaching-aware status -# ask "git status" (dev/git skill) - -# Shows: -# - Current branch (dev/main) -# - What students see (main content) -# - What you're editing (current branch) -``` - -### Deployment Not Updating - -**Problem:** Published but site not updating - -**Checks:** - -1. **Verify production build:** - - ```bash - git checkout main - /folio:site:build - git push origin main - ``` - -2. **Check GitHub Pages:** - - Repo → Settings → Pages - - Verify source: `gh-pages` branch or `main` branch + `/docs` - -3. **Force rebuild:** - - ```bash - /folio:site:publish --force-rebuild - ``` - -### Week Numbers Off - -**Problem:** Progress shows wrong week - -**Fix:** - -Update semester dates in `teach-config.yml`: - -```yaml -semester: - start_date: "2024-08-26" # First day of Week 1 - weeks: 15 # Total weeks -``` - -## Configuration Reference - -### Complete teach-config.yml Schema - -For detailed configuration options, see [`docs/teaching-config-schema.md`](../teaching-config-schema.md). - -**Key sections:** - -| Section | Required | Purpose | -|---------|----------|---------| -| `teaching.enabled` | Yes | Enable/disable teaching mode | -| `teaching.branches` | Yes | Preview and production branch names | -| `teaching.semester` | Yes | Semester dates, name, weeks | -| `teaching.content` | Yes | Path to schedule and syllabus files | -| `teaching.validation` | No | Validation strictness options | -| `teaching.publishing` | No | Auto-update and backup options | -| `teaching.progress` | No | Dashboard display options | - -### Quick Config Validation - -```bash -# Validate teach-config.yml syntax -# ask "git status" (dev/git skill) - -# This command will: -# - Check if file exists -# - Validate YAML syntax -# - Verify required fields -# - Show any configuration errors -``` - -## Common Failure Scenarios - -### Scenario 1: Accidental Main Branch Modification - -**What happens:** - -- You edit files while on `main` branch -- Publish publishes immediately (no preview) -- Students see incomplete/broken content - -**Prevention:** - -```bash -# Always check your branch first -git branch --show-current -# Should show: dev - -# Use teaching-aware status -# ask "git status" (dev/git skill) -# Will show: ✅ Safe to experiment (if on preview branch) -``` - -**Recovery:** - -```bash -# 1. Revert changes on main -git checkout main -git reset --hard HEAD~1 - -# 2. Move changes to dev -git checkout dev -# Re-apply changes here - -# 3. Preview and publish properly -/folio:site:publish -``` - -### Scenario 2: Semester Dates Misalignment - -**What happens:** - -- Current week shows incorrectly in progress dashboard -- Assignment due dates appear out of order -- Validation rejects valid dates as "out of range" - -**Debug steps:** - -```bash -# Check config dates -cat .flow/teach-config.yml | grep -A 5 "semester:" - -# Check what the system thinks -/folio:site:progress - -# Calculate week number manually -# Today: Jan 17, 2026 -# Semester start: Jan 13, 2025 -# Week = floor((today - start) / 7) + 1 -``` - -**Fix:** - -```yaml -semester: - start_date: "2025-01-13" # Must be first day of Week 1 - end_date: "2025-05-09" # Must be last day of final week - weeks: 15 # Should match actual count -``` - -### Scenario 3: Assignment Due Date Validation Error - -**Error message:** - -``` -✗ Assignment due date (2025-12-20) is after semester end (2025-05-09) -``` - -**Common causes:** - -1. Copy-pasted due date with wrong year -2. Date format confusion (MM-DD vs DD-MM) -3. Semester dates don't match actual schedule - -**Fix:** - -```bash -# Check assignment file -grep -r "due.*date" assignments/ - -# Verify it's within semester -# semester.start_date → semester.end_date - -# Update assignment due date to valid range -``` - -### Scenario 4: Build Fails Silently - -**Symptom:** - -- `/folio:site:build` returns success -- But preview doesn't update -- Old content still visible in browser - -**Diagnosis:** - -```bash -# 1. Check build output -/folio:site:build --verbose - -# 2. Clear cache -rm -rf site/ -rm -rf .mkdocs_cache/ - -# 3. Rebuild -/folio:site:build - -# 4. Check browser cache -# Hard refresh: Cmd+Shift+R (Mac) or Ctrl+Shift+R (Linux) -``` - -### Scenario 5: Config File Not Found - -**Error:** - -``` -❌ Teaching mode enabled but .flow/teach-config.yml not found -``` - -**Solutions:** - -```bash -# Create the directory -mkdir -p .flow - -# Create minimal config -cat > .flow/teach-config.yml << 'EOF' -teaching: - enabled: true - branches: - preview: dev - production: main - semester: - name: "Spring 2025" - start_date: "2025-01-13" - end_date: "2025-05-09" - weeks: 15 - content: - schedule: "schedule.qmd" - syllabus: "syllabus.qmd" -EOF - -# Verify -# ask "git status" (dev/git skill) -``` - -## Debug Commands - -```bash -# View full config -cat .flow/teach-config.yml - -# Validate YAML syntax -python3 -m yaml .flow/teach-config.yml - -# Check detection -# ask "git status --verbose" (dev/git skill) - -# Dry-run publish (see what would happen) -/folio:site:publish --dry-run --validate-only - -# Build with verbose output -/folio:site:build --verbose - -# Check git branch (teaching-aware) -# ask "git status" (dev/git skill) -``` - -## Migration Guide - -### From Manual Git Workflow - -**Before:** - -```bash -# Manual process -git checkout main -mkdocs build -mkdocs gh-deploy -git checkout dev -``` - -**After:** - -```bash -# One command -/folio:site:publish -``` - -See [`docs/teaching-migration.md`](../teaching-migration.md) for complete migration guide. - -### From Other Systems - -If you're coming from: - -- **Jekyll**: Quarto is similar, adapt frontmatter -- **Hugo**: Content structure maps 1:1 -- **Wordpress**: Export to markdown first - -Teaching mode works with any static site generator that uses git branches. - -## Advanced Features - -### Custom Validation Rules - -```python -# utils/teaching_validation.py - -def validate_custom_content(config): - """Add your own validation logic""" - - # Example: Check lecture notes exist - for week in range(1, config.semester.weeks + 1): - lecture_file = f"lectures/week-{week:02d}.qmd" - if not Path(lecture_file).exists(): - yield ValidationError( - level="warning", - message=f"Lecture notes missing for Week {week}", - file=lecture_file - ) -``` - -### Semester Templates - -Create templates for rapid course setup: - -```yaml -# templates/teaching/semester-template.yml -teaching: - enabled: true - branches: { preview: dev, production: main } - semester: - weeks: 15 - # Dates filled in per semester - content: - schedule: "schedule.qmd" - syllabus: "syllabus.qmd" -``` - -### Automated Publishing - -Set up GitHub Actions to publish on schedule: - -```yaml -# .github/workflows/weekly-publish.yml -name: Weekly Publish -on: - schedule: - - cron: '0 8 * * 1' # Every Monday at 8am - workflow_dispatch: - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Publish week's content - run: /folio:site:publish --auto -``` - -## See Also - -- [Teaching Mode Setup Tutorial](../tutorials/teaching-mode-setup.md) - Step-by-step setup -- [Teaching Config Schema](../teaching-config-schema.md) - Complete config reference -- [Migration Guide](../teaching-migration.md) - Moving from manual workflows -- [Manual Testing Checklist](https://github.com/Data-Wise/craft/blob/main/tests/MANUAL-TESTING-CHECKLIST.md) - QA procedures - -## Impact - -**Measured improvements:** - -- ⏱️ **80% time reduction**: 15 min → 3 min per publish -- 🐛 **Zero production bugs**: Content validation catches issues -- 🎯 **100% confidence**: Preview-before-publish eliminates anxiety -- 📊 **Better planning**: Semester progress shows what's ahead - -Teaching mode transforms course management from error-prone and stressful to automated and reliable. diff --git a/docs/reference/REFCARD-DOCS-UPDATE.md b/docs/reference/REFCARD-DOCS-UPDATE.md index d8f1a1caf..30c1d06cc 100644 --- a/docs/reference/REFCARD-DOCS-UPDATE.md +++ b/docs/reference/REFCARD-DOCS-UPDATE.md @@ -312,7 +312,6 @@ python3 tests/test_docs_utilities.py - [Full Documentation](../commands/docs/update.md) - Complete command reference - [Example Walkthrough](../examples/docs-update-interactive-example.md) - Step-by-step example - [Implementation Summary](https://github.com/Data-Wise/craft/blob/main/docs/specs/_archive/SPEC-docs-update-interactive-2026-01-22.md) - Technical details -- [Teaching Workflow Guide](../guide/teaching-workflow.md) - Teaching mode integration ## Quick Tips diff --git a/docs/reference/REFCARD-TEACHING.md b/docs/reference/REFCARD-TEACHING.md deleted file mode 100644 index 6ca7da807..000000000 --- a/docs/reference/REFCARD-TEACHING.md +++ /dev/null @@ -1,247 +0,0 @@ -# Teaching Workflow - Quick Reference - -> **Preview → Validate → Publish** - Safe course site deployment - -![Teaching Workflow Demo](../demos/teaching-workflow.gif) - -## Essential Commands - -> Site/docs commands below moved to the `folio` plugin. - -```bash -/folio:site:build # Build preview (current branch) -/folio:site:publish # Validate → Switch to main → Deploy -/folio:site:progress # Semester progress dashboard -ask "git status" (dev/git skill) # Teaching-aware git status -``` - -## Quick Start - -```bash -# 1. Create config -cat > .flow/teach-config.yml << 'EOF' -teaching: - enabled: true - branches: { preview: dev, production: main } - semester: - name: "Fall 2024" - start_date: "2024-08-26" - end_date: "2024-12-13" - content: - schedule: "schedule.qmd" - syllabus: "syllabus.qmd" -EOF - -# 2. Test -ask "git status" (dev/git skill) # Should show teaching mode - -# 3. Preview -/folio:site:build - -# 4. Publish -/folio:site:publish -``` - -## Common Workflows - -### Weekly Content Update - -```bash -git checkout dev # Start on preview -# ... edit content ... -/folio:site:build # Preview changes -/folio:site:publish # Deploy to production -``` - -### Check Progress - -```bash -/folio:site:progress # Semester dashboard -``` - -### Emergency Fix - -```bash -git checkout main # Go to production -# ... fix issue ... -/folio:site:build # Build -git push origin main # Deploy -git checkout dev # Back to preview -``` - -## Validation - -### Check Before Publish - -```bash -/folio:site:publish --dry-run --validate-only -``` - -### Common Issues - -| Issue | Fix | -|-------|-----| -| Missing week | Add to schedule.qmd | -| Date out of range | Check assignment due dates | -| Broken links | `/folio:docs:check-links` | -| Bad YAML | Validate teach-config.yml | - -### Skip Validation (Emergency) - -```bash -/folio:site:publish --skip-validation -``` - -## Branch Strategy - -``` -main (production) ← Students see this - ↑ -dev (preview) ← You edit here -``` - -**Rules:** - -- Edit on `dev` -- Preview with `/folio:site:build` -- Publish with `/folio:site:publish` (auto-switches branches) -- Asking "git status" (dev/git skill) shows which branch you're on - -## Configuration - -### Minimal - -```yaml -teaching: - enabled: true - branches: { preview: dev, production: main } - semester: - name: "Fall 2024" - start_date: "2024-08-26" - end_date: "2024-12-13" - content: - schedule: "schedule.qmd" - syllabus: "syllabus.qmd" -``` - -### Standard - -```yaml -teaching: - enabled: true - branches: { preview: dev, production: main } - semester: - name: "Fall 2024" - start_date: "2024-08-26" - end_date: "2024-12-13" - weeks: 15 - content: - schedule: "schedule.qmd" - syllabus: "syllabus.qmd" - assignments_dir: "assignments/" - validation: - strict: true - publishing: - auto_nav_update: true -``` - -## Flow-CLI Config Compatibility - -If your project uses flow-cli's schema, Craft reads it natively — no migration needed: - -| Flow-CLI | Craft reads as | -|----------|---------------| -| `course.name` | `course.number` | -| `course.full_name` | `course.title` | -| `course.semester: "spring"` | `course.semester: "Spring"` | -| `semester_info.start_date` | `dates.start` | -| `semester_info.end_date` | `dates.end` | -| `branches.production` | `deployment.production_branch` | - -Single-day breaks (`start == end`) are supported for holidays like MLK Day. - -See [Config Schema](../teaching-config-schema.md#flow-cli-config-compatibility) for details. - -## Flags - -### `/folio:site:build` - -```bash -/folio:site:build # Normal build -/folio:site:build --force # Force rebuild -``` - -### `/folio:site:publish` - -```bash -/folio:site:publish # Full workflow -/folio:site:publish --dry-run # Preview what would happen -/folio:site:publish --validate-only # Just run validation -/folio:site:publish --skip-validation # Emergency publish -/folio:site:publish --force-rebuild # Force site rebuild -``` - -### `/folio:site:progress` - -```bash -/folio:site:progress # Full dashboard -/folio:site:progress --json # JSON output -``` - -## Troubleshooting - -### Not Detecting Teaching Mode - -```bash -# Check config exists -ls .flow/teach-config.yml - -# Verify enabled -grep "enabled: true" .flow/teach-config.yml - -# Test detection -ask "git status" (dev/git skill) # Should show teaching context -``` - -### Wrong Branch - -```bash -ask "git status" (dev/git skill) # Shows current branch + context -git checkout dev # Switch to preview -git checkout main # Switch to production -``` - -### Validation Errors - -```bash -# See what's wrong -/folio:site:publish --dry-run --validate-only - -# Fix issues, then try again -/folio:site:publish -``` - -### Site Not Updating - -```bash -# 1. Check GitHub Pages settings -# Repo → Settings → Pages - -# 2. Force rebuild -git checkout main -/folio:site:build --force -git push origin main -``` - -## Impact - -- ⏱️ **80% faster**: 15 min → 3 min per publish -- 🐛 **Zero bugs**: Validation catches issues -- 🎯 **100% confidence**: Preview before publish - -## See Also - -- [Full Guide](../guide/teaching-workflow.md) -- [Config Schema](../teaching-config-schema.md) -- [Migration Guide](../teaching-migration.md) -- [Setup Tutorial](../tutorials/teaching-mode-setup.md) diff --git a/docs/teaching-config-schema.md b/docs/teaching-config-schema.md deleted file mode 100644 index ddf40f3ce..000000000 --- a/docs/teaching-config-schema.md +++ /dev/null @@ -1,523 +0,0 @@ -# Teaching Configuration Schema - -⏱️ **5 minutes** • 🟢 Beginner • ✓ Complete reference - -> **TL;DR** (30 seconds) -> -> - **What:** YAML schema for teaching project configuration -> - **Why:** Enables automated course workflows, week tracking, and deployment -> - **Where:** `.flow/teach-config.yml` in your teaching project root -> - **Next:** Copy the [full example](#complete-example) and customize for your course - -Teaching projects use `.flow/teach-config.yml` to configure course information, dates, breaks, instructor details, and deployment settings. This enables Craft's teaching commands to provide context-aware automation. - -!!! tip "Quick Win: Auto-Week Calculation" - Set `progress.current_week: auto` and Craft automatically calculates which week you're in based on your semester dates and break schedule - no manual updates needed! - -!!! note "Flow-CLI Compatibility" - If your config uses flow-cli's schema (`semester_info.start_date`, `course.name`, `branches.production`), Craft normalizes it automatically — no changes needed. See [Flow-CLI Config Compatibility](#flow-cli-config-compatibility) below. - -## File Location - -``` -your-teaching-project/ -├── .flow/ -│ └── teach-config.yml # Teaching configuration (this file) -├── syllabus/ -├── lectures/ -└── assignments/ -``` - -## Schema Reference - -### Course Information - -Basic course metadata displayed across teaching workflows. - -| Field | Type | Required | Description | Example | -|-------|------|----------|-------------|---------| -| `course.number` | string | ✓ | Course code or number | `"STAT 545"`, `"CS 101"` | -| `course.title` | string | ✓ | Full course title | `"Regression Analysis"` | -| `course.semester` | string | ✓ | Semester name | `"Spring"`, `"Fall"`, `"Winter"`, `"Summer"` | -| `course.year` | integer | ✓ | Four-digit year | `2026` | - -**Example:** - -```yaml -course: - number: "STAT 545" - title: "Regression Analysis" - semester: "Spring" - year: 2026 -``` - -### Semester Dates - -Define when your semester starts, ends, and breaks occur. - -| Field | Type | Required | Description | Format | -|-------|------|----------|-------------|--------| -| `dates.start` | date | ✓ | First day of semester | `YYYY-MM-DD` | -| `dates.end` | date | ✓ | Last day of semester | `YYYY-MM-DD` | -| `dates.breaks` | array | - | Break periods (optional) | See below | - -**Break Configuration:** - -Each break in `dates.breaks` includes: - -| Field | Type | Required | Description | Example | -|-------|------|----------|-------------|---------| -| `name` | string | ✓ | Break name | `"Spring Break"`, `"MLK Day"` | -| `start` | date | ✓ | First day of break | `2026-03-16` | -| `end` | date | ✓ | Last day of break (can equal start for single-day breaks) | `2026-03-20` | - -Single-day breaks (where `start == end`) are valid for holidays like MLK Day or Veterans Day. - -**Example:** - -```yaml -dates: - start: "2026-01-19" - end: "2026-05-08" - breaks: - - name: "MLK Day" - start: "2026-01-20" - end: "2026-01-20" # Single-day break (start == end) - - name: "Spring Break" - start: "2026-03-16" - end: "2026-03-20" - - name: "Reading Week" - start: "2026-04-13" - end: "2026-04-14" -``` - -### Instructor Information - -Optional instructor contact details for syllabus generation and student communication. - -| Field | Type | Required | Description | Example | -|-------|------|----------|-------------|---------| -| `instructor.name` | string | - | Full name | `"Dr. Jane Smith"` | -| `instructor.email` | string | - | Contact email | `"jsmith@university.edu"` | -| `instructor.office_hours` | string | - | Office hours description | `"Tu/Th 2-3pm, Zoom"` | - -**Example:** - -```yaml -instructor: - name: "Dr. Jane Smith" - email: "jsmith@university.edu" - office_hours: "Tuesday/Thursday 2-3pm, Zoom link on Canvas" -``` - -### Deployment Configuration - -Controls how your course site is built and published. - -| Field | Type | Required | Description | Default | -|-------|------|----------|-------------|---------| -| `deployment.production_branch` | string | - | Branch for published site | `"production"` | -| `deployment.draft_branch` | string | - | Branch for draft content | `"draft"` | -| `deployment.gh_pages_url` | string | - | GitHub Pages URL | - | - -**Example:** - -```yaml -deployment: - production_branch: "production" - draft_branch: "draft" - gh_pages_url: "https://username.github.io/stat-545" -``` - -**How it works:** - -- **Draft branch**: Work-in-progress content, visible to instructors only -- **Production branch**: Published content, visible to students -- **GitHub Pages**: Automatically deploys from production branch - -### Progress Tracking - -Track which week of the semester you're currently in. - -| Field | Type | Required | Description | Options | -|-------|------|----------|-------------|---------| -| `progress.current_week` | string/integer | - | Current week number | `"auto"` or `1-16` | - -**Automatic calculation (`auto`)**: - -- Calculates week based on today's date -- Accounts for semester start/end dates -- Skips weeks with breaks -- Recommended for most courses - -**Manual override (integer)**: - -- Set specific week number (1-16) -- Useful for: - - Testing future weeks - - Non-standard schedules - - Review weeks - -**Example:** - -```yaml -progress: - current_week: auto # Let Craft calculate based on dates - -# Or manual override: -progress: - current_week: 8 # Force week 8 (for testing/review) -``` - -**How automatic calculation works:** - -1. Count weeks from `dates.start` to today -2. Skip any weeks that fall within `dates.breaks` -3. Cap at semester length (weeks between `start` and `end`) - -### Validation Rules - -Optional validation settings for quality control. - -| Field | Type | Required | Description | Default | -|-------|------|----------|-------------|---------| -| `validation.required_sections` | array | - | Syllabus sections that must exist | `["grading", "policies", "objectives", "schedule"]` | -| `validation.strict_mode` | boolean | - | Errors block publishing | `true` | - -**Strict Mode Behavior:** - -| Mode | Missing Sections | Broken Links | Stale Content | Action | -|------|------------------|--------------|---------------|--------| -| `true` | ❌ Block | ❌ Block | ⚠️ Warn | Prevent publish | -| `false` | ⚠️ Warn | ⚠️ Warn | ⚠️ Warn | Allow publish | - -**Example:** - -```yaml -validation: - required_sections: - - grading - - policies - - objectives - - schedule - - textbook - - accommodations - strict_mode: true -``` - -## Complete Example - -Full configuration for a typical Spring semester course: - -```yaml -# .flow/teach-config.yml - STAT 545 Spring 2026 - -# ============================================================================ -# Course Information -# ============================================================================ -course: - number: "STAT 545" - title: "Regression Analysis" - semester: "Spring" - year: 2026 - -# ============================================================================ -# Semester Dates -# ============================================================================ -dates: - # Semester runs January 19 - May 8 - start: "2026-01-19" - end: "2026-05-08" - - # Break periods (Spring Break + Reading Week) - breaks: - - name: "Spring Break" - start: "2026-03-16" - end: "2026-03-20" - - - name: "Reading Week" - start: "2026-04-13" - end: "2026-04-14" - -# ============================================================================ -# Instructor Information (optional) -# ============================================================================ -instructor: - name: "Dr. Jane Smith" - email: "jsmith@university.edu" - office_hours: "Tuesday/Thursday 2-3pm, Zoom link on Canvas" - -# ============================================================================ -# Deployment Configuration (optional) -# ============================================================================ -deployment: - production_branch: "production" # Students see this - draft_branch: "draft" # Instructors see this - gh_pages_url: "https://jsmith.github.io/stat-545" - -# ============================================================================ -# Progress Tracking (optional) -# ============================================================================ -progress: - # Options: - # - "auto": Calculate based on dates and breaks (recommended) - # - 1-16: Manual override for specific week number - current_week: auto - -# ============================================================================ -# Validation Rules (optional) -# ============================================================================ -validation: - # Sections that must exist in syllabus - required_sections: - - grading - - policies - - objectives - - schedule - - # Strict mode: true = errors block publishing, false = warnings only - strict_mode: true -``` - -## Usage Examples - -### Example 1: Spring Semester with Spring Break - -Typical 16-week Spring semester: - -```yaml -course: - number: "CS 240" - title: "Data Structures" - semester: "Spring" - year: 2026 - -dates: - start: "2026-01-12" - end: "2026-05-01" - breaks: - - name: "Spring Break" - start: "2026-03-09" - end: "2026-03-13" - -progress: - current_week: auto -``` - -**Timeline:** - -- Weeks 1-8: Jan 12 - Mar 6 -- Spring Break: Mar 9-13 (skipped) -- Weeks 9-16: Mar 16 - May 1 - -### Example 2: Fall Semester with Thanksgiving - -Fall semester with Thanksgiving break: - -```yaml -course: - number: "MATH 301" - title: "Probability Theory" - semester: "Fall" - year: 2025 - -dates: - start: "2025-08-25" - end: "2025-12-12" - breaks: - - name: "Thanksgiving Break" - start: "2025-11-24" - end: "2025-11-28" - -progress: - current_week: auto -``` - -### Example 3: Summer Session (No Breaks) - -Compressed 8-week summer session: - -```yaml -course: - number: "STAT 101" - title: "Intro to Statistics" - semester: "Summer" - year: 2026 - -dates: - start: "2026-06-01" - end: "2026-07-24" - # No breaks in summer session - -progress: - current_week: auto -``` - -### Example 4: Multi-Break Semester - -Complex schedule with multiple breaks: - -```yaml -course: - number: "ECON 405" - title: "Econometrics" - semester: "Fall" - year: 2025 - -dates: - start: "2025-09-02" - end: "2025-12-15" - breaks: - - name: "Fall Break" - start: "2025-10-12" - end: "2025-10-13" - - - name: "Thanksgiving" - start: "2025-11-25" - end: "2025-11-29" - - - name: "Reading Days" - start: "2025-12-08" - end: "2025-12-09" - -progress: - current_week: auto -``` - -## How Commands Use This Config - -Teaching commands read `.flow/teach-config.yml` to provide context-aware automation: - -| Command | Uses | Example | -|---------|------|---------| -| `/craft:teach:week` | `progress.current_week`, `dates` | Show current week info | -| `/craft:teach:status` | All fields | Teaching dashboard | -| `/craft:teach:publish` | `deployment`, `validation` | Publish to production | -| `/craft:teach:syllabus` | `course`, `instructor`, `dates` | Generate syllabus | -| `/craft:teach:schedule` | `dates`, `breaks` | Create semester calendar | - -## Validation - -### Validate Your Configuration - -Use `/craft:teach:config` to validate your configuration: - -```bash -/craft:teach:config validate -``` - -**Checks:** - -- ✓ Required fields present -- ✓ Date formats correct (YYYY-MM-DD) -- ✓ Dates in logical order (start < end) -- ✓ Breaks fall within semester dates -- ✓ Semester/year combination valid -- ✓ Week calculation works correctly - -**Example output:** - -``` -✓ Configuration valid -✓ Semester: Spring 2026 (16 weeks) -✓ Current week: 8 (auto-calculated) -✓ Breaks: 2 configured - - Spring Break (5 days) - - Reading Week (2 days) -``` - -### Common Validation Errors - -| Error | Fix | -|-------|-----| -| Missing required field | Add `course.number`, `course.title`, etc. (or use flow-cli equivalents: `course.name`, `course.full_name`) | -| Invalid date format | Use `YYYY-MM-DD` format | -| Break outside semester | Adjust break dates to fall within `start`/`end` | -| Break start after end | Swap break `start` and `end` dates (single-day breaks where `start == end` are valid) | -| End before start | Swap semester `start` and `end` dates | -| Invalid semester | Use `"Spring"`, `"Fall"`, `"Winter"`, or `"Summer"` (lowercase auto-capitalized) | -| Invalid current_week | Use `"auto"` or integer 1-16 | - -## Tips and Best Practices - -!!! success "Recommended Settings" - - Use `progress.current_week: auto` for automatic tracking - - Set `validation.strict_mode: true` to catch errors early - - Include all breaks (even 1-2 day breaks) for accurate week calculation - - Use descriptive break names (`"Spring Break"` not `"Break 1"`) - -!!! warning "Common Pitfalls" - - Forgetting to update year when copying from previous semester - - Break dates that don't account for weekends - - Missing `deployment.gh_pages_url` prevents publish command from working - - Setting `current_week` manually and forgetting to update it - -!!! tip "Pro Tips" - - **Test future weeks**: Temporarily set `current_week: 15` to preview end-of-semester content - - **Multiple instructors**: Use `instructor.name: "Dr. Smith & Dr. Jones"` for co-taught courses - - **Non-standard schedules**: Manual `current_week` override handles exam weeks, review sessions - - **Version control**: Commit `.flow/teach-config.yml` - it's your course's source of truth - -## Flow-CLI Config Compatibility - -If your project uses [flow-cli](https://github.com/Data-Wise/flow-cli)'s `teach-config.yml` schema, Craft normalizes it automatically via `_normalize_config()` in `teach_config.py`. No migration needed — both schemas work transparently. - -### Field Mappings - -| Flow-CLI Schema | Craft-Native Schema | Behavior | -|-----------------|---------------------|----------| -| `course.name` | `course.number` | Mapped if `number` absent | -| `course.full_name` | `course.title` | Mapped if `title` absent | -| `course.semester: "spring"` | `course.semester: "Spring"` | Auto-capitalized | -| `semester_info.start_date` | `dates.start` | Mapped if `dates.start` absent | -| `semester_info.end_date` | `dates.end` | Mapped if `dates.end` absent | -| `semester_info.breaks` | `dates.breaks` | Mapped if `dates.breaks` absent | -| `branches.production` | `deployment.production_branch` | Mapped if `production_branch` absent | -| `branches.draft` | `deployment.draft_branch` | Mapped if `draft_branch` absent | - -### Design Principles - -- **Non-destructive**: Craft-native keys always win (never overwritten) -- **Silent**: No warnings or migration prompts -- **Idempotent**: Safe to normalize multiple times -- **Gap-filling**: Only adds missing fields, original keys preserved - -### Example: Flow-CLI Config That Works in Craft - -```yaml -# This flow-cli config works in Craft without changes -course: - name: "STAT 545" # -> course.number - full_name: "STAT 545 - ANOVA" # -> course.title - semester: "spring" # -> "Spring" (capitalized) - year: 2026 - -semester_info: - start_date: "2026-01-19" # -> dates.start - end_date: "2026-05-16" # -> dates.end - breaks: - - name: "MLK Day" - start: "2026-01-20" - end: "2026-01-20" # Single-day break OK - - name: "Spring Break" - start: "2026-03-15" - end: "2026-03-22" - -branches: - production: "production" # -> deployment.production_branch - draft: "draft" # -> deployment.draft_branch -``` - -## Schema Changelog - -| Version | Date | Changes | -|---------|------|---------| -| 1.1 | 2026-02-06 | Flow-CLI config normalization, single-day breaks support | -| 1.0 | 2026-01-16 | Initial schema for teaching workflow | - -## See Also - -- **`/folio:site:publish`** (moved to folio) - Publish teaching site with preview workflow -- **`utils/detect_teaching_mode.py`** - Teaching mode detection utility -- **`commands/utils/teaching_validation.py`** - Content validation utility -- **[Configuration Reference](reference/configuration.md)** - General Craft config diff --git a/docs/teaching-migration.md b/docs/teaching-migration.md deleted file mode 100644 index 54d73eede..000000000 --- a/docs/teaching-migration.md +++ /dev/null @@ -1,735 +0,0 @@ -# Migrating to Craft Teaching Workflows - -⏱️ **15 minutes** • 🟡 Intermediate • ✓ From manual to automated workflows - -> **TL;DR** (30 seconds) -> -> - **What:** Migrate from manual teaching site deployment to Craft automation -> - **Why:** Eliminate errors, save time, gain confidence in publishing -> - **How:** Add config file → Test workflows → Retire old scripts -> - **Benefit:** 5-step safety workflow replaces fragile shell scripts - -This guide helps you transition from manual teaching site workflows to Craft's automated teaching mode. - -> `site:publish`, `site:status`, and `site:progress` below moved to the `folio` plugin; `site:validate` remains in craft. - -## Before and After - -### Old Manual Workflow - -```bash -# 1. Remember which branch is production -git branch --show-current # Am I on draft or production? - -# 2. Manually switch branches -git checkout production - -# 3. Hope the merge works -git merge draft # Fingers crossed! - -# 4. Fix conflicts (no preview, just hope) -# ... manual conflict resolution ... - -# 5. Push and pray -git push origin production - -# 6. Wait 5 minutes and manually check site -# Is it live? Did it break? Who knows! - -# 7. Realize you forgot to validate syllabus -# Too late - it's already published! -``` - -**Problems with manual workflow:** - -- No pre-publish validation (catch errors AFTER students see them) -- No preview of changes (surprises after deployment) -- Manual branch management (easy to mess up) -- No rollback on failure (broken site = panic) -- No verification (did it actually deploy?) -- Cognitive load (remember all steps, every time) - -### New Craft Workflow - -```bash -# One command does everything -/folio:site:publish -``` - -**What happens automatically:** - -1. ✓ Validates content (syllabus, schedule, assignments) -2. ✓ Shows preview with categorized changes -3. ✓ Asks for confirmation -4. ✓ Creates backup branch -5. ✓ Safe merge with rollback on failure -6. ✓ Verifies deployment -7. ✓ Returns to original branch - -**Benefits:** - -- Errors caught BEFORE publishing -- Preview changes before students see them -- Automatic branch management -- Automatic rollback on failure -- Deployment verification -- Zero cognitive load (just run the command) - -## Migration Steps - -### Step 1: Add Configuration File (5 min) - -!!! note "Already have a flow-cli config?" - If your project already has `.flow/teach-config.yml` from flow-cli (with `semester_info`, `course.name`, `branches`), **skip this step** — Craft normalizes it automatically. No separate config needed. - -Create `.flow/teach-config.yml` with your course details: - -```yaml -# .flow/teach-config.yml - -course: - number: "STAT 440" - title: "Regression Analysis" - semester: "Spring" - year: 2026 - -dates: - start: "2026-01-19" - end: "2026-05-08" - breaks: - - name: "Spring Break" - start: "2026-03-16" - end: "2026-03-20" - -deployment: - production_branch: "production" - draft_branch: "draft" - gh_pages_url: "https://yourname.github.io/stat-440" - -validation: - required_sections: - - grading - - policies - - objectives - - schedule - strict_mode: true - -progress: - current_week: auto -``` - -**Customize:** - -- Course number and title -- Semester dates (start, end, breaks) -- GitHub Pages URL -- Required syllabus sections - -**Test configuration:** - -```bash -/folio:site:status -``` - -**Expected output:** - -``` -✓ Teaching mode detected -✓ Configuration valid -``` - -### Step 2: Test Validation (3 min) - -Before using the publish workflow, test content validation: - -```bash -/craft:site:validate -``` - -**First run will likely show errors:** - -``` -🚫 ERRORS (must fix before publishing): - 1. Syllabus missing required sections: policies - 2. Schedule has incomplete weeks: Week 2, 4 -``` - -**Fix these errors:** - -- Add missing syllabus sections -- Complete schedule for all weeks -- Fix any broken assignment references - -**Validate again until clean:** - -``` -✅ ALL CHECKS PASSED -Status: Ready to publish ✅ -``` - -### Step 3: Test Progress Tracking (1 min) - -Verify semester progress calculation: - -```bash -/folio:site:progress -``` - -**Check output:** - -- Current week matches calendar -- Progress bar is accurate -- Break countdown is correct - -**If week is wrong:** - -- Verify `dates.start` in config -- Check break dates -- Use `--week` for manual override - -### Step 4: First Craft Publish (5 min) - -Run your first automated publish: - -```bash -# Make sure you're on draft branch -git checkout draft - -# Run publish workflow -/folio:site:publish -``` - -**What to expect:** - -1. Validation runs automatically -2. Preview shows categorized changes -3. Confirmation prompt (3 options) -4. Safe merge and push -5. Deployment verification - -**Review the preview carefully:** - -``` -CRITICAL CHANGES: -⚠️ syllabus/index.qmd +15 -3 - -CONTENT CHANGES: -✓ lectures/week-01.qmd +120 -0 -``` - -**Confirm publish:** - -- Choose "Yes - Merge and deploy (Recommended)" -- Monitor output for errors -- Verify live site after deployment - -### Step 5: Retire Old Scripts (1 min) - -Once Craft workflows are working, retire your old deployment scripts: - -```bash -# Archive old scripts -mkdir -p archive/ -mv deploy.sh archive/ -mv publish.sh archive/ -mv validate-syllabus.sh archive/ - -# Update team documentation -# Replace manual steps with /folio:site:publish -``` - -**Update `.gitignore` (optional):** - -``` -# Old deployment scripts (archived) -archive/ -``` - -## Common Migration Patterns - -### Pattern 1: Weekly Content Updates - -**Old workflow:** - -```bash -# Edit content (draft branch) -vim lectures/week-05.qmd - -# Commit changes -git add lectures/week-05.qmd -git commit -m "Add week 5 lecture" - -# Switch to production -git checkout production - -# Merge (hope it works) -git merge draft - -# Push (hope it deploys) -git push origin production - -# Wait and manually check -# Open browser, reload, verify -``` - -**New workflow:** - -```bash -# Edit content (draft branch) -vim lectures/week-05.qmd - -# Commit changes -git add lectures/week-05.qmd -git commit -m "Add week 5 lecture" - -# Publish with safety checks -/folio:site:publish -``` - -### Pattern 2: Assignment Releases - -**Old workflow:** - -```bash -# Add assignment file -vim assignments/hw-05.qmd - -# Update schedule manually -vim schedule.qmd -# Add due date, hope you didn't typo - -# Commit -git add assignments/hw-05.qmd schedule.qmd -git commit -m "Add HW 5" - -# Manual publish (cross fingers) -git checkout production -git merge draft -git push origin production - -# Realize later you forgot to link it -# Students can't find assignment -# Emergency fix required -``` - -**New workflow:** - -```bash -# Add assignment file -vim assignments/hw-05.qmd - -# Update schedule -vim schedule.qmd - -# Commit -git add assignments/hw-05.qmd schedule.qmd -git commit -m "Add HW 5" - -# Validate catches missing links -/craft:site:validate -# Shows: "Assignment HW 5 referenced but file missing link" - -# Fix link, then publish -/folio:site:publish -# Preview shows assignment in CRITICAL CHANGES -# Verify before students see it -``` - -### Pattern 3: Schedule Changes - -**Old workflow:** - -```bash -# Change due dates (high risk!) -vim schedule.qmd -# Move HW 3 from Week 5 to Week 6 - -# Commit and publish immediately -git add schedule.qmd -git commit -m "Move HW 3 due date" -git checkout production -git merge draft -git push origin production - -# Students confused (no notification) -# Canvas still shows old date -# Email apology required -``` - -**New workflow:** - -```bash -# Change due dates -vim schedule.qmd - -# Commit -git add schedule.qmd -git commit -m "Move HW 3 due date" - -# Preview shows CRITICAL CHANGE -/folio:site:publish -# Highlights: schedule.qmd in CRITICAL section -# Review diff before confirming - -# After publish, update Canvas manually -# Send announcement to students -``` - -### Pattern 4: Semester Progress Check - -**Old workflow:** - -```bash -# Mentally calculate: "What week is it?" -# Check calendar, count weeks manually -# Account for spring break... or did I? -# Confusion about whether we're on Week 7 or 8 - -# Update syllabus manually -vim syllabus.qmd -# Change "Current week: 7" to "Current week: 8" -# Commit, publish, hope it's right -``` - -**New workflow:** - -```bash -# One command shows everything -/folio:site:progress - -# Output: -# Week 8 of 16 (50% complete) -# Next break: Spring Break in 12 days - -# No manual updates needed -# Auto-calculated, always accurate -``` - -## What to Do with Old Scripts - -### Review and Archive - -Before deleting old scripts, review them for: - -1. **Environment-specific configuration** - - Server URLs - - API keys - - Paths - -2. **Custom validation logic** - - Unique checks for your course - - Can be added to Craft config - -3. **Deployment hooks** - - Pre/post-deploy actions - - May need custom integration - -**Archive, don't delete:** - -```bash -mkdir -p docs/archive/old-workflows/ -mv *.sh docs/archive/old-workflows/ -git add docs/archive/ -git commit -m "Archive old deployment scripts" -``` - -### Update Team Documentation - -Update your teaching team's workflow documentation: - -**Before:** - -```markdown -## Publishing to Production - -1. Switch to production branch: `git checkout production` -2. Merge from draft: `git merge draft` -3. Resolve conflicts if any -4. Push: `git push origin production` -5. Wait 5 minutes -6. Manually verify site is live -``` - -**After:** - -````markdown -## Publishing to Production - -Run the publish command: -```bash -/folio:site:publish -``` - -The command will: - -- Validate content automatically -- Show preview of changes -- Ask for confirmation -- Deploy with rollback on failure -- Verify site is live -```` - -### Notify Your Team - -**Email template:** - -``` - -Subject: New Teaching Site Workflow - Craft Automation - -We've migrated to Craft for automated teaching site deployment. - -What changed: - -- Old: Manual branch switching, merge, push (error-prone) -- New: One command with validation and preview - -New workflow: - -1. Edit content on draft branch -2. Run: /folio:site:publish -3. Review preview and confirm - -Benefits: - -- Catches errors BEFORE students see them -- Preview changes before publishing -- Automatic rollback on failure -- No more manual branch management - -Documentation: - - -Questions? Reply to this email. - -``` - -## Troubleshooting Migration Issues - -### Existing Content Fails Validation - -**Problem:** Old content doesn't meet Craft's validation rules - -**Solution:** - -```bash -# See what's wrong -/craft:site:validate - -# Fix issues one by one -# OR adjust validation rules - -# Edit config to relax rules temporarily -vim .flow/teach-config.yml -``` - -```yaml -validation: - required_sections: - - grading # Only require grading for now - strict_mode: false # Warnings only -``` - -**Gradually tighten rules:** - -- Start with relaxed validation -- Fix content over time -- Add sections to `required_sections` -- Enable `strict_mode` when ready - -### Branch Names Don't Match - -**Problem:** You use `main` and `dev` instead of `production` and `draft` - -**Solution:** Update config to match your branches - -```yaml -deployment: - production_branch: "main" # Your production branch - draft_branch: "dev" # Your draft branch -``` - -### Merge Conflicts During Migration - -**Problem:** Production and draft have diverged significantly - -**Solution:** - -```bash -# Option 1: Force sync (if production is outdated) -git checkout production -git reset --hard draft -git push --force origin production - -# Option 2: Merge manually first -git checkout production -git merge draft -# Resolve conflicts -git commit -# Then use Craft for future publishes -``` - -### GitHub Pages Not Deploying - -**Problem:** Site doesn't update after publish - -**Solution:** - -1. Check GitHub Actions: `gh run list --limit 5` -2. Verify branch in Settings → Pages -3. Check `gh_pages_url` in config matches actual URL -4. Wait up to 10 minutes for first deploy - -### Team Members Don't Have Craft - -**Problem:** Only you have Craft installed - -**Solution:** - -```bash -# Share installation instructions -# Homebrew (recommended) -brew tap data-wise/tap -brew install craft - -# Or curl -curl -fsSL https://raw.githubusercontent.com/Data-Wise/craft/main/install.sh | bash -``` - -**Document in team README:** - -````markdown -## Setup - -Install Craft: -```bash -brew install craft -``` - -Then use teaching workflows: - -```bash -/folio:site:publish -/folio:site:progress -/craft:site:validate -``` -```` - -## Migration Checklist - -Use this checklist to track migration progress: - -**Pre-migration:** - -- [ ] Review old deployment scripts -- [ ] Document current workflow -- [ ] Identify custom validation logic -- [ ] List all team members who deploy - -**Configuration:** - -- [ ] Create `.flow/teach-config.yml` -- [ ] Set course info (number, title, semester) -- [ ] Add semester dates (start, end, breaks) -- [ ] Configure deployment (branches, URL) -- [ ] Set validation rules -- [ ] Test config with `/folio:site:status` - -**Validation:** - -- [ ] Run `/craft:site:validate` -- [ ] Fix all validation errors -- [ ] Test with incomplete content (intentionally break validation) -- [ ] Verify error messages are clear - -**Publishing:** - -- [ ] Test `/folio:site:publish` on draft branch -- [ ] Review preview output carefully -- [ ] Confirm publish and monitor deployment -- [ ] Verify live site is correct -- [ ] Test rollback (create a failure scenario) - -**Progress Tracking:** - -- [ ] Run `/folio:site:progress` -- [ ] Verify current week calculation -- [ ] Check break countdown accuracy -- [ ] Test `--week` manual override - -**Team Migration:** - -- [ ] Install Craft for all team members -- [ ] Update team workflow documentation -- [ ] Send migration announcement email -- [ ] Hold training session (optional) -- [ ] Archive old deployment scripts - -**Post-migration:** - -- [ ] Monitor first 3 publishes for issues -- [ ] Collect team feedback -- [ ] Adjust validation rules if needed -- [ ] Update issue templates to reference new workflow - -## Success Metrics - -Track these to measure migration success: - -**Before Craft (baseline):** - -- Time to publish: ~15 minutes (manual steps) -- Publishing errors: ~20% of publishes have issues -- Broken link incidents: ~2 per semester -- Rollback required: ~10% of publishes - -**After Craft (target):** - -- Time to publish: ~3 minutes (one command) -- Publishing errors: <5% (caught by validation) -- Broken link incidents: 0 (validation catches them) -- Rollback required: 0 (automatic on failure) - -**Measure at 4 weeks, 8 weeks, and end of semester.** - -## Next Steps - -After successful migration: - -1. **Explore advanced features:** - - Custom validation rules - - JSON output for scripting - - CI/CD integration - -2. **Share experience:** - - Write blog post about migration - - Share config with other instructors - - Contribute validation patterns - -3. **Provide feedback:** - - What worked well? - - What was confusing? - - What features are missing? - -4. **Help others migrate:** - - Share this guide with colleagues - - Offer to help with their migration - - Build a community of practice - -## Resources - -- **[Teaching Mode Setup Tutorial](tutorials/teaching-mode-setup.md)** - First-time setup guide -- **[Config Schema](teaching-config-schema.md)** - Complete YAML reference -- **[Command Reference: `/folio:site:publish`](commands/site.md)** - Publishing workflow with validation -- **[Command Reference: `/folio:site:progress`](commands/site.md)** - Progress dashboard -- **[GitHub Issues](https://github.com/Data-Wise/craft/issues)** - Report problems or suggest features - -## Get Help - -**Questions during migration?** - -- Review troubleshooting section above -- Check [Teaching Config Schema](teaching-config-schema.md) -- Open an issue: [GitHub Issues](https://github.com/Data-Wise/craft/issues) - -**Feedback welcome!** Share your migration experience to help improve this guide. diff --git a/docs/teaching/index.md b/docs/teaching/index.md deleted file mode 100644 index 3a6d17c1e..000000000 --- a/docs/teaching/index.md +++ /dev/null @@ -1,139 +0,0 @@ -# Teaching Workflow - -> **TL;DR**: Three tools, one workflow. Flow-cli dispatches commands at shell speed, Scholar generates AI content, Craft manages your course site safely. - -## Teaching Ecosystem - -The teaching workflow spans three tools. Each has clear ownership: - -
- -- :zap:{ .lg .middle } **Flow-CLI** *(Shell Speed)* - - Config, deployment, semester tracking, shell aliases. - `teach status`, `teach deploy`, `teach config` - -- :brain:{ .lg .middle } **Scholar** *(AI Content)* - - Lectures, exams, quizzes, slides, assignments, rubrics. - `teach lecture`, `teach exam`, `teach quiz` - -- :shield:{ .lg .middle } **Craft** *(Site Safety)* - - Preview-before-publish, validation, progress tracking. - `/folio:site:publish`, `/folio:site:progress` - -
- -## Which tool do I use? - -| I want to... | Use | Command | -|---|---|---| -| Generate a lecture/exam/quiz | Scholar | `teach lecture`, `teach exam` | -| Deploy the course site | flow-cli | `teach deploy` | -| Check if content is ready | All three | `teach check` | -| See what week it is | flow-cli | `teach status` or `tst` | -| Publish with CI safety | Craft | `/folio:site:publish` | -| See all teaching commands | flow-cli | `teach map` | -| Track semester progress | Craft | `/folio:site:progress` | - -## Getting Started - -
- -- :rocket:{ .lg .middle } **First-Time Setup** - - --- - - 25-minute tutorial: create config, test detection, validate, publish. - - [:octicons-arrow-right-24: Setup Tutorial](../tutorials/teaching-mode-setup.md) - -- :gear:{ .lg .middle } **Config Reference** - - --- - - Complete YAML schema for `.flow/teach-config.yml`. Flow-cli configs work too. - - [:octicons-arrow-right-24: Config Schema](../teaching-config-schema.md) - -- :arrows_counterclockwise:{ .lg .middle } **Migration Guide** - - --- - - Moving from manual `git checkout && mkdocs build && git push` to one command. - - [:octicons-arrow-right-24: Migration Guide](../teaching-migration.md) - -- :card_file_box:{ .lg .middle } **Quick Reference** - - --- - - Printable cheat sheet: commands, flags, branch strategy, troubleshooting. - - [:octicons-arrow-right-24: Quick Reference](../reference/REFCARD-TEACHING.md) - -
- -## Site Commands (moved to folio) - -| Command | What it does | -|---------|-------------| -| `/folio:site:publish` | Preview → Validate → Switch branch → Build → Deploy (5-step safety) | -| `/folio:site:progress` | Semester dashboard with week tracking, break countdown, completion % | -| `/folio:site:build` | Build site with teaching-aware context (branch, week, course info) | -| `/folio:site:check` | Content validation (syllabus sections, schedule completeness, assignments) | -| ask "git status" (dev/git skill) | Teaching-aware git status (shows deployment context, what students see) | - -## Config Compatibility - -Flow-cli is the canonical config owner. Craft normalizes flow-cli's schema silently: - -| Flow-CLI writes | Craft reads as | -|---|---| -| `course.name` | `course.number` | -| `course.full_name` | `course.title` | -| `semester_info.start_date` | `dates.start` | -| `semester_info.end_date` | `dates.end` | -| `branches.production` | `deployment.production_branch` | -| `course.semester: "spring"` | `course.semester: "Spring"` | - -No migration needed. Both schemas work transparently. See [Config Schema](../teaching-config-schema.md#flow-cli-config-compatibility) for details. - -## Common Workflows - -### Weekly Content Update - -```bash -# 1. Edit content on draft branch -# 2. Preview changes -/folio:site:build -# 3. Validate and publish -/folio:site:publish -``` - -### Semester Check-In - -```bash -# See where you are -/folio:site:progress - -# Validate everything -/folio:site:publish --dry-run --validate-only -``` - -### Deploy Course Website - -See the full [Deploy Course Website recipe](../cookbook/common/deploy-course-website.md) for step-by-step instructions. - -## Documentation - -| Resource | Purpose | -|----------|---------| -| [Ecosystem Overview](../guide/teaching-workflow.md) | Full guide with workflows, validation, troubleshooting | -| [Setup Tutorial](../tutorials/teaching-mode-setup.md) | First-time 25-minute walkthrough | -| [Config Schema](../teaching-config-schema.md) | YAML reference (flow-cli compatible) | -| [Migration Guide](../teaching-migration.md) | From manual workflows to Craft | -| [Quick Reference](../reference/REFCARD-TEACHING.md) | Printable cheat sheet | -| [Teaching Spec](https://github.com/Data-Wise/craft/blob/main/docs/specs/_archive/SPEC-teaching-workflow-2026-01-16.md) | Original implementation spec | -| [Ecosystem Spec](https://github.com/Data-Wise/craft/blob/main/docs/specs/_archive/SPEC-teaching-ecosystem-coordination-2026-02-06.md) | Cross-tool coordination spec | diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 90a332269..9e38f7fea 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -62,7 +62,6 @@ |----------|------|-------------------| | [Dependency Management](dependency-management-getting-started.md) | 10 min | Check, install, and manage tool dependencies | | [Formatting Migration](TUTORIAL-formatting-migration.md) | 15 min | Migrate scripts to the shared formatting library | -| [Teaching Mode Setup](teaching-mode-setup.md) | 15 min | Configure teaching-specific features and workflows | ## What's New diff --git a/docs/tutorials/teaching-mode-setup.md b/docs/tutorials/teaching-mode-setup.md deleted file mode 100644 index 6c08e77ab..000000000 --- a/docs/tutorials/teaching-mode-setup.md +++ /dev/null @@ -1,567 +0,0 @@ - -# Teaching Mode Setup - -⏱️ **25 minutes** • 🟢 Beginner • ✓ Complete first-time setup - -> **TL;DR** (30 seconds) -> -> - **What:** Set up Craft's teaching workflows for your course -> - **Why:** Automated validation, safe publishing, progress tracking -> - **How:** Create config file → Test detection → Validate → Publish -> - **Next:** Use `/folio:site:progress` weekly, `/folio:site:publish` for releases - -Teaching Mode transforms Craft into a comprehensive teaching workflow automation tool. This tutorial walks you through first-time setup for a Quarto-based course website. - -!!! note "Already using flow-cli?" - If your project already has a `.flow/teach-config.yml` from flow-cli (with `semester_info`, `course.name`, `branches`), Craft reads it directly — no separate config needed. See the [Config Schema Reference](../teaching-config-schema.md#flow-cli-config-compatibility) for details. - -!!! tip "Before You Start" - You need: - - - A Quarto project for your teaching content - - Git repository with draft/production branches (or willing to create them) - - GitHub Pages or similar deployment target - - Basic familiarity with YAML and Git - -## What You'll Learn - -By the end of this tutorial, you'll have: - -- ✓ Teaching mode enabled with auto-detection -- ✓ Content validation catching missing syllabus sections -- ✓ Preview-before-publish workflow preventing errors -- ✓ Automatic semester progress tracking -- ✓ Safe deployment to production - -## Step 1: Create Config File (5 min) - -Teaching mode activates when Craft detects `.flow/teach-config.yml` in your project root. - -### 1.1 Create Directory - -```bash -# In your teaching project root -mkdir -p .flow -``` - -### 1.2 Create Configuration - -Create `.flow/teach-config.yml` with your course details: - -```yaml -# .flow/teach-config.yml - STAT 440 Spring 2026 - -# ============================================================================ -# Course Information -# ============================================================================ -course: - number: "STAT 440" - title: "Regression Analysis" - semester: "Spring" - year: 2026 - -# ============================================================================ -# Semester Dates -# ============================================================================ -dates: - # Semester runs January 19 - May 8 - start: "2026-01-19" - end: "2026-05-08" - - # Break periods (holidays + Spring Break) - breaks: - - name: "MLK Day" - start: "2026-01-20" - end: "2026-01-20" # Single-day breaks are supported - - name: "Spring Break" - start: "2026-03-16" - end: "2026-03-20" - -# ============================================================================ -# Deployment Configuration -# ============================================================================ -deployment: - production_branch: "production" # Students see this - draft_branch: "draft" # Instructors see this - gh_pages_url: "https://yourname.github.io/stat-440" - -# ============================================================================ -# Progress Tracking -# ============================================================================ -progress: - # "auto" calculates based on dates and breaks (recommended) - current_week: auto - -# ============================================================================ -# Validation Rules -# ============================================================================ -validation: - # Sections that must exist in syllabus - required_sections: - - grading - - policies - - objectives - - schedule - - # Strict mode: true = errors block publishing - strict_mode: true -``` - -**Customize for your course:** - -- Replace `STAT 440` with your course number -- Update semester dates to match your academic calendar -- Add all break periods (even 1-2 day breaks) -- Set your GitHub Pages URL - -!!! warning "Common Mistake" - Don't forget to update the **year** field. Copy-paste errors from previous semesters are common! - -### 1.3 Validate Configuration - -Test that your config file is valid: - -```bash -# In Claude Code -/folio:site:status -``` - -**Expected output:** - -``` -✓ Teaching mode detected -✓ Configuration valid -✓ Semester: Spring 2026 (16 weeks) -✓ Current week: 1 (auto-calculated) -``` - -**If you see errors**, check: - -- Date format is `YYYY-MM-DD` -- Dates are in logical order (start < end) -- Breaks fall within semester dates -- All required fields present - -## Step 2: Test Detection (2 min) - -Verify that Craft recognizes your project as a teaching project. - -### 2.1 Check Status - -```bash -/folio:site:status -``` - -**What to look for:** - -``` -┌─────────────────────────────────────────────────────────┐ -│ 📚 TEACHING PROJECT STATUS │ -├─────────────────────────────────────────────────────────┤ -│ ✓ Mode: Teaching (detected via .flow/teach-config.yml) │ -│ ✓ Course: STAT 440 - Regression Analysis │ -│ ✓ Semester: Spring 2026 │ -│ ✓ Current Week: 1 of 16 │ -└─────────────────────────────────────────────────────────┘ -``` - -If teaching mode is **NOT** detected, check: - -1. File location: Must be `.flow/teach-config.yml` in project root -2. File permissions: Must be readable -3. YAML syntax: Use a validator if needed - -## Step 3: Validate Content (3 min) - -Run content validation to check for missing or incomplete sections. - -### 3.1 Run Validation - -```bash -/craft:site:validate -``` - -### 3.2 Understand Validation Output - -**Example output with errors:** - -``` -============================================================ -TEACHING CONTENT VALIDATION: ❌ BLOCKED -============================================================ - -🚫 ERRORS (must fix before publishing): - 1. Syllabus missing required sections: policies, objectives - 2. Schedule has incomplete weeks (no content): Week 2, 4 - -⚠️ WARNINGS (recommended to fix): - 1. Missing assignment files: HW 2 - -📋 DETAILED CHECKS: - [✓] Syllabus: grading - [✗] Syllabus: policies - [✗] Syllabus: objectives - [✓] Syllabus: schedule - [✓] Schedule: exists - [✗] Schedule: 2/4 weeks complete - [✗] Assignments: 2/3 found - -============================================================ -Summary: 3/7 checks passed -Status: 2 error(s) blocking publish ❌ -============================================================ -``` - -**Clean output (all checks pass):** - -``` -============================================================ -TEACHING CONTENT VALIDATION: ✅ PASSED -============================================================ - -✅ ALL CHECKS PASSED - -📋 DETAILED CHECKS: - [✓] Syllabus: grading - [✓] Syllabus: policies - [✓] Syllabus: objectives - [✓] Syllabus: schedule - [✓] Schedule: exists - [✓] Schedule: 4/4 weeks complete - [✓] Assignments: 3/3 found - -============================================================ -Summary: 7/7 checks passed -Status: Ready to publish ✅ -============================================================ -``` - -### 3.3 Fix Validation Errors - -**Missing syllabus sections:** - -- Add headings in your `syllabus/index.qmd` or `syllabus.qmd` -- Required sections (default): grading, policies, objectives, schedule -- Can customize in `validation.required_sections` - -**Incomplete schedule:** - -- Check your `schedule.qmd` for missing weeks -- Validation expects content for each week (lectures, readings, or assignments) - -**Missing assignment files:** - -- Validation checks that assignments referenced in schedule exist -- Add missing files or update references - -!!! tip "Pro Tip: Use Warnings" - Warnings don't block publishing - they're helpful reminders. Errors MUST be fixed (unless you use `--skip-validation`). - -## Step 4: First Publish (10 min) - -Publish your content from draft to production with preview and safety checks. - -### 4.1 Ensure You're on Draft Branch - -```bash -# Check current branch -git branch --show-current - -# Switch to draft if needed -git checkout draft -``` - -### 4.2 Run Publish Command - -```bash -/folio:site:publish -``` - -### 4.3 Review Preview - -The command shows a categorized diff: - -``` -┌─────────────────────────────────────────────┐ -│ 📋 PUBLISH PREVIEW │ -├─────────────────────────────────────────────┤ -│ CRITICAL CHANGES: │ -│ ⚠️ syllabus/index.qmd +15 -3 │ -│ ⚠️ schedule.qmd +42 -8 │ -│ │ -│ CONTENT CHANGES: │ -│ ✓ lectures/week-01.qmd +120 -0 │ -│ ✓ lectures/week-02.qmd +95 -0 │ -│ ✓ readings/chapter-1.qmd +58 -2 │ -│ │ -│ OTHER CHANGES: │ -│ _quarto.yml +2 -1 │ -│ .gitignore +3 -0 │ -│ │ -│ Summary: 8 files, +335 lines, -14 lines │ -└─────────────────────────────────────────────┘ -``` - -**File categories:** - -- **CRITICAL**: syllabus*, schedule*, assignments/ -- **CONTENT**: lectures/, readings/, resources/ -- **OTHER**: All other files - -### 4.4 Confirm Publish - -You'll be prompted with 3 options: - -1. **"Yes - Merge and deploy (Recommended)"** - Proceed -2. **"Preview full diff first"** - See detailed `git diff` -3. **"Cancel"** - Abort - -Choose option 1 to proceed. - -### 4.5 Monitor Deployment - -The command will: - -1. Create backup branch (`production-backup-`) -2. Checkout production branch -3. Fast-forward merge from draft -4. Push to remote -5. Verify deployment (if `gh_pages_url` configured) - -**Success output:** - -``` -✅ PUBLISH SUCCESSFUL - -🌐 Live Site: https://yourname.github.io/stat-440/ - -📊 Changes Published: - - 8 files modified - - +335 lines added - - -14 lines removed - -💡 Next Steps: - • Review the live site: https://yourname.github.io/stat-440/ - • Clean up worktree by asking "clean up merged branches" (dev/git skill) - • Continue editing on draft branch - -⏱ Deployment may take 1-2 minutes to fully propagate. -``` - -### 4.6 Verify Live Site - -Wait 1-2 minutes, then visit your GitHub Pages URL: - -``` -https://yourname.github.io/stat-440/ -``` - -**Check:** - -- Syllabus displays correctly -- Schedule shows all weeks -- Assignments are linked properly - -!!! success "Congratulations!" - You've successfully published your first teaching site with Craft's safety workflow! - -## Step 5: Track Progress (2 min) - -Use the progress dashboard to monitor semester status. - -### 5.1 Run Progress Command - -```bash -/folio:site:progress -``` - -**Example output:** - -``` -┌─────────────────────────────────────────────────────────┐ -│ 📚 STAT 440: Regression Analysis │ -│ Spring 2026 · Week 1 of 16 (6% complete) │ -├─────────────────────────────────────────────────────────┤ -│ │ -│ 📅 CURRENT WEEK: Week 1 │ -│ Date Range: Jan 19-25 │ -│ │ -│ 📊 PROGRESS: │ -│ ▓░░░░░░░░░░░░░░░ 6% complete │ -│ │ -│ 📌 UPCOMING MILESTONES: │ -│ • Spring Break: Mar 16-20 (57 days) │ -│ │ -│ ⏰ NEXT BREAK: Spring Break in 57 days │ -│ │ -└─────────────────────────────────────────────────────────┘ -``` - -**Key information:** - -- Current week (auto-calculated from dates and breaks) -- Visual progress bar -- Upcoming break countdown -- Date range for current week - -### 5.2 Weekly Check-In - -Run `/folio:site:progress` at the start of each week to: - -- Confirm current week is correct -- Plan upcoming content -- Track progress toward semester goals - -!!! tip "Manual Override" - Need to test future weeks? Use `--week` flag: - ```bash - /folio:site:progress --week 8 - ``` - -## Common Workflows - -### Weekly Content Updates - -```bash -# 1. Edit content on draft branch -# (edit lectures, assignments, etc.) - -# 2. Validate before publishing -/craft:site:validate - -# 3. Publish to production -/folio:site:publish -``` - -### Assignment Releases - -```bash -# 1. Add assignment files -# assignments/hw-02.qmd - -# 2. Update schedule -# schedule.qmd (add HW 2 due date) - -# 3. Validate -/craft:site:validate - -# 4. Publish -/folio:site:publish -``` - -### Schedule Changes - -```bash -# 1. Edit schedule.qmd -# (move due dates, adjust topics) - -# 2. Validate completeness -/craft:site:validate - -# 3. Preview changes -/folio:site:publish -# (review CRITICAL CHANGES section carefully) - -# 4. Confirm and publish -``` - -## Troubleshooting - -### Teaching Mode Not Detected - -**Symptom:** `/folio:site:status` doesn't show teaching mode - -**Solution:** - -1. Check file location: `.flow/teach-config.yml` in project root -2. Verify YAML syntax with validator -3. Ensure file is readable (`ls -la .flow/`) - -### Validation Errors Block Publishing - -**Symptom:** Errors prevent `/folio:site:publish` from proceeding - -**Solution:** - -1. Read error messages carefully -2. Fix missing sections or incomplete content -3. Run `/craft:site:validate` to verify fixes -4. **Emergency bypass** (not recommended): `--skip-validation` - -### Merge Conflicts During Publish - -**Symptom:** "Cannot fast-forward" error - -**Solution:** - -```bash -# Production branch has diverged from draft -git checkout production -git merge draft -# Resolve conflicts -git commit -/folio:site:publish # Try again -``` - -### Progress Inaccurate - -**Symptom:** Current week calculation is wrong - -**Solution:** - -1. Verify `dates.start` and `dates.end` in config -2. Check break dates are correct -3. Ensure breaks don't overlap -4. Use `--week` flag for manual override - -### Deployment Not Live After 5 Minutes - -**Symptom:** GitHub Pages not updating - -**Solution:** - -1. Check GitHub Actions: `gh run list --limit 5` -2. Verify branch in Settings → Pages (should be "production") -3. Check build logs for errors -4. Wait up to 10 minutes for first deployment - -## Next Steps - -Now that teaching mode is set up: - -**Weekly tasks:** - -- [ ] Run `/folio:site:progress` each Monday -- [ ] Update content on `draft` branch -- [ ] Validate with `/craft:site:validate` -- [ ] Publish with `/folio:site:publish` - -**Semester tasks:** - -- [ ] Add assignment files before due dates -- [ ] Update syllabus when policies change -- [ ] Keep schedule current (adjust for snow days, etc.) - -**Advanced features:** - -- [ ] Explore `--json` output for scripting -- [ ] Set up CI/CD to auto-validate on push -- [ ] Create custom validation rules -- [ ] Integrate with LMS (future enhancement) - -## Additional Resources - -- **[Config Schema Reference](../teaching-config-schema.md)** - Complete YAML specification (includes flow-cli compatibility) -- **[Teaching Workflow Guide](../guide/teaching-workflow.md)** - Ecosystem overview (Craft vs Scholar vs flow-cli) -- **[Migration Guide](../teaching-migration.md)** - From manual to Craft workflows -- **[Site Commands Reference](../commands/site.md)** - All site commands including publish, progress, and validate - -## Help and Support - -**Questions or issues?** - -- Check troubleshooting section above -- Review [Teaching Config Schema](../teaching-config-schema.md) -- Open an issue: [GitHub Issues](https://github.com/Data-Wise/craft/issues) - -**Feedback welcome!** Teaching mode is actively developed. Share your experiences and suggestions. diff --git a/examples/test_parser.py b/examples/test_parser.py deleted file mode 100644 index 83d6f13e2..000000000 --- a/examples/test_parser.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 -""" -Example script demonstrating teach_config.py usage. - -Usage: - python3 examples/test_parser.py [path/to/config/directory] - -This will load and validate the teaching config from the specified directory -(or current directory if not specified). -""" - -import sys -import json -from pathlib import Path - -# Add parent directory to path -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from commands.utils.teach_config import load_teach_config - - -def main(): - # Get directory from command line or use current directory - cwd = sys.argv[1] if len(sys.argv) > 1 else "." - - print(f"Loading teaching config from: {Path(cwd).resolve()}") - print("-" * 60) - - try: - config = load_teach_config(cwd) - - if config is None: - print("No teaching config found.") - print("\nSearched for:") - print(" - .flow/teach-config.yml") - print(" - teach-config.yml") - sys.exit(1) - - print("Configuration loaded successfully!\n") - - # Display course info - course = config["course"] - print(f"Course: {course['number']} - {course['title']}") - print(f"Semester: {course['semester']} {course['year']}") - - # Display dates - dates = config["dates"] - print(f"\nDates:") - print(f" Start: {dates['start']}") - print(f" End: {dates['end']}") - - # Display breaks if any - if dates.get("breaks"): - print(f"\nBreaks:") - for i, brk in enumerate(dates["breaks"], 1): - print(f" {i}. {brk['name']}: {brk['start']} to {brk['end']}") - - # Display instructor if present - if "instructor" in config: - instructor = config["instructor"] - print(f"\nInstructor:") - if "name" in instructor: - print(f" Name: {instructor['name']}") - if "email" in instructor: - print(f" Email: {instructor['email']}") - if "office_hours" in instructor: - print(f" Office Hours: {instructor['office_hours']}") - - # Display deployment settings - deployment = config["deployment"] - print(f"\nDeployment:") - print(f" Production Branch: {deployment['production_branch']}") - print(f" Draft Branch: {deployment['draft_branch']}") - if "gh_pages_url" in deployment: - print(f" GitHub Pages: {deployment['gh_pages_url']}") - - # Display progress - progress = config["progress"] - print(f"\nProgress:") - print(f" Current Week: {progress['current_week']}") - - # Display validation settings - validation = config["validation"] - print(f"\nValidation:") - print(f" Strict Mode: {validation['strict_mode']}") - print(f" Required Sections: {', '.join(validation['required_sections'])}") - - # Option to dump full config as JSON - if "--json" in sys.argv: - print("\n" + "=" * 60) - print("Full configuration (JSON):") - print("=" * 60) - print(json.dumps(config, indent=2, default=str)) - - except ValueError as e: - print(f"Validation Error:\n{e}") - sys.exit(1) - except Exception as e: - print(f"Error: {e}") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/mkdocs.yml b/mkdocs.yml index ddcb4ef09..0255c9223 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: Craft Plugin -site_description: "Full-stack developer toolkit - 48 commands, 2 agents, 41 skills for code, git, site, docs, testing, architecture, CI, distribution, teaching workflows, and planning with ADHD-friendly interfaces. v4.5.0 adds /craft:finish (renamed from /craft:done, ADR-006). See NEWS.md for release-by-release highlights." +site_description: "Full-stack developer toolkit - 48 commands, 2 agents, 41 skills for code, git, site, docs, testing, architecture, CI, distribution, and planning with ADHD-friendly interfaces. v4.5.0 adds /craft:finish (renamed from /craft:done, ADR-006). See NEWS.md for release-by-release highlights." site_url: https://data-wise.github.io/craft/ repo_name: Data-Wise/craft @@ -249,18 +249,6 @@ nav: - site:deploy Tutorial: tutorials/TUTORIAL-site-deploy.md - smart-help Tutorial: tutorials/TUTORIAL-smart-help.md - # 3. TEACHING - Dedicated teaching workflow section - - 📚 Teaching: - - Teaching Home: teaching/index.md - - Getting Started: - - Setup Tutorial: tutorials/teaching-mode-setup.md - - Config Schema: teaching-config-schema.md - - Migration Guide: teaching-migration.md - - Ecosystem & Guides: - - Ecosystem Overview: guide/teaching-workflow.md - - Documentation Index: TEACHING-DOCS-INDEX.md - - Quick Reference: reference/REFCARD-TEACHING.md - # 4. COMMANDS & REFERENCE - Command documentation - ⚙️ Commands & Reference: - Command Overview: commands/overview.md diff --git a/pyproject.toml b/pyproject.toml index a74596a86..0bf44ca12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,6 @@ markers = [ "skills: Skill file validation and behavior", "agents: Agent configuration and hooks", "docs: Documentation link checking, content validation", - "teaching: Teaching workflow and config validation", "claude_md: CLAUDE.md sync, audit, fix, edit operations", "branch_guard: Branch protection hook behavior", "orchestrator: Orchestrator v2.1 features and workflows", diff --git a/tests/MANUAL-TESTING-CHECKLIST.md b/tests/MANUAL-TESTING-CHECKLIST.md deleted file mode 100644 index b6cb90a13..000000000 --- a/tests/MANUAL-TESTING-CHECKLIST.md +++ /dev/null @@ -1,362 +0,0 @@ -# Teaching Workflow Manual Testing Checklist - -## Overview - -This checklist covers manual testing scenarios that complement automated tests. -Use with a real teaching project (e.g., STAT 545) when available. - -## Pre-Testing Setup - -- [ ] Identify test teaching project path -- [ ] Backup project before testing -- [ ] Verify git status is clean -- [ ] Note current git branch - -## 1. Teaching Mode Detection - -### Test Scenarios - -- [ ] **Config Detection**: Project with teach-config.yml - - Expected: Detects as teaching via "config" method - - Command: Run detection on project with config - -- [ ] **Structure Detection**: Project with syllabus.qmd + schedule.qmd - - Expected: Detects as teaching via "structure" method - - Command: Remove config temporarily, test detection - -- [ ] **Non-Teaching Project**: Random code project - - Expected: Not detected as teaching - - Command: Test detection on ~/projects/dev-tools/flow-cli - -### Verification - -```bash -# From test project directory -python3 -c " -import sys -from pathlib import Path -sys.path.insert(0, '/path/to/craft') -from utils.detect_teaching_mode import detect_teaching_mode -is_teaching, method = detect_teaching_mode('.') -print(f'Teaching: {is_teaching}, Method: {method}') -" -``` - -## 2. Configuration Loading & Validation - -### Test Scenarios - -- [ ] **Valid Minimal Config**: Only required fields - - Expected: Loads successfully, no errors - - Fixture: `tests/fixtures/teaching/minimal` - -- [ ] **Valid Full Config**: All optional fields - - Expected: Loads successfully, parses all sections - - Fixture: `tests/fixtures/teaching/stat-545` - -- [ ] **Invalid YAML**: Syntax errors - - Expected: Returns None or error message - - Create: Temporarily break YAML syntax - -- [ ] **Missing Required Fields**: Incomplete config - - Expected: Validation errors reported - - Create: Remove required fields temporarily - -- [ ] **Invalid Dates**: End before start - - Expected: Validation error - - Create: Swap start/end dates - -- [ ] **Overlapping Breaks**: Two breaks overlap - - Expected: Validation error - - Create: Configure overlapping break dates - -### Verification - -```bash -python3 tests/test_teach_config.py -``` - -## 3. Progress Calculation - -### Test Scenarios - -- [ ] **During Semester**: Current date within semester - - Expected: Week 1-16, percentage 0-100 - - Test: Use real course during semester - -- [ ] **Before Semester**: Current date before start - - Expected: Week ≤ 0 or "not started" status - - Create: Future course config - -- [ ] **After Semester**: Current date after end - - Expected: 100% complete or "ended" status - - Create: Past course config - -- [ ] **During Break**: Currently on break - - Expected: Break status indicated - - Test: Configure break around current date - -- [ ] **No Breaks**: Summer session - - Expected: Progress calculated without breaks - - Fixture: `tests/fixtures/teaching/summer` - -### Verification - -```bash -python3 tests/test_semester_progress.py -``` - -## 4. Content Validation - -### Test Scenarios - -- [ ] **Complete Syllabus**: All recommended sections - - Expected: No errors, all checkmarks - - Test: Well-formed syllabus - -- [ ] **Missing Syllabus Sections**: Incomplete - - Expected: Warnings for missing sections - - Create: Remove grading section - -- [ ] **Complete Schedule**: Proper week structure - - Expected: No errors - - Test: Well-formed schedule - -- [ ] **Malformed Schedule**: Missing week headers - - Expected: Warnings reported - - Create: Remove ## Week headers - -- [ ] **Missing Assignments**: No homework files - - Expected: Info message (not error) - - Test: Project without assignments/ - -### Verification - -```bash -python3 tests/test_teaching_validation.py -``` - -## 5. Publishing Workflow - -### Test Scenarios - -**WARNING**: These tests modify git state. Use on test branch only! - -- [ ] **Clean Publish**: No uncommitted changes - - Expected: Builds, pushes, deploys successfully - - Setup: Clean git status - -- [ ] **Dirty Publish**: Uncommitted changes - - Expected: Prompts to commit or stash - - Setup: Make uncommitted edits - -- [ ] **Build Failure**: Quarto errors - - Expected: Rollback, clear error message - - Create: Break quarto syntax - -- [ ] **Deployment Verification**: Check GitHub Pages - - Expected: Site accessible, content updated - - Verify: Visit - -- [ ] **Rollback on Failure**: Git restored on error - - Expected: Returns to pre-publish state - - Test: Simulate failure (break build) - -### Verification - -```bash -# Dry run first -python3 commands/teaching/publish.py --dry-run - -# Real publish (test branch only!) -python3 commands/teaching/publish.py -``` - -## 6. Dashboard & Progress Display - -### Test Scenarios - -- [ ] **Progress Bar**: Visual indicator - - Expected: ASCII progress bar, percentage - - Test: View dashboard during semester - -- [ ] **Week Display**: Current week highlighted - - Expected: Week X of Y format - - Test: View at different semester points - -- [ ] **Break Indicator**: Currently on break - - Expected: Break name shown - - Test: Configure break around today - -- [ ] **Upcoming Milestones**: Next 3 assignments - - Expected: Due dates listed - - Test: Course with upcoming deadlines - -- [ ] **Dashboard Layout**: ADHD-friendly format - - Expected: Short paragraphs, visual structure - - Test: Read dashboard output - -### Verification - -```bash -# View semester progress -python3 commands/teaching/semester-progress.py - -# View full dashboard (when implemented) -python3 commands/teaching/dashboard.py -``` - -## 7. Cross-Component Integration - -### Test Scenarios - -- [ ] **Detection → Config → Validation**: Full chain - - Expected: Smooth flow, no errors - - Test: Run all three in sequence - -- [ ] **Config → Progress → Dashboard**: Data flow - - Expected: Consistent data across tools - - Test: Verify week numbers match - -- [ ] **Validation → Publish**: Blocking errors - - Expected: Publish blocked if validation fails - - Test: Try publishing with errors - -### Verification - -Run complete workflow manually: - -```bash -# 1. Detect -python3 utils/detect_teaching_mode.py - -# 2. Validate -python3 commands/teaching/validate.py - -# 3. Progress -python3 commands/teaching/semester-progress.py - -# 4. Publish (if clean) -python3 commands/teaching/publish.py --dry-run -``` - -## 8. Performance Checks - -### Test Scenarios - -- [ ] **Detection Speed**: < 100ms - - Method: Time detection command - - Target: Instant response - -- [ ] **Config Parsing**: < 200ms - - Method: Time config load - - Target: Very fast - -- [ ] **Full Validation**: < 5s - - Method: Time validation command - - Target: Complete in seconds - -- [ ] **Progress Calculation**: < 100ms - - Method: Time progress calculation - - Target: Instant - -### Verification - -```bash -# Time each component -time python3 -c "from utils.detect_teaching_mode import detect_teaching_mode; detect_teaching_mode('.')" -time python3 -c "from commands.utils.teach_config import load_teach_config; load_teach_config('.')" -time python3 commands/teaching/validate.py -time python3 commands/teaching/semester-progress.py -``` - -## 9. Error Handling & Messages - -### Test Scenarios - -- [ ] **Clear Error Messages**: Actionable guidance - - Expected: Specific error + suggestion - - Test: Trigger various errors - -- [ ] **Graceful Degradation**: Partial failures - - Expected: Continues with warnings - - Test: Missing optional content - -- [ ] **Help Text**: Useful documentation - - Expected: Clear usage instructions - - Test: Run with --help - -### Verification - -Trigger errors intentionally and verify messages are helpful. - -## 10. Edge Cases - -### Test Scenarios - -- [ ] **Empty Config**: Valid YAML but no content - - Expected: Validation errors for missing fields - -- [ ] **Very Long Semester**: 20+ weeks - - Expected: Handles correctly - -- [ ] **Very Short Semester**: 4 weeks (summer) - - Expected: Handles correctly - - Fixture: `tests/fixtures/teaching/summer` - -- [ ] **Many Breaks**: 5+ break periods - - Expected: All counted correctly - -- [ ] **Break Spanning Weeks**: Multi-week break - - Expected: All weeks excluded - -- [ ] **Unicode Content**: Non-ASCII characters - - Expected: Handles correctly - -### Verification - -Create edge case configs and test each component. - -## Success Criteria - -### Must Pass - -- [x] All automated tests pass (139+ tests) -- [ ] All detection scenarios work -- [ ] All validation checks accurate -- [ ] Progress calculation correct -- [ ] Publish workflow safe (no data loss) - -### Quality Metrics - -- [ ] Performance targets met -- [ ] Error messages clear and actionable -- [ ] ADHD-friendly output maintained -- [ ] No false positives (warnings OK) - -## Testing Notes - -### Date - -### Tester - -### Project Used - -### Issues Found - -1. - -### Suggestions - -1. - -## Sign-Off - -- [ ] All critical scenarios tested -- [ ] No blocking bugs found -- [ ] Documentation accurate -- [ ] Ready for merge - -**Tester Signature**: ___________________________ - -**Date**: ___________________________ diff --git a/tests/demo_teaching_validation.py b/tests/demo_teaching_validation.py deleted file mode 100644 index 16b866fdf..000000000 --- a/tests/demo_teaching_validation.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env python3 -"""Demo script for teaching content validation. - -Shows validation output for different course scenarios: -1. Complete valid course -2. Course with missing syllabus sections -3. Course with incomplete schedule -4. Course with missing assignments -""" - -import sys -import tempfile -from pathlib import Path - -import pytest - -# Add parent directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from commands.utils.teaching_validation import validate_teaching_content - -pytestmark = [pytest.mark.e2e, pytest.mark.teaching] - - -def create_demo_course(scenario: str) -> Path: - """Create a temporary course directory for demo.""" - course_dir = Path(tempfile.mkdtemp()) - - if scenario == "complete": - # Complete valid course - (course_dir / 'syllabus.qmd').write_text(""" -# Course Syllabus - -## Learning Objectives -Students will master statistical methods and apply them to real-world problems. - -## Course Policies -Attendance is required. Late work accepted with 24-hour notice. - -## Grading -- Homework: 40% -- Exams: 60% - -## Schedule -See the full schedule page for weekly topics and assignments. - """) - - (course_dir / 'schedule.qmd').write_text(""" -# Course Schedule - -## Week 1 -Introduction to statistics. Read Chapter 1. -Complete HW 1 by Friday. - -## Week 2 -Descriptive statistics. Read Chapter 2. -Complete HW 2 by Friday. - -## Week 3 -Probability theory. Read Chapter 3. -Midterm exam on Friday. - """) - - assignments_dir = course_dir / 'assignments' - assignments_dir.mkdir() - (assignments_dir / 'hw-1.qmd').write_text("# Homework 1") - (assignments_dir / 'hw-2.qmd').write_text("# Homework 2") - - elif scenario == "missing_syllabus": - # Missing syllabus sections - (course_dir / 'syllabus.qmd').write_text(""" -# Course Syllabus - -## Grading -Homework and exams. - """) - - (course_dir / 'schedule.qmd').write_text(""" -## Week 1 -Introduction with plenty of content here. - """) - - elif scenario == "incomplete_schedule": - # Incomplete schedule - (course_dir / 'syllabus.qmd').write_text(""" -# Syllabus - -## Objectives -Learn statistics. - -## Policies -Be nice. - -## Grading -Homework 50%, Exams 50%. - -## Schedule -See schedule page. - """) - - (course_dir / 'schedule.qmd').write_text(""" -## Week 1 -Introduction. Lots of content here. - -## Week 2 - -## Week 3 -More content here. - """) - - elif scenario == "missing_assignments": - # Missing assignment files - (course_dir / 'syllabus.qmd').write_text(""" -# Syllabus - -## Objectives -Learn statistics. - -## Policies -Be respectful. - -## Grading -Homework counts. - -## Schedule -Weekly topics. - """) - - (course_dir / 'schedule.qmd').write_text(""" -## Week 1 -Introduction. HW 1 due Friday. Good content here. - -## Week 2 -Descriptive stats. HW 2 due Friday. More content. - -## Week 3 -Probability. HW 3 due Friday. Even more content. - """) - - # Only create HW 1, missing HW 2 and HW 3 - assignments_dir = course_dir / 'assignments' - assignments_dir.mkdir() - (assignments_dir / 'hw-1.qmd').write_text("# Homework 1") - - return course_dir - - -def run_demo(): - """Run validation demos for all scenarios.""" - scenarios = [ - ("complete", "✅ Complete Valid Course"), - ("missing_syllabus", "❌ Missing Syllabus Sections"), - ("incomplete_schedule", "❌ Incomplete Schedule"), - ("missing_assignments", "⚠️ Missing Assignments"), - ] - - for scenario_id, scenario_name in scenarios: - print(f"\n{'=' * 70}") - print(f"SCENARIO: {scenario_name}") - print('=' * 70) - - # Create demo course - course_dir = create_demo_course(scenario_id) - - # Run validation - result = validate_teaching_content(str(course_dir)) - - # Print report - print(result.format_report()) - - # Cleanup - import shutil - shutil.rmtree(course_dir) - - -if __name__ == '__main__': - print("Teaching Content Validation Demo") - print("=" * 70) - print("This demo shows validation output for different course scenarios.") - run_demo() - print("\n" + "=" * 70) - print("Demo complete!") - print("=" * 70) diff --git a/tests/test_coverage_gaps.py b/tests/test_coverage_gaps.py index c490c62ef..ea3a43932 100644 --- a/tests/test_coverage_gaps.py +++ b/tests/test_coverage_gaps.py @@ -3,7 +3,6 @@ Comprehensive tests to achieve 90%+ coverage for utils modules. This test file specifically targets coverage gaps identified in: -- utils/detect_teaching_mode.py (65% → 90%+) - utils/linkcheck_ignore_parser.py (71% → 90%+) Tests focus on: @@ -25,148 +24,6 @@ pytestmark = [pytest.mark.unit, pytest.mark.structure] -# ============================================================================ -# Test: detect_teaching_mode.py Coverage Gaps -# ============================================================================ - -class TestDetectTeachingModeCoverageGaps: - """Tests for detect_teaching_mode.py coverage gaps (targeting 90%+).""" - - def test_yaml_import_error_fallback(self, tmp_path): - """ - Test fallback behavior when YAML import fails. - - Coverage: Lines 33-34 (ImportError except block) - Scenario: YAML library not available, should fall back to text search - """ - from utils import detect_teaching_mode - - # Mock YAML_AVAILABLE = False - with patch('utils.detect_teaching_mode.YAML_AVAILABLE', False): - # Create _quarto.yml with teaching: true - quarto_file = tmp_path / "_quarto.yml" - quarto_file.write_text("teaching: true\nproject:\n type: website") - - is_teaching, method = detect_teaching_mode.detect_teaching_mode(str(tmp_path)) - - # Should still detect teaching mode via text search fallback - assert is_teaching is True - assert "metadata" in method.lower() - - def test_yaml_unavailable_text_search_fallback(self, tmp_path): - """ - Test text search fallback when YAML parsing is unavailable. - - Coverage: Lines 107-112 (fallback text search logic) - Scenario: YAML library missing, use simple text search - """ - from utils import detect_teaching_mode - - # Create _quarto.yml with teaching: true - quarto_file = tmp_path / "_quarto.yml" - quarto_file.write_text(""" -project: - type: website - teaching: true - title: "Course Website" -""") - - with patch('utils.detect_teaching_mode.YAML_AVAILABLE', False): - is_teaching, method = detect_teaching_mode.detect_teaching_mode(str(tmp_path)) - - assert is_teaching is True - assert method == "metadata" - - def test_yaml_unavailable_false_positive_prevention(self, tmp_path): - """ - Test that text search doesn't create false positives. - - Coverage: Lines 107-112 (fallback validation) - Scenario: File contains "teaching" but not "teaching: true" - """ - from utils import detect_teaching_mode - - # Create _quarto.yml mentioning teaching but not enabled - quarto_file = tmp_path / "_quarto.yml" - quarto_file.write_text(""" -# This is a teaching resource reference -# teaching: false -project: - type: website -""") - - with patch('utils.detect_teaching_mode.YAML_AVAILABLE', False): - is_teaching, method = detect_teaching_mode.detect_teaching_mode(str(tmp_path)) - - # Should not detect as teaching mode (teaching: false or missing) - assert is_teaching is False - - def test_yaml_fallback_exception_handling(self, tmp_path): - """ - Test exception handling in text search fallback. - - Coverage: Lines 111-112 (except Exception in fallback) - Scenario: File read error during fallback - """ - from utils import detect_teaching_mode - - quarto_file = tmp_path / "_quarto.yml" - quarto_file.write_text("teaching: true") - - with patch('utils.detect_teaching_mode.YAML_AVAILABLE', False): - # Mock file read to raise exception - with patch('pathlib.Path.read_text', side_effect=PermissionError("Access denied")): - # Should handle exception gracefully - # Fallback will fail, but shouldn't crash - is_teaching, method = detect_teaching_mode.detect_teaching_mode(str(tmp_path)) - - # Should fall through to False (no detection) - assert is_teaching is False - - def test_main_execution_current_directory(self, tmp_path, capsys, monkeypatch): - """ - Test __main__ block execution for current directory. - - Coverage: Lines 153-159 (main block, current directory test) - Scenario: Running module directly with no arguments - """ - from utils import detect_teaching_mode - - # Change to temp directory - monkeypatch.chdir(tmp_path) - - # Create teaching mode indicators - (tmp_path / "_quarto.yml").write_text("teaching: true") - - # Execute main block - with patch.object(sys, 'argv', ['detect_teaching_mode.py']): - detect_teaching_mode.main() if hasattr(detect_teaching_mode, 'main') else None - - captured = capsys.readouterr() - # Should print current directory results - # Note: main() may not exist, check exists first - - def test_main_execution_with_argument(self, tmp_path, capsys): - """ - Test __main__ block execution with command line argument. - - Coverage: Lines 162-167 (main block, test directory argument) - Scenario: Running module with path argument - """ - from utils import detect_teaching_mode - - # Create test directory with teaching mode - test_dir = tmp_path / "test_course" - test_dir.mkdir() - (test_dir / "_quarto.yml").write_text("teaching: true") - - # Execute with argument - with patch.object(sys, 'argv', ['detect_teaching_mode.py', str(test_dir)]): - # Only test if __name__ == "__main__" block can be executed - # In practice, this is hard to test directly without running the module - pass - - # ============================================================================ # Test: linkcheck_ignore_parser.py Coverage Gaps # ============================================================================ @@ -450,76 +307,6 @@ def test_main_execution_examples(self, capsys): assert "No changes" in preview3 or "reads" in preview3 -# ============================================================================ -# Integration: Cross-Module Coverage Tests -# ============================================================================ - -class TestCrossModuleCoverage: - """Integration tests to ensure all modules work together at 90%+ coverage.""" - - def test_teaching_mode_with_linkcheck_integration(self, tmp_path): - """ - Test teaching mode detection in combination with link checking. - - Scenario: Teaching mode project with expected broken links - """ - from utils.detect_teaching_mode import detect_teaching_mode - from utils.linkcheck_ignore_parser import parse_linkcheck_ignore - - # Create teaching mode project - (tmp_path / "_quarto.yml").write_text("teaching: true") - (tmp_path / "syllabus.qmd").write_text("# Syllabus") - (tmp_path / "schedule.qmd").write_text("# Schedule") - - # Create .linkcheck-ignore - ignore_file = tmp_path / ".linkcheck-ignore" - ignore_file.write_text(""" -### Teaching Files -File: syllabus.qmd -Target: ../resources/*.pdf -""") - - # Detect teaching mode - is_teaching, method = detect_teaching_mode(str(tmp_path)) - assert is_teaching is True - - # Parse ignore rules - rules = parse_linkcheck_ignore(str(ignore_file)) - should_ignore, category = rules.should_ignore("syllabus.qmd", "../resources/lecture1.pdf") - assert should_ignore is True - - def test_dry_run_with_teaching_mode_detection(self, tmp_path): - """ - Test dry-run output for teaching mode commands. - - Scenario: Preview teaching mode deployment - """ - from utils.dry_run_output import render_dry_run_preview, RiskLevel - from utils.detect_teaching_mode import detect_teaching_mode - - # Create teaching mode project - (tmp_path / "_quarto.yml").write_text("teaching: true") - - is_teaching, _ = detect_teaching_mode(str(tmp_path)) - assert is_teaching is True - - # Generate dry-run preview for teaching deployment - preview = render_dry_run_preview( - command_name="Deploy Teaching Site", - actions=[ - "✓ Build course website", - "✓ Validate syllabus dates", - "✓ Push to gh-pages branch" - ], - warnings=["Teaching mode detected: extra validations applied"], - summary="Safe to deploy (teaching mode)", - risk_level=RiskLevel.LOW - ) - - assert "Deploy Teaching Site" in preview - assert "Teaching mode" in preview or "teaching" in preview.lower() - - # ============================================================================ # Run Coverage Report # ============================================================================ diff --git a/tests/test_integration_teaching_workflow.py b/tests/test_integration_teaching_workflow.py deleted file mode 100644 index 791cd7a98..000000000 --- a/tests/test_integration_teaching_workflow.py +++ /dev/null @@ -1,353 +0,0 @@ -#!/usr/bin/env python3 -""" -Integration Tests: Teaching Workflow -===================================== -Tests the full teaching workflow end-to-end including detection, -validation, and publish cycle. - -Components tested: -- commands/utils/detect_teaching_mode.py -- commands/utils/teaching_validation.py -- commands/utils/teach_config.py -- commands/site/publish.md -- commands/site/progress.md -- commands/site/build.md - -Run with: python tests/test_integration_teaching_workflow.py -""" - -import unittest -import tempfile -import yaml -from pathlib import Path -from datetime import date, timedelta -import sys - -import pytest - -# Add plugin directory to path -plugin_dir = Path(__file__).parent.parent -utils_dir = plugin_dir / "commands" / "utils" -sys.path.insert(0, str(plugin_dir)) -sys.path.insert(0, str(utils_dir)) - -# Try to import teaching modules -try: - from commands.utils.detect_teaching_mode import detect_teaching_mode - DETECT_AVAILABLE = True -except ImportError: - DETECT_AVAILABLE = False - -try: - from commands.utils.teach_config import load_teach_config, _normalize_config - CONFIG_AVAILABLE = True -except ImportError: - CONFIG_AVAILABLE = False - -pytestmark = [pytest.mark.integration, pytest.mark.teaching] - - -class TestTeachingWorkflowIntegration(unittest.TestCase): - """Integration tests for teaching workflow.""" - - def setUp(self): - """Create temporary teaching project.""" - self.temp_dir = tempfile.mkdtemp() - self.project_dir = Path(self.temp_dir) - - def tearDown(self): - """Clean up temporary files.""" - import shutil - shutil.rmtree(self.temp_dir, ignore_errors=True) - - @unittest.skipIf(not DETECT_AVAILABLE, "Teaching detection module not available") - def test_01_detection_with_config(self): - """Test: Detects teaching mode from .flow/teach-config.yml.""" - # Arrange: Create teach-config.yml - config_dir = self.project_dir / ".flow" - config_dir.mkdir(parents=True) - config_file = config_dir / "teach-config.yml" - - config_content = { - "course": { - "number": "STAT 545", - "title": "Regression Analysis", - "semester": "Spring 2026", - "year": 2026 - } - } - config_file.write_text(yaml.dump(config_content)) - - # Act - is_teaching, method = detect_teaching_mode(str(self.project_dir)) - - # Assert - self.assertTrue(is_teaching, "Should detect teaching mode") - self.assertEqual(method, "teach-config.yml", f"Should detect via config file, got: {method}") - - @unittest.skipIf(not DETECT_AVAILABLE, "Teaching detection module not available") - def test_02_detection_without_config(self): - """Test: Returns False when no teaching indicators present.""" - # Act - is_teaching, method = detect_teaching_mode(str(self.project_dir)) - - # Assert - self.assertFalse(is_teaching, "Should not detect teaching mode in empty project") - self.assertIsNone(method, "Method should be None when not detected") - - @unittest.skipIf(not CONFIG_AVAILABLE, "Teaching config module not available") - def test_03_config_parsing(self): - """Test: Parses teach-config.yml correctly.""" - # Arrange - config_dir = self.project_dir / ".flow" - config_dir.mkdir(parents=True) - config_file = config_dir / "teach-config.yml" - - start_date = date.today() - end_date = start_date + timedelta(days=105) # 15 weeks - - config_content = { - "course": {"number": "TEST 101", "title": "Test Course", - "semester": "Spring", "year": 2026}, - "dates": { - "start": start_date.isoformat(), - "end": end_date.isoformat() - } - } - config_file.write_text(yaml.dump(config_content)) - - # Act - config = load_teach_config(str(self.project_dir)) - - # Assert - self.assertIsNotNone(config, "Should successfully parse config") - self.assertEqual(config["course"]["number"], "TEST 101") - self.assertEqual(config["course"]["title"], "Test Course") - - def test_04_teaching_commands_exist(self): - """Test: Teaching-related site logic is preserved somewhere. - - publish.md/progress.md/build.md were KILLED (not moved) in the folio - split (Phase 3, 2026-07-12) — their unique logic was salvage-merged - into folio's site-management skill references per ADR-002, not kept - as standalone command files in either repo. This checks the salvage - landed, rather than asserting command files that no longer exist - anywhere.""" - # Arrange - folio_refs_dir = plugin_dir.parent / "folio" / "skills" / "docs" / "site-management" / "references" - - expected_refs = ["publish.md", "build.md"] - - if not folio_refs_dir.exists(): - self.skipTest("folio repo not present alongside craft") - - for ref in expected_refs: - with self.subTest(reference=ref): - # Assert - ref_path = folio_refs_dir / ref - self.assertTrue(ref_path.exists(), f"Salvaged reference {ref} should exist") - - def test_05_teaching_utilities_exist(self): - """Test: Teaching utility modules exist.""" - # Arrange - utilities are split across two directories - expected_utils = [ - ("utils", "detect_teaching_mode.py"), - ("commands/utils", "teach_config.py"), - ("commands/utils", "teaching_validation.py") - ] - - for directory, util in expected_utils: - with self.subTest(utility=util): - # Assert - util_path = plugin_dir / directory / util - self.assertTrue(util_path.exists(), f"Utility {util} should exist in {directory}") - - def test_06_config_structure_validation(self): - """Test: Config file validates required structure.""" - # Arrange: Create minimal valid config - config_dir = self.project_dir / ".flow" - config_dir.mkdir(parents=True) - config_file = config_dir / "teach-config.yml" - - # Minimal config - config_content = { - "course": { - "number": "TEST 101", - "title": "Test Course", - "semester": "Spring 2026", - "year": 2026 - }, - "dates": { - "start": "2026-01-20", - "end": "2026-05-15" - } - } - config_file.write_text(yaml.dump(config_content)) - - # Act: Parse config - content = yaml.safe_load(config_file.read_text()) - - # Assert: Required fields present - self.assertIn("course", content, "Should have course section") - self.assertIn("dates", content, "Should have dates section") - self.assertIn("number", content["course"], "Course should have number") - self.assertIn("title", content["course"], "Course should have title") - - -class TestConfigNormalization(unittest.TestCase): - """Tests for flow-cli -> craft schema normalization. - - All fixtures are hardcoded (self-contained, no dependency on stat-545 repo). - """ - - def setUp(self): - self.temp_dir = tempfile.mkdtemp() - self.project_dir = Path(self.temp_dir) - - def tearDown(self): - import shutil - shutil.rmtree(self.temp_dir, ignore_errors=True) - - @unittest.skipIf(not CONFIG_AVAILABLE, "Config module not available") - def test_flowcli_schema_loads(self): - """Flow-cli schema (semester_info) loads through full pipeline.""" - config_dir = self.project_dir / ".flow" - config_dir.mkdir(parents=True) - (config_dir / "teach-config.yml").write_text(yaml.dump({ - "course": {"name": "STAT 545", "full_name": "STAT 545 - ANOVA", - "semester": "spring", "year": 2026}, - "semester_info": { - "start_date": "2026-01-19", "end_date": "2026-05-16", - "breaks": [ - {"name": "MLK Day", "start": "2026-01-20", "end": "2026-01-20"}, - {"name": "Spring Break", "start": "2026-03-15", "end": "2026-03-22"} - ] - } - })) - config = load_teach_config(str(self.project_dir)) - self.assertIsNotNone(config) - self.assertEqual(config["course"]["number"], "STAT 545") - self.assertEqual(config["course"]["title"], "STAT 545 - ANOVA") - self.assertEqual(config["dates"]["start"], "2026-01-19") - self.assertEqual(config["course"]["semester"], "Spring") - - @unittest.skipIf(not CONFIG_AVAILABLE, "Config module not available") - def test_craft_native_unchanged(self): - """Craft-native schema (dates.*) still works as before.""" - config_dir = self.project_dir / ".flow" - config_dir.mkdir(parents=True) - (config_dir / "teach-config.yml").write_text(yaml.dump({ - "course": {"number": "TEST 101", "title": "Test Course", - "semester": "Spring", "year": 2026}, - "dates": {"start": "2026-01-20", "end": "2026-05-15"} - })) - config = load_teach_config(str(self.project_dir)) - self.assertIsNotNone(config) - self.assertEqual(config["course"]["number"], "TEST 101") - self.assertEqual(config["dates"]["start"], "2026-01-20") - - @unittest.skipIf(not CONFIG_AVAILABLE, "Config module not available") - def test_mixed_schema_merge(self): - """Both dates and semester_info present -> dates wins, gaps filled.""" - config = { - "course": {"number": "TEST 101", "title": "Test", - "semester": "Spring", "year": 2026}, - "dates": {"start": "2026-01-20"}, # has start, missing end - "semester_info": { - "start_date": "2026-01-19", # should NOT overwrite dates.start - "end_date": "2026-05-16", # should fill dates.end - } - } - result = _normalize_config(config) - self.assertEqual(result["dates"]["start"], "2026-01-20") # craft-native wins - self.assertEqual(result["dates"]["end"], "2026-05-16") # gap filled - - @unittest.skipIf(not CONFIG_AVAILABLE, "Config module not available") - def test_semester_capitalization(self): - """Lowercase 'spring' -> 'Spring'.""" - config = {"course": {"semester": "spring"}} - result = _normalize_config(config) - self.assertEqual(result["course"]["semester"], "Spring") - - @unittest.skipIf(not CONFIG_AVAILABLE, "Config module not available") - def test_name_to_number_mapping(self): - """course.name -> course.number when number absent.""" - config = {"course": {"name": "STAT 545"}} - result = _normalize_config(config) - self.assertEqual(result["course"]["number"], "STAT 545") - self.assertIn("name", result["course"]) # original key preserved - - @unittest.skipIf(not CONFIG_AVAILABLE, "Config module not available") - def test_full_name_to_title(self): - """course.full_name -> course.title when title absent.""" - config = {"course": {"full_name": "Analysis of Variance"}} - result = _normalize_config(config) - self.assertEqual(result["course"]["title"], "Analysis of Variance") - - @unittest.skipIf(not CONFIG_AVAILABLE, "Config module not available") - def test_single_day_break_valid(self): - """Single-day break (start == end) accepted by validation.""" - config_dir = self.project_dir / ".flow" - config_dir.mkdir(parents=True) - (config_dir / "teach-config.yml").write_text(yaml.dump({ - "course": {"number": "TEST 101", "title": "Test", - "semester": "Spring", "year": 2026}, - "dates": { - "start": "2026-01-19", "end": "2026-05-16", - "breaks": [{"name": "MLK Day", - "start": "2026-01-20", "end": "2026-01-20"}] - } - })) - config = load_teach_config(str(self.project_dir)) - self.assertIsNotNone(config, "Single-day break should not cause validation error") - - @unittest.skipIf(not CONFIG_AVAILABLE, "Config module not available") - def test_branches_to_deployment(self): - """branches.production -> deployment.production_branch.""" - config = {"branches": {"production": "main", "draft": "staging"}} - result = _normalize_config(config) - self.assertEqual(result["deployment"]["production_branch"], "main") - self.assertEqual(result["deployment"]["draft_branch"], "staging") - - -class TestTeachingEdgeCases(unittest.TestCase): - """Edge case tests for teaching workflow.""" - - def test_invalid_yaml_handling(self): - """Test: System handles invalid YAML gracefully.""" - # Arrange - with tempfile.TemporaryDirectory() as tmpdir: - config_dir = Path(tmpdir) / ".flow" - config_dir.mkdir() - config_file = config_dir / "teach-config.yml" - - # Write invalid YAML - config_file.write_text("invalid: yaml: syntax: error:") - - # Act & Assert: Should not crash - try: - content = yaml.safe_load(config_file.read_text()) - except yaml.YAMLError: - pass # Expected behavior - - def test_missing_required_fields(self): - """Test: Detects missing required config fields.""" - # Arrange - with tempfile.TemporaryDirectory() as tmpdir: - config_dir = Path(tmpdir) / ".flow" - config_dir.mkdir() - config_file = config_dir / "teach-config.yml" - - # Incomplete config - config_content = {"course": {"number": "TEST 101"}} # Missing title - config_file.write_text(yaml.dump(config_content)) - - # Act - content = yaml.safe_load(config_file.read_text()) - - # Assert: Can detect missing fields - self.assertNotIn("title", content["course"], "Title should be missing") - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/test_semester_progress.py b/tests/test_semester_progress.py deleted file mode 100644 index 3ba36e5a3..000000000 --- a/tests/test_semester_progress.py +++ /dev/null @@ -1,504 +0,0 @@ -""" -Unit tests for semester progress calculation. - -Tests week calculation, break handling, edge cases, and date formatting. - -Run with: - python3 -m pytest tests/test_semester_progress.py -v - python3 -m pytest tests/test_semester_progress.py::TestSemesterProgress::test_week_calculation_no_breaks -v -""" - -import sys -from pathlib import Path - -import pytest - -# Add project root to path -project_root = Path(__file__).parent.parent -sys.path.insert(0, str(project_root)) - -pytestmark = [pytest.mark.unit, pytest.mark.teaching] - -from commands.utils.semester_progress import ( - calculate_current_week, - count_break_days, - format_date_range, - get_week_boundaries, - is_on_break, -) - - -class TestSemesterProgress: - """Test semester progress calculations.""" - - def test_week_calculation_no_breaks(self): - """Test basic week calculation without breaks.""" - config = { - "dates": { - "start": "2026-01-19", - "end": "2026-05-08" - } - } - - # Week 1 (start date) - result = calculate_current_week(config, "2026-01-19") - assert result["current_week"] == 1 - assert result["total_weeks"] == 16 - assert result["on_break"] is False - assert result["break_name"] is None - assert result["percent_complete"] < 10 - - # Week 4 (3 weeks after start) - result = calculate_current_week(config, "2026-02-09") - assert result["current_week"] == 4 - assert result["on_break"] is False - - # Week 9 (March 16 = 56 days = 8 full weeks) - result = calculate_current_week(config, "2026-03-16") - assert result["current_week"] == 9 - assert 50 < result["percent_complete"] < 60 - - # Final week - result = calculate_current_week(config, "2026-05-04") - assert result["current_week"] == 16 - assert result["percent_complete"] > 90 - - def test_week_calculation_with_breaks(self): - """Test week calculation with spring break.""" - config = { - "dates": { - "start": "2026-01-19", - "end": "2026-05-08", - "breaks": [ - { - "name": "Spring Break", - "start": "2026-03-16", - "end": "2026-03-20" - } - ] - } - } - - # Before break (Mar 15 = 55 days = 7 full weeks, so week 8) - result = calculate_current_week(config, "2026-03-15") - assert result["current_week"] == 8 - assert result["on_break"] is False - - # During break (Week 9 - break days don't advance week) - result = calculate_current_week(config, "2026-03-18") - assert result["on_break"] is True - assert result["break_name"] == "Spring Break" - assert result["current_week"] == 9 - - # After break (Week 9 - 63 total days - 5 break days = 58 days = week 9) - result = calculate_current_week(config, "2026-03-23") - assert result["current_week"] == 9 - assert result["on_break"] is False - - def test_multiple_breaks(self): - """Test semester with multiple breaks.""" - config = { - "dates": { - "start": "2025-09-02", - "end": "2025-12-15", - "breaks": [ - { - "name": "Fall Break", - "start": "2025-10-12", - "end": "2025-10-13" - }, - { - "name": "Thanksgiving", - "start": "2025-11-25", - "end": "2025-11-29" - } - ] - } - } - - # Before first break - result = calculate_current_week(config, "2025-10-10") - assert result["on_break"] is False - - # During first break - result = calculate_current_week(config, "2025-10-12") - assert result["on_break"] is True - assert result["break_name"] == "Fall Break" - - # Between breaks - result = calculate_current_week(config, "2025-11-15") - assert result["on_break"] is False - - # During second break - result = calculate_current_week(config, "2025-11-27") - assert result["on_break"] is True - assert result["break_name"] == "Thanksgiving" - - # After all breaks - result = calculate_current_week(config, "2025-12-10") - assert result["on_break"] is False - - def test_on_break_detection(self): - """Test is_on_break function.""" - breaks = [ - {"name": "Spring Break", "start": "2026-03-16", "end": "2026-03-20"}, - {"name": "Reading Week", "start": "2026-04-13", "end": "2026-04-14"} - ] - - # Not on break - on_break, name = is_on_break("2026-02-10", breaks) - assert on_break is False - assert name is None - - # On Spring Break (first day) - on_break, name = is_on_break("2026-03-16", breaks) - assert on_break is True - assert name == "Spring Break" - - # On Spring Break (middle) - on_break, name = is_on_break("2026-03-18", breaks) - assert on_break is True - assert name == "Spring Break" - - # On Spring Break (last day) - on_break, name = is_on_break("2026-03-20", breaks) - assert on_break is True - assert name == "Spring Break" - - # Between breaks - on_break, name = is_on_break("2026-04-01", breaks) - assert on_break is False - assert name is None - - # On Reading Week - on_break, name = is_on_break("2026-04-13", breaks) - assert on_break is True - assert name == "Reading Week" - - def test_before_semester(self): - """Test date before semester starts.""" - config = { - "dates": { - "start": "2026-01-19", - "end": "2026-05-08" - } - } - - result = calculate_current_week(config, "2025-12-15") - assert result["current_week"] == 0 - assert result["percent_complete"] == 0.0 - assert result["on_break"] is False - assert result["days_elapsed"] == 0 - assert result["days_remaining"] > 0 - - def test_after_semester(self): - """Test date after semester ends.""" - config = { - "dates": { - "start": "2026-01-19", - "end": "2026-05-08" - } - } - - result = calculate_current_week(config, "2026-06-01") - assert result["current_week"] == result["total_weeks"] - assert result["percent_complete"] == 100.0 - assert result["on_break"] is False - assert result["days_remaining"] == 0 - - def test_summer_session_no_breaks(self): - """Test compressed summer session without breaks.""" - config = { - "dates": { - "start": "2026-06-01", - "end": "2026-07-24" - } - } - - # Week 1 - result = calculate_current_week(config, "2026-06-01") - assert result["current_week"] == 1 - assert result["total_weeks"] == 8 - - # Week 4 (21 days = 3 full weeks, so week 4) - result = calculate_current_week(config, "2026-06-22") - assert result["current_week"] == 4 - # 21 days out of 53 total = ~40% - assert 38 < result["percent_complete"] < 42 - - # Final week - result = calculate_current_week(config, "2026-07-20") - assert result["current_week"] == 8 - - def test_manual_override(self): - """Test manual week override in config.""" - config = { - "dates": { - "start": "2026-01-19", - "end": "2026-05-08" - }, - "progress": { - "current_week": 12 # Manual override - } - } - - # Should use manual week regardless of date - result = calculate_current_week(config, "2026-02-01") - assert result["current_week"] == 12 - assert result["total_weeks"] == 16 - # Percent should reflect manual week - assert 70 < result["percent_complete"] < 80 - - def test_week_boundaries(self): - """Test week boundary calculation.""" - # No breaks - start, end = get_week_boundaries(1, "2026-01-19", []) - assert start == "2026-01-19" - assert end == "2026-01-25" - - start, end = get_week_boundaries(2, "2026-01-19", []) - assert start == "2026-01-26" - assert end == "2026-02-01" - - # With break (week 9 should skip over spring break) - breaks = [{"name": "Spring Break", "start": "2026-03-16", "end": "2026-03-20"}] - start, end = get_week_boundaries(9, "2026-01-19", breaks) - # Week 9 starts after break ends - assert start > "2026-03-20" - - def test_count_break_days(self): - """Test counting break days in a range.""" - breaks = [ - {"name": "Spring Break", "start": "2026-03-16", "end": "2026-03-20"} - ] - - # Entire semester (5-day break) - days = count_break_days("2026-01-01", "2026-12-31", breaks) - assert days == 5 - - # Range containing break - days = count_break_days("2026-03-10", "2026-03-25", breaks) - assert days == 5 - - # Range before break - days = count_break_days("2026-01-01", "2026-03-01", breaks) - assert days == 0 - - # Range after break - days = count_break_days("2026-04-01", "2026-05-01", breaks) - assert days == 0 - - # Partial overlap (last 2 days of break) - days = count_break_days("2026-03-19", "2026-03-25", breaks) - assert days == 2 - - def test_date_formatting(self): - """Test ADHD-friendly date formatting.""" - # Same month - formatted = format_date_range("2026-01-27", "2026-01-30") - assert formatted == "Jan 27-30" - - # Different months - formatted = format_date_range("2026-01-27", "2026-02-02") - assert formatted == "Jan 27 - Feb 2" - - # Spring break example - formatted = format_date_range("2026-03-16", "2026-03-20") - assert formatted == "Mar 16-20" - - def test_first_week_edge_case(self): - """Test first week of semester.""" - config = { - "dates": { - "start": "2026-01-19", - "end": "2026-05-08" - } - } - - # First day - result = calculate_current_week(config, "2026-01-19") - assert result["current_week"] == 1 - assert result["week_start"] == "2026-01-19" - assert result["days_elapsed"] == 0 - - # Second day (still week 1) - result = calculate_current_week(config, "2026-01-20") - assert result["current_week"] == 1 - assert result["days_elapsed"] == 1 - - def test_last_week_edge_case(self): - """Test last week of semester.""" - config = { - "dates": { - "start": "2026-01-19", - "end": "2026-05-08" - } - } - - # Last day - result = calculate_current_week(config, "2026-05-08") - assert result["current_week"] == result["total_weeks"] - assert result["days_remaining"] == 0 - assert result["percent_complete"] == 100.0 - - # Day before last - result = calculate_current_week(config, "2026-05-07") - assert result["current_week"] == result["total_weeks"] - assert result["days_remaining"] == 1 - - def test_days_elapsed_and_remaining(self): - """Test days_elapsed and days_remaining calculations.""" - config = { - "dates": { - "start": "2026-01-19", - "end": "2026-02-09", # 3 weeks - "breaks": [ - {"name": "Break", "start": "2026-01-26", "end": "2026-01-28"} # 3 days - ] - } - } - - # Before break (7 days elapsed) - result = calculate_current_week(config, "2026-01-25") - assert result["days_elapsed"] == 6 - - # During break (7 days elapsed, frozen) - result = calculate_current_week(config, "2026-01-27") - assert result["on_break"] is True - assert result["days_elapsed"] == 7 # Doesn't count break days - - # After break - result = calculate_current_week(config, "2026-01-30") - assert result["days_elapsed"] == 8 # 7 before + 1 after (skipped 3 break days) - - def test_return_structure_completeness(self): - """Test that return dictionary has all required fields.""" - config = { - "dates": { - "start": "2026-01-19", - "end": "2026-05-08" - } - } - - result = calculate_current_week(config, "2026-02-10") - - # Check all required fields exist - required_fields = { - "current_week", - "total_weeks", - "percent_complete", - "on_break", - "break_name", - "days_elapsed", - "days_remaining", - "semester_start", - "semester_end", - "week_start", - "week_end" - } - - assert set(result.keys()) == required_fields - - # Check types - assert isinstance(result["current_week"], int) - assert isinstance(result["total_weeks"], int) - assert isinstance(result["percent_complete"], (int, float)) - assert isinstance(result["on_break"], bool) - assert result["break_name"] is None or isinstance(result["break_name"], str) - assert isinstance(result["days_elapsed"], int) - assert isinstance(result["days_remaining"], int) - assert isinstance(result["semester_start"], str) - assert isinstance(result["semester_end"], str) - assert isinstance(result["week_start"], str) - assert isinstance(result["week_end"], str) - - def test_performance(self): - """Test calculation performance (< 100ms target).""" - import time - - config = { - "dates": { - "start": "2026-01-19", - "end": "2026-05-08", - "breaks": [ - {"name": "Spring Break", "start": "2026-03-16", "end": "2026-03-20"}, - {"name": "Reading Week", "start": "2026-04-13", "end": "2026-04-14"} - ] - } - } - - # Run 100 calculations - start_time = time.time() - for _ in range(100): - calculate_current_week(config, "2026-03-10") - elapsed = time.time() - start_time - - # Should complete 100 calculations in < 100ms (avg < 1ms each) - assert elapsed < 0.1, f"Performance regression: {elapsed*10:.2f}ms per call" - - def test_break_on_semester_boundary(self): - """Test break that starts/ends on semester boundaries.""" - config = { - "dates": { - "start": "2026-01-19", - "end": "2026-03-01", - "breaks": [ - # Break starts on semester start - {"name": "Orientation", "start": "2026-01-19", "end": "2026-01-20"} - ] - } - } - - result = calculate_current_week(config, "2026-01-19") - assert result["on_break"] is True - assert result["break_name"] == "Orientation" - - def test_auto_vs_manual_override(self): - """Test that manual override takes precedence over auto calculation.""" - config_auto = { - "dates": { - "start": "2026-01-19", - "end": "2026-05-08" - }, - "progress": { - "current_week": "auto" - } - } - - config_manual = { - "dates": { - "start": "2026-01-19", - "end": "2026-05-08" - }, - "progress": { - "current_week": 15 # Manual override - } - } - - # Auto should calculate based on date - result_auto = calculate_current_week(config_auto, "2026-02-10") - assert result_auto["current_week"] == 4 - - # Manual should use override - result_manual = calculate_current_week(config_manual, "2026-02-10") - assert result_manual["current_week"] == 15 - - def test_default_current_date(self): - """Test using default current date (today).""" - config = { - "dates": { - "start": "2020-01-01", # Past date - "end": "2030-12-31" # Future date - } - } - - # Should use today's date when current_date is None - result = calculate_current_week(config) - assert result["current_week"] > 0 - assert isinstance(result["current_week"], int) - assert isinstance(result["percent_complete"], (int, float)) - - -if __name__ == "__main__": - import pytest - sys.exit(pytest.main([__file__, "-v"])) diff --git a/tests/test_site_publish.py b/tests/test_site_publish.py deleted file mode 100644 index f7e1cff4b..000000000 --- a/tests/test_site_publish.py +++ /dev/null @@ -1,463 +0,0 @@ -#!/usr/bin/env python3 -""" -Unit tests for /craft:site:publish command. - -Tests the 5-step teaching workflow: -1. Validate draft branch -2. Preview changes -3. Confirm publish -4. Execute publish (with rollback) -5. Cleanup - -Mock git operations to avoid actual repository changes. -""" - -import os -import sys -import tempfile -import unittest -from pathlib import Path -from unittest.mock import Mock, patch, MagicMock, call - -import pytest - -# Add parent directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from utils.detect_teaching_mode import detect_teaching_mode -from commands.utils.teaching_validation import ( - ValidationResult, - validate_teaching_content, -) - -pytestmark = [pytest.mark.integration, pytest.mark.site] - - -class TestSitePublishValidation(unittest.TestCase): - """Test validation step of publish workflow.""" - - def setUp(self): - """Create temporary teaching project.""" - self.test_dir = tempfile.mkdtemp() - - # Create teaching project structure - flow_dir = Path(self.test_dir) / ".flow" - flow_dir.mkdir() - config_file = flow_dir / "teach-config.yml" - config_file.write_text(""" -course: - code: "STAT 440" - name: "Regression Analysis" - -branches: - draft: "draft" - production: "production" - -deployment: - gh_pages_url: "https://example.com/course/" - verify_deployment: true - deployment_timeout: 300 -""") - - def tearDown(self): - """Clean up temporary directory.""" - import shutil - shutil.rmtree(self.test_dir) - - def test_teaching_mode_detected(self): - """Should detect teaching mode from config.""" - is_teaching, method = detect_teaching_mode(self.test_dir) - - self.assertTrue(is_teaching) - self.assertEqual(method, "config") - - def test_validation_called_in_teaching_mode(self): - """Should call validation when teaching mode detected.""" - # Create complete course for validation - syllabus_content = """ -# Syllabus -## Objectives -Learn stats. -## Policies -Be nice. -## Grading -Homework 100%. -## Schedule -See schedule. -""" - (Path(self.test_dir) / 'syllabus.qmd').write_text(syllabus_content) - - schedule_content = """ -## Week 1 -Good content here with sufficient length. -""" - (Path(self.test_dir) / 'schedule.qmd').write_text(schedule_content) - - result = validate_teaching_content(self.test_dir) - - self.assertTrue(result.can_publish()) - self.assertEqual(len(result.errors), 0) - - def test_validation_errors_block_publish(self): - """Should block publish when validation errors exist.""" - # Create incomplete course - syllabus_content = """ -# Syllabus -## Grading -Homework only. -""" - (Path(self.test_dir) / 'syllabus.qmd').write_text(syllabus_content) - - schedule_content = """ -## Week 1 -Content. - -## Week 2 - -## Week 3 -More content. -""" - (Path(self.test_dir) / 'schedule.qmd').write_text(schedule_content) - - result = validate_teaching_content(self.test_dir) - - self.assertFalse(result.can_publish()) - self.assertGreater(len(result.errors), 0) - - def test_validation_warnings_allow_publish(self): - """Should allow publish when only warnings exist.""" - # Create complete syllabus and schedule - syllabus_content = """ -# Syllabus -## Objectives -Learn. -## Policies -Rules. -## Grading -Grades. -## Schedule -Times. -""" - (Path(self.test_dir) / 'syllabus.qmd').write_text(syllabus_content) - - # Schedule references assignments but files don't exist - schedule_content = """ -## Week 1 -Complete HW 1. Sufficient content here. - -## Week 2 -Complete HW 2. More content here. -""" - (Path(self.test_dir) / 'schedule.qmd').write_text(schedule_content) - - result = validate_teaching_content(self.test_dir) - - # Should be publishable (warnings don't block) - self.assertTrue(result.can_publish()) - # But should have warnings about missing assignments - self.assertGreater(len(result.warnings), 0) - - -class TestSitePublishPreview(unittest.TestCase): - """Test preview changes step of publish workflow.""" - - @patch('subprocess.run') - def test_git_diff_stat_called(self, mock_run): - """Should call git diff --stat for preview.""" - mock_run.return_value = Mock( - returncode=0, - stdout=""" - syllabus/index.qmd | 18 +++++++++------- - schedule.qmd | 50 +++++++++++++++++++++++++++++++++++++++++ - lectures/week-01.qmd | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - 3 files changed, 180 insertions(+), 8 deletions(-) -""", - stderr="" - ) - - # Would call: git diff production..draft --stat - # Verify output can be parsed - result = mock_run.return_value.stdout - - self.assertIn("syllabus/index.qmd", result) - self.assertIn("schedule.qmd", result) - self.assertIn("lectures/week-01.qmd", result) - - def test_categorize_changed_files(self): - """Should categorize files into critical/content/other.""" - files = [ - "syllabus/index.qmd", - "syllabus.qmd", - "schedule.qmd", - "assignments/hw-1.qmd", - "lectures/week-01.qmd", - "readings/chapter-1.qmd", - "resources/datasets.qmd", - "_quarto.yml", - ".gitignore", - ] - - critical = [] - content = [] - other = [] - - for file in files: - if any(pattern in file for pattern in ["syllabus", "schedule", "assignments/"]): - critical.append(file) - elif any(pattern in file for pattern in ["lectures/", "readings/", "resources/"]): - content.append(file) - else: - other.append(file) - - self.assertEqual(len(critical), 4) # syllabus/index, syllabus, schedule, assignments/hw-1 - self.assertEqual(len(content), 3) # lectures, readings, resources - self.assertEqual(len(other), 2) # _quarto.yml, .gitignore - - -class TestSitePublishConfirmation(unittest.TestCase): - """Test confirmation step of publish workflow.""" - - def test_confirmation_options(self): - """Should present 3 options: Yes, Preview diff, Cancel.""" - options = [ - "Yes - Merge and deploy (Recommended)", - "Preview full diff first", - "Cancel" - ] - - self.assertEqual(len(options), 3) - self.assertIn("Yes", options[0]) - self.assertIn("Preview", options[1]) - self.assertIn("Cancel", options[2]) - - -class TestSitePublishExecution(unittest.TestCase): - """Test execution step with git operations.""" - - @patch('subprocess.run') - @patch('datetime.datetime') - def test_backup_branch_created(self, mock_datetime, mock_run): - """Should create backup branch with timestamp.""" - mock_datetime.now.return_value.strftime.return_value = "20260116-143022" - mock_run.return_value = Mock(returncode=0, stdout="", stderr="") - - # Simulate: git branch production-backup-20260116-143022 - backup_name = f"production-backup-{mock_datetime.now().strftime('%Y%m%d-%H%M%S')}" - - self.assertEqual(backup_name, "production-backup-20260116-143022") - - @patch('subprocess.run') - def test_fast_forward_merge(self, mock_run): - """Should attempt fast-forward merge.""" - mock_run.return_value = Mock(returncode=0, stdout="", stderr="") - - # Would call: git merge draft --ff-only - # Verify command structure - expected_args = ["git", "merge", "draft", "--ff-only"] - - self.assertEqual(len(expected_args), 4) - self.assertIn("--ff-only", expected_args) - - @patch('subprocess.run') - def test_merge_conflict_rollback(self, mock_run): - """Should rollback on merge conflict.""" - # Simulate merge failure - mock_run.side_effect = [ - Mock(returncode=0, stdout="", stderr=""), # backup creation - Mock(returncode=0, stdout="", stderr=""), # checkout production - Mock(returncode=1, stdout="", stderr="CONFLICT"), # merge fails - Mock(returncode=0, stdout="", stderr=""), # rollback - ] - - # On conflict, should call: git reset --hard production-backup- - rollback_command = ["git", "reset", "--hard", "production-backup-20260116-143022"] - - self.assertIn("--hard", rollback_command) - self.assertIn("production-backup", rollback_command[3]) - - @patch('subprocess.run') - def test_push_to_remote(self, mock_run): - """Should push production branch to remote.""" - mock_run.return_value = Mock(returncode=0, stdout="", stderr="") - - # Would call: git push origin production - push_command = ["git", "push", "origin", "production"] - - self.assertEqual(len(push_command), 4) - self.assertEqual(push_command[3], "production") - - @patch('subprocess.run') - def test_push_failure_rollback(self, mock_run): - """Should rollback on push failure.""" - # Simulate push failure - side_effects = [ - Mock(returncode=0, stdout="", stderr=""), # backup - Mock(returncode=0, stdout="", stderr=""), # checkout - Mock(returncode=0, stdout="", stderr=""), # merge success - Mock(returncode=1, stdout="", stderr="Permission denied"), # push fails - Mock(returncode=0, stdout="", stderr=""), # rollback - ] - mock_run.side_effect = side_effects - - # Should preserve backup branch and rollback - # Verify rollback logic is triggered - self.assertEqual(len(side_effects), 5) - - @patch('requests.get') - def test_deployment_verification(self, mock_get): - """Should verify deployment by checking URL.""" - mock_get.return_value = Mock(status_code=200) - - # Would call: requests.get("https://example.com/course/") - url = "https://example.com/course/" - response = mock_get(url) - - self.assertEqual(response.status_code, 200) - - @patch('requests.get') - def test_deployment_verification_timeout(self, mock_get): - """Should handle deployment verification timeout gracefully.""" - import requests - mock_get.side_effect = requests.Timeout() - - # Should warn but not rollback - try: - response = mock_get("https://example.com/course/", timeout=300) - self.fail("Should have raised Timeout") - except requests.Timeout: - # Expected - should warn user but continue - pass - - @patch('requests.get') - def test_deployment_verification_404(self, mock_get): - """Should warn on 404 but not rollback.""" - mock_get.return_value = Mock(status_code=404) - - url = "https://example.com/course/" - response = mock_get(url) - - # Should warn but not rollback (may take time to propagate) - self.assertEqual(response.status_code, 404) - - -class TestSitePublishCleanup(unittest.TestCase): - """Test cleanup step of publish workflow.""" - - @patch('subprocess.run') - def test_return_to_original_branch(self, mock_run): - """Should checkout original branch after publish.""" - mock_run.return_value = Mock(returncode=0, stdout="", stderr="") - - # Would call: git checkout draft - checkout_command = ["git", "checkout", "draft"] - - self.assertEqual(len(checkout_command), 3) - self.assertEqual(checkout_command[2], "draft") - - def test_success_message_format(self): - """Should show ADHD-friendly success message.""" - message_parts = [ - "✅ PUBLISH SUCCESSFUL", - "🌐 Live Site:", - "📊 Changes Published:", - "💡 Next Steps:", - ] - - # Verify all key components present - for part in message_parts: - self.assertIsNotNone(part) - - -class TestSitePublishNonTeaching(unittest.TestCase): - """Test publish workflow for non-teaching projects.""" - - def setUp(self): - """Create temporary non-teaching project.""" - self.test_dir = tempfile.mkdtemp() - - def tearDown(self): - """Clean up temporary directory.""" - import shutil - shutil.rmtree(self.test_dir) - - def test_non_teaching_mode_detected(self): - """Should detect non-teaching mode.""" - is_teaching, method = detect_teaching_mode(self.test_dir) - - self.assertFalse(is_teaching) - self.assertIsNone(method) - - def test_validation_skipped_for_non_teaching(self): - """Should skip validation for non-teaching projects.""" - is_teaching, _ = detect_teaching_mode(self.test_dir) - - if not is_teaching: - # Validation should be skipped - self.assertFalse(is_teaching) - - -class TestSitePublishIntegration(unittest.TestCase): - """Integration tests for full publish workflow.""" - - def setUp(self): - """Create temporary teaching project.""" - self.test_dir = tempfile.mkdtemp() - - # Create complete teaching project - flow_dir = Path(self.test_dir) / ".flow" - flow_dir.mkdir() - config_file = flow_dir / "teach-config.yml" - config_file.write_text(""" -course: - code: "STAT 440" -branches: - draft: "draft" - production: "production" -deployment: - gh_pages_url: "https://example.com/course/" -""") - - # Complete syllabus - syllabus_content = """ -# Syllabus -## Objectives -Learn. -## Policies -Rules. -## Grading -Grades. -## Schedule -Times. -""" - (Path(self.test_dir) / 'syllabus.qmd').write_text(syllabus_content) - - # Complete schedule - schedule_content = """ -## Week 1 -Content here that is long enough to pass validation checks. -""" - (Path(self.test_dir) / 'schedule.qmd').write_text(schedule_content) - - def tearDown(self): - """Clean up temporary directory.""" - import shutil - shutil.rmtree(self.test_dir) - - def test_full_workflow_success(self): - """Should complete all 5 steps successfully.""" - # Step 1: Detect teaching mode - is_teaching, method = detect_teaching_mode(self.test_dir) - self.assertTrue(is_teaching) - - # Step 2: Validate content - validation = validate_teaching_content(self.test_dir) - self.assertTrue(validation.can_publish()) - - # Step 3-5 would require git operations (mocked in other tests) - # Verify workflow components are present - self.assertTrue(is_teaching) - self.assertTrue(validation.can_publish()) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_teach_config.py b/tests/test_teach_config.py deleted file mode 100644 index be7ae3a29..000000000 --- a/tests/test_teach_config.py +++ /dev/null @@ -1,810 +0,0 @@ -#!/usr/bin/env python3 -""" -Unit tests for teaching configuration parser. - -Tests cover: -- Valid configurations (complete and minimal) -- Invalid YAML syntax -- Missing required fields -- Date validation (format, logical order) -- Break validation (dates, overlaps, semester bounds) -- Config path detection (.flow/ vs root) -- Default value application -- Edge cases and error handling - -Run: - python3 -m pytest tests/test_teach_config.py -v - python3 tests/test_teach_config.py # Direct execution -""" - -import os -import sys -import tempfile -import unittest -from datetime import datetime -from pathlib import Path - -import pytest - -# Add parent directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - -pytestmark = [pytest.mark.unit, pytest.mark.teaching] - -from commands.utils.teach_config import ( - get_config_path, - validate_date, - parse_date, - validate_breaks, - apply_defaults, - validate_config, - load_teach_config, - DEFAULTS, - VALID_SEMESTERS, -) - - -class TestValidateDate(unittest.TestCase): - """Test date validation function""" - - def test_valid_dates(self): - """Valid YYYY-MM-DD dates should pass""" - valid_dates = [ - "2026-01-19", - "2025-12-31", - "2000-01-01", - "2099-12-31", - ] - for date_str in valid_dates: - with self.subTest(date=date_str): - self.assertTrue(validate_date(date_str)) - - def test_invalid_format(self): - """Invalid date formats should fail""" - invalid_dates = [ - "2026/01/19", # Wrong separator - "01-19-2026", # Wrong order - "2026-1-19", # Missing zero padding - "2026-13-01", # Invalid month - "2026-01-32", # Invalid day - "not-a-date", # Text - "", # Empty - None, # None type - ] - for date_str in invalid_dates: - with self.subTest(date=date_str): - self.assertFalse(validate_date(date_str)) - - -class TestParseDate(unittest.TestCase): - """Test date parsing function""" - - def test_parse_valid_date(self): - """Valid dates should parse to datetime objects""" - result = parse_date("2026-01-19") - self.assertIsInstance(result, datetime) - self.assertEqual(result.year, 2026) - self.assertEqual(result.month, 1) - self.assertEqual(result.day, 19) - - def test_parse_invalid_date(self): - """Invalid dates should return None""" - self.assertIsNone(parse_date("invalid")) - self.assertIsNone(parse_date("2026/01/19")) - self.assertIsNone(parse_date(None)) - - -class TestValidateBreaks(unittest.TestCase): - """Test break validation function""" - - def test_empty_breaks(self): - """Empty breaks list should be valid""" - errors = validate_breaks([], "2026-01-19", "2026-05-08") - self.assertEqual(errors, []) - - def test_valid_break(self): - """Valid break should pass validation""" - breaks = [ - { - "name": "Spring Break", - "start": "2026-03-16", - "end": "2026-03-20", - } - ] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - self.assertEqual(errors, []) - - def test_multiple_valid_breaks(self): - """Multiple non-overlapping breaks should be valid""" - breaks = [ - { - "name": "Spring Break", - "start": "2026-03-16", - "end": "2026-03-20", - }, - { - "name": "Reading Week", - "start": "2026-04-13", - "end": "2026-04-14", - }, - ] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - self.assertEqual(errors, []) - - def test_missing_name(self): - """Break missing name should fail""" - breaks = [{"start": "2026-03-16", "end": "2026-03-20"}] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - self.assertTrue(any("missing 'name'" in e for e in errors)) - - def test_missing_start(self): - """Break missing start should fail""" - breaks = [{"name": "Spring Break", "end": "2026-03-20"}] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - self.assertTrue(any("missing 'start'" in e for e in errors)) - - def test_missing_end(self): - """Break missing end should fail""" - breaks = [{"name": "Spring Break", "start": "2026-03-16"}] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - self.assertTrue(any("missing 'end'" in e for e in errors)) - - def test_invalid_date_format(self): - """Break with invalid date format should fail""" - breaks = [ - { - "name": "Spring Break", - "start": "2026/03/16", - "end": "2026-03-20", - } - ] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - self.assertTrue(any("invalid start date format" in e for e in errors)) - - def test_end_before_start(self): - """Break with end before start should fail""" - breaks = [ - { - "name": "Spring Break", - "start": "2026-03-20", - "end": "2026-03-16", - } - ] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - self.assertTrue(any("start date must be before or equal to end date" in e for e in errors)) - - def test_break_before_semester(self): - """Break starting before semester should fail""" - breaks = [ - { - "name": "Winter Break", - "start": "2026-01-01", - "end": "2026-01-18", - } - ] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - self.assertTrue(any("starts before semester begins" in e for e in errors)) - - def test_break_after_semester(self): - """Break ending after semester should fail""" - breaks = [ - { - "name": "Summer Break", - "start": "2026-05-07", - "end": "2026-05-15", - } - ] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - self.assertTrue(any("ends after semester ends" in e for e in errors)) - - def test_overlapping_breaks(self): - """Overlapping breaks should fail""" - breaks = [ - { - "name": "Break 1", - "start": "2026-03-16", - "end": "2026-03-20", - }, - { - "name": "Break 2", - "start": "2026-03-19", # Overlaps with Break 1 - "end": "2026-03-23", - }, - ] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - self.assertTrue(any("overlap" in e for e in errors)) - - def test_invalid_semester_dates(self): - """Invalid semester dates should fail gracefully""" - breaks = [ - { - "name": "Spring Break", - "start": "2026-03-16", - "end": "2026-03-20", - } - ] - errors = validate_breaks(breaks, "invalid", "2026-05-08") - self.assertTrue(any("invalid semester dates" in e for e in errors)) - - -class TestApplyDefaults(unittest.TestCase): - """Test default value application""" - - def test_apply_all_defaults(self): - """Empty config should get all defaults""" - config = {"course": {}, "dates": {}} - result = apply_defaults(config) - - # Check deployment defaults - self.assertIn("deployment", result) - self.assertEqual(result["deployment"]["production_branch"], "production") - self.assertEqual(result["deployment"]["draft_branch"], "draft") - - # Check progress defaults - self.assertIn("progress", result) - self.assertEqual(result["progress"]["current_week"], "auto") - - # Check validation defaults - self.assertIn("validation", result) - self.assertEqual(result["validation"]["strict_mode"], True) - self.assertIn("grading", result["validation"]["required_sections"]) - - # Check dates defaults - self.assertIn("breaks", result["dates"]) - self.assertEqual(result["dates"]["breaks"], []) - - def test_preserve_existing_values(self): - """Existing values should not be overwritten""" - config = { - "course": {}, - "dates": {"breaks": [{"name": "test"}]}, - "deployment": {"production_branch": "prod"}, - "progress": {"current_week": 5}, - "validation": {"strict_mode": False}, - } - result = apply_defaults(config) - - self.assertEqual(result["deployment"]["production_branch"], "prod") - self.assertEqual(result["progress"]["current_week"], 5) - self.assertEqual(result["validation"]["strict_mode"], False) - self.assertEqual(len(result["dates"]["breaks"]), 1) - - -class TestValidateConfig(unittest.TestCase): - """Test configuration validation""" - - def get_minimal_valid_config(self): - """Return minimal valid configuration""" - return { - "course": { - "number": "STAT 545", - "title": "Regression Analysis", - "semester": "Spring", - "year": 2026, - }, - "dates": { - "start": "2026-01-19", - "end": "2026-05-08", - "breaks": [], - }, - } - - def test_valid_minimal_config(self): - """Minimal valid config should pass""" - config = self.get_minimal_valid_config() - errors = validate_config(config) - self.assertEqual(errors, []) - - def test_missing_course_section(self): - """Missing course section should fail""" - config = {"dates": {"start": "2026-01-19", "end": "2026-05-08"}} - errors = validate_config(config) - self.assertTrue(any("Missing required section: 'course'" in e for e in errors)) - - def test_missing_dates_section(self): - """Missing dates section should fail""" - config = { - "course": { - "number": "STAT 545", - "title": "Test", - "semester": "Spring", - "year": 2026, - } - } - errors = validate_config(config) - self.assertTrue(any("Missing required section: 'dates'" in e for e in errors)) - - def test_missing_course_fields(self): - """Missing required course fields should fail""" - config = self.get_minimal_valid_config() - del config["course"]["number"] - errors = validate_config(config) - self.assertTrue(any("course.number" in e for e in errors)) - - def test_invalid_semester(self): - """Invalid semester value should fail""" - config = self.get_minimal_valid_config() - config["course"]["semester"] = "Invalid" - errors = validate_config(config) - self.assertTrue(any("Invalid semester" in e for e in errors)) - - def test_invalid_year_type(self): - """Non-integer year should fail""" - config = self.get_minimal_valid_config() - config["course"]["year"] = "2026" - errors = validate_config(config) - self.assertTrue(any("Invalid year" in e for e in errors)) - - def test_invalid_year_range(self): - """Year out of range should fail""" - config = self.get_minimal_valid_config() - config["course"]["year"] = 1999 - errors = validate_config(config) - self.assertTrue(any("must be between 2000 and 2100" in e for e in errors)) - - def test_invalid_start_date(self): - """Invalid start date format should fail""" - config = self.get_minimal_valid_config() - config["dates"]["start"] = "2026/01/19" - errors = validate_config(config) - self.assertTrue(any("Invalid start date format" in e for e in errors)) - - def test_end_before_start(self): - """End date before start should fail""" - config = self.get_minimal_valid_config() - config["dates"]["start"] = "2026-05-08" - config["dates"]["end"] = "2026-01-19" - errors = validate_config(config) - self.assertTrue(any("end date must be after start date" in e for e in errors)) - - def test_invalid_current_week_type(self): - """Invalid current_week type should fail""" - config = self.get_minimal_valid_config() - config["progress"] = {"current_week": "five"} - errors = validate_config(config) - self.assertTrue(any("Invalid current_week" in e for e in errors)) - - def test_invalid_current_week_range(self): - """current_week out of range should fail""" - config = self.get_minimal_valid_config() - config["progress"] = {"current_week": 100} - errors = validate_config(config) - self.assertTrue(any("must be between 1 and 52" in e for e in errors)) - - def test_invalid_strict_mode_type(self): - """Non-boolean strict_mode should fail""" - config = self.get_minimal_valid_config() - config["validation"] = {"strict_mode": "yes"} - errors = validate_config(config) - self.assertTrue(any("Invalid strict_mode" in e for e in errors)) - - -class TestGetConfigPath(unittest.TestCase): - """Test config file path detection""" - - def test_flow_config_priority(self): - """Should prefer .flow/teach-config.yml over root""" - with tempfile.TemporaryDirectory() as tmpdir: - # Create both files - flow_dir = Path(tmpdir) / ".flow" - flow_dir.mkdir() - flow_config = flow_dir / "teach-config.yml" - flow_config.write_text("test: flow") - - root_config = Path(tmpdir) / "teach-config.yml" - root_config.write_text("test: root") - - # Should return .flow version (resolve both paths for comparison) - result = get_config_path(tmpdir) - self.assertEqual(Path(result).resolve(), flow_config.resolve()) - - def test_root_config_fallback(self): - """Should use root config if .flow doesn't exist""" - with tempfile.TemporaryDirectory() as tmpdir: - root_config = Path(tmpdir) / "teach-config.yml" - root_config.write_text("test: root") - - result = get_config_path(tmpdir) - self.assertEqual(Path(result).resolve(), root_config.resolve()) - - def test_no_config_found(self): - """Should return None if no config exists""" - with tempfile.TemporaryDirectory() as tmpdir: - result = get_config_path(tmpdir) - self.assertIsNone(result) - - -class TestLoadTeachConfig(unittest.TestCase): - """Test full config loading and validation""" - - def test_load_complete_valid_config(self): - """Complete valid config should load successfully""" - config_yaml = """ -course: - number: "STAT 545" - title: "Regression Analysis" - semester: "Spring" - year: 2026 - -dates: - start: "2026-01-19" - end: "2026-05-08" - breaks: - - name: "Spring Break" - start: "2026-03-16" - end: "2026-03-20" - -instructor: - name: "Dr. Jane Smith" - email: "jsmith@university.edu" - office_hours: "Tu/Th 2-3pm" - -deployment: - production_branch: "production" - draft_branch: "draft" - gh_pages_url: "https://example.com" - -progress: - current_week: auto - -validation: - required_sections: - - grading - - policies - strict_mode: true -""" - with tempfile.TemporaryDirectory() as tmpdir: - config_path = Path(tmpdir) / ".flow" / "teach-config.yml" - config_path.parent.mkdir() - config_path.write_text(config_yaml) - - config = load_teach_config(tmpdir) - self.assertIsNotNone(config) - self.assertEqual(config["course"]["number"], "STAT 545") - self.assertEqual(config["dates"]["start"], "2026-01-19") - self.assertEqual(len(config["dates"]["breaks"]), 1) - - def test_load_minimal_config_with_defaults(self): - """Minimal config should load with defaults applied""" - config_yaml = """ -course: - number: "STAT 545" - title: "Regression Analysis" - semester: "Spring" - year: 2026 - -dates: - start: "2026-01-19" - end: "2026-05-08" -""" - with tempfile.TemporaryDirectory() as tmpdir: - config_path = Path(tmpdir) / "teach-config.yml" - config_path.write_text(config_yaml) - - config = load_teach_config(tmpdir) - self.assertIsNotNone(config) - - # Check defaults applied - self.assertEqual(config["deployment"]["production_branch"], "production") - self.assertEqual(config["progress"]["current_week"], "auto") - self.assertTrue(config["validation"]["strict_mode"]) - self.assertEqual(config["dates"]["breaks"], []) - - def test_no_config_returns_none(self): - """Missing config should return None (not an error)""" - with tempfile.TemporaryDirectory() as tmpdir: - config = load_teach_config(tmpdir) - self.assertIsNone(config) - - def test_malformed_yaml_returns_none(self): - """Malformed YAML should return None with warning""" - config_yaml = """ -course: - number: "STAT 545" - - invalid yaml structure -""" - with tempfile.TemporaryDirectory() as tmpdir: - config_path = Path(tmpdir) / "teach-config.yml" - config_path.write_text(config_yaml) - - config = load_teach_config(tmpdir) - self.assertIsNone(config) - - def test_invalid_config_raises_error(self): - """Invalid config should raise ValueError""" - config_yaml = """ -course: - number: "STAT 545" - # Missing required fields -dates: - start: "2026-01-19" - end: "2026-05-08" -""" - with tempfile.TemporaryDirectory() as tmpdir: - config_path = Path(tmpdir) / "teach-config.yml" - config_path.write_text(config_yaml) - - with self.assertRaises(ValueError) as cm: - load_teach_config(tmpdir) - - self.assertIn("validation failed", str(cm.exception)) - - def test_non_dict_config_returns_none(self): - """Non-dictionary YAML should return None""" - config_yaml = "just a string" - - with tempfile.TemporaryDirectory() as tmpdir: - config_path = Path(tmpdir) / "teach-config.yml" - config_path.write_text(config_yaml) - - config = load_teach_config(tmpdir) - self.assertIsNone(config) - - def test_invalid_breaks_raises_error(self): - """Config with invalid breaks should raise ValueError""" - config_yaml = """ -course: - number: "STAT 545" - title: "Test" - semester: "Spring" - year: 2026 - -dates: - start: "2026-01-19" - end: "2026-05-08" - breaks: - - name: "Spring Break" - start: "2026-03-20" - end: "2026-03-16" # End before start -""" - with tempfile.TemporaryDirectory() as tmpdir: - config_path = Path(tmpdir) / "teach-config.yml" - config_path.write_text(config_yaml) - - with self.assertRaises(ValueError) as cm: - load_teach_config(tmpdir) - - self.assertIn("validation failed", str(cm.exception)) - - -class TestEdgeCases(unittest.TestCase): - """Test edge cases and error conditions""" - - def test_leap_year_date(self): - """Leap year dates should be valid""" - self.assertTrue(validate_date("2024-02-29")) - - def test_non_leap_year_invalid(self): - """Non-leap year Feb 29 should be invalid""" - self.assertFalse(validate_date("2023-02-29")) - - def test_all_valid_semesters(self): - """All valid semester values should pass""" - for semester in VALID_SEMESTERS: - config = { - "course": { - "number": "TEST", - "title": "Test", - "semester": semester, - "year": 2026, - }, - "dates": { - "start": "2026-01-19", - "end": "2026-05-08", - }, - } - errors = validate_config(config) - self.assertEqual(errors, []) - - def test_current_week_auto_string(self): - """current_week: auto should be valid""" - config = { - "course": { - "number": "TEST", - "title": "Test", - "semester": "Spring", - "year": 2026, - }, - "dates": { - "start": "2026-01-19", - "end": "2026-05-08", - }, - "progress": { - "current_week": "auto", - }, - } - errors = validate_config(config) - self.assertEqual(errors, []) - - def test_current_week_integer(self): - """current_week as integer should be valid""" - config = { - "course": { - "number": "TEST", - "title": "Test", - "semester": "Spring", - "year": 2026, - }, - "dates": { - "start": "2026-01-19", - "end": "2026-05-08", - }, - "progress": { - "current_week": 8, - }, - } - errors = validate_config(config) - self.assertEqual(errors, []) - - def test_file_read_error(self): - """File read errors should return None with warning""" - with tempfile.TemporaryDirectory() as tmpdir: - # Create a directory instead of a file - config_path = Path(tmpdir) / "teach-config.yml" - config_path.mkdir() # This will fail to read as YAML - - config = load_teach_config(tmpdir) - self.assertIsNone(config) - - def test_validation_required_sections_optional(self): - """validation.required_sections should be optional""" - config_yaml = """ -course: - number: "STAT 545" - title: "Test" - semester: "Spring" - year: 2026 - -dates: - start: "2026-01-19" - end: "2026-05-08" - -validation: - strict_mode: false -""" - with tempfile.TemporaryDirectory() as tmpdir: - config_path = Path(tmpdir) / "teach-config.yml" - config_path.write_text(config_yaml) - - config = load_teach_config(tmpdir) - self.assertIsNotNone(config) - # Should have default required_sections - self.assertIn("required_sections", config["validation"]) - - def test_instructor_section_optional(self): - """Instructor section is completely optional""" - config = { - "course": { - "number": "TEST", - "title": "Test", - "semester": "Spring", - "year": 2026, - }, - "dates": { - "start": "2026-01-19", - "end": "2026-05-08", - }, - } - errors = validate_config(config) - self.assertEqual(errors, []) - - def test_deployment_gh_pages_url_optional(self): - """deployment.gh_pages_url is optional""" - config_yaml = """ -course: - number: "STAT 545" - title: "Test" - semester: "Spring" - year: 2026 - -dates: - start: "2026-01-19" - end: "2026-05-08" - -deployment: - production_branch: "prod" - draft_branch: "draft" -""" - with tempfile.TemporaryDirectory() as tmpdir: - config_path = Path(tmpdir) / "teach-config.yml" - config_path.write_text(config_yaml) - - config = load_teach_config(tmpdir) - self.assertIsNotNone(config) - self.assertEqual(config["deployment"]["production_branch"], "prod") - self.assertNotIn("gh_pages_url", config["deployment"]) - - def test_breaks_validation_with_multiple_errors(self): - """Break validation should collect all errors""" - breaks = [ - { - "name": "Bad Break 1", - "start": "2026-03-20", - "end": "2026-03-16", # End before start - }, - { - "name": "Bad Break 2", - "start": "2026-01-01", # Before semester - "end": "2026-01-05", - }, - ] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - # Should have multiple errors - self.assertGreater(len(errors), 1) - - def test_breaks_not_a_dict(self): - """Break that's not a dictionary should fail""" - breaks = ["not a dict"] - errors = validate_breaks(breaks, "2026-01-19", "2026-05-08") - self.assertTrue(any("must be a dictionary" in e for e in errors)) - - def test_config_without_instructor_section(self): - """Config without instructor section should work""" - config_yaml = """ -course: - number: "STAT 545" - title: "Test" - semester: "Spring" - year: 2026 - -dates: - start: "2026-01-19" - end: "2026-05-08" -""" - with tempfile.TemporaryDirectory() as tmpdir: - config_path = Path(tmpdir) / "teach-config.yml" - config_path.write_text(config_yaml) - - config = load_teach_config(tmpdir) - self.assertIsNotNone(config) - self.assertNotIn("instructor", config) - - def test_validation_section_partial(self): - """Partial validation section should merge with defaults""" - config = { - "course": { - "number": "TEST", - "title": "Test", - "semester": "Spring", - "year": 2026, - }, - "dates": { - "start": "2026-01-19", - "end": "2026-05-08", - }, - "validation": { - "strict_mode": False, - # required_sections should get default - }, - } - result = apply_defaults(config) - self.assertIn("required_sections", result["validation"]) - self.assertEqual(result["validation"]["strict_mode"], False) - - def test_progress_section_partial(self): - """Partial progress section should merge with defaults""" - config = { - "course": {"number": "TEST"}, - "dates": {"start": "2026-01-19"}, - "progress": {}, # Empty progress section - } - result = apply_defaults(config) - self.assertEqual(result["progress"]["current_week"], "auto") - - def test_deployment_section_partial(self): - """Partial deployment section should merge with defaults""" - config = { - "course": {"number": "TEST"}, - "dates": {"start": "2026-01-19"}, - "deployment": { - "production_branch": "custom-prod", - # draft_branch should get default - }, - } - result = apply_defaults(config) - self.assertEqual(result["deployment"]["production_branch"], "custom-prod") - self.assertEqual(result["deployment"]["draft_branch"], "draft") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_teaching_documentation.py b/tests/test_teaching_documentation.py deleted file mode 100644 index 79c7b16f5..000000000 --- a/tests/test_teaching_documentation.py +++ /dev/null @@ -1,288 +0,0 @@ -#!/usr/bin/env python3 -""" -Test suite for teaching workflow documentation. - -Validates that all teaching workflow documentation is complete, accurate, -and properly integrated into the documentation site. -""" - -import os -import re -from pathlib import Path - -import pytest - -pytestmark = [pytest.mark.unit, pytest.mark.teaching] - -# Test configuration -DOCS_DIR = Path(__file__).parent.parent / "docs" -COMMANDS_DIR = Path(__file__).parent.parent / "commands" - - -class TestTeachingDocumentation: - """Test teaching workflow documentation completeness""" - - def test_teaching_guide_exists(self): - """Teaching workflow guide should exist""" - guide_path = DOCS_DIR / "guide" / "teaching-workflow.md" - assert guide_path.exists(), "Teaching workflow guide not found" - - content = guide_path.read_text() - assert len(content) > 1000, "Teaching guide seems incomplete" - assert "Preview-before-publish" in content, "Missing key workflow description" - assert "Content validation" in content, "Missing validation section" - assert "Semester progress" in content, "Missing progress tracking section" - - def test_teaching_refcard_exists(self): - """Teaching quick reference should exist""" - refcard_path = DOCS_DIR / "reference" / "REFCARD-TEACHING.md" - assert refcard_path.exists(), "Teaching refcard not found" - - content = refcard_path.read_text() - # site:publish/progress/build moved to folio in the folio split (Phase 3, 2026-07-12) - assert "/folio:site:publish" in content, "Missing publish command" - assert "/folio:site:progress" in content, "Missing progress command" - assert "/folio:site:build" in content, "Missing build command" - - def test_teaching_demo_exists(self): - """VHS demo tape should exist""" - demo_path = DOCS_DIR / "demos" / "teaching-workflow.tape" - assert demo_path.exists(), "Teaching demo tape not found" - - content = demo_path.read_text() - assert "Output" in content, "Missing output directive" - # site:publish moved to folio in the folio split (Phase 3, 2026-07-12) - assert "/folio:site:publish" in content, "Demo doesn't show publish workflow" - - def test_teaching_tutorial_exists(self): - """Teaching mode setup tutorial should exist (from PR)""" - tutorial_path = DOCS_DIR / "tutorials" / "teaching-mode-setup.md" - assert tutorial_path.exists(), "Teaching setup tutorial not found" - - def test_teaching_config_schema_exists(self): - """Teaching config schema should exist (from PR)""" - schema_path = DOCS_DIR / "teaching-config-schema.md" - assert schema_path.exists(), "Teaching config schema not found" - - def test_teaching_migration_guide_exists(self): - """Teaching migration guide should exist (from PR)""" - migration_path = DOCS_DIR / "teaching-migration.md" - assert migration_path.exists(), "Teaching migration guide not found" - - def test_teaching_commands_documented(self): - """All teaching commands should be documented. - - site/publish.md, site/progress.md, site/build.md moved to the `folio` - plugin in the folio split (Phase 3, 2026-07-12) — folio now owns that - surface. git/status.md (formerly craft's only remaining - teaching-adjacent command) was folded entirely into the dev/git skill - in the v4 consolidation (Phase 3.5, 2026-07) — commands/git/ is now - empty, so no teaching-adjacent commands remain to check here.""" - commands: list[str] = [] - - for cmd in commands: - cmd_path = COMMANDS_DIR / cmd - assert cmd_path.exists(), f"Command file {cmd} not found" - - def test_mkdocs_navigation_includes_teaching(self): - """mkdocs.yml should include teaching documentation""" - mkdocs_path = Path(__file__).parent.parent / "mkdocs.yml" - assert mkdocs_path.exists(), "mkdocs.yml not found" - - content = mkdocs_path.read_text() - assert "teaching-workflow.md" in content, "Teaching guide not in navigation" - assert "REFCARD-TEACHING.md" in content, "Teaching refcard not in help section" - - def test_refcard_includes_teaching_section(self): - """Main refcard should include teaching commands""" - refcard_path = DOCS_DIR / "REFCARD.md" - assert refcard_path.exists(), "Main REFCARD.md not found" - - content = refcard_path.read_text() - # site:publish/progress moved to folio in the folio split (Phase 3, 2026-07-12) - assert "/folio:site:publish" in content, "Publish command not in main refcard" - assert "/folio:site:progress" in content, "Progress command not in main refcard" - assert "Teaching Mode" in content, "Teaching mode section missing" - - def test_changelog_includes_v122(self): - """CHANGELOG should document v1.22.0 teaching release""" - changelog_path = Path(__file__).parent.parent / "CHANGELOG.md" - assert changelog_path.exists(), "CHANGELOG.md not found" - - content = changelog_path.read_text() - assert "## [1.22.0]" in content, "v1.22.0 release not documented" - assert "Teaching Workflow System" in content, "Teaching feature not in changelog" - assert "80% time reduction" in content, "Impact metrics missing" - - def test_teaching_guide_has_complete_sections(self): - """Teaching guide should have all required sections""" - guide_path = DOCS_DIR / "guide" / "teaching-workflow.md" - content = guide_path.read_text() - - required_sections = [ - "## Overview", - "## Quick Start", - "## Core Commands", - "## How It Works", - "## Configuration", - "## Common Workflows", - "## Teaching-Aware Commands", - "## Troubleshooting", - "## Migration Guide", - "## Advanced Features", - "## Impact" - ] - - for section in required_sections: - assert section in content, f"Missing section: {section}" - - def test_teaching_guide_has_mermaid_diagram(self): - """Teaching guide should include workflow diagram""" - guide_path = DOCS_DIR / "guide" / "teaching-workflow.md" - content = guide_path.read_text() - - assert "```mermaid" in content, "Missing Mermaid diagram" - assert ( - "graph TD" in content - or "graph LR" in content - or "flowchart TD" in content - or "flowchart LR" in content - ), "Mermaid diagram incomplete" - - def test_teaching_guide_has_code_examples(self): - """Teaching guide should include practical examples""" - guide_path = DOCS_DIR / "guide" / "teaching-workflow.md" - content = guide_path.read_text() - - # Check for YAML examples - assert "```yaml" in content, "Missing YAML configuration examples" - assert "teaching:" in content, "Missing teaching config example" - - # Check for shell examples - assert "```bash" in content, "Missing shell command examples" - - def test_refcard_has_quick_start(self): - """Refcard should have copy-paste ready quick start""" - refcard_path = DOCS_DIR / "reference" / "REFCARD-TEACHING.md" - content = refcard_path.read_text() - - assert "## Quick Start" in content, "Missing quick start section" - assert "cat >" in content or "teaching:" in content, "Missing quick start config" - - def test_refcard_has_common_workflows(self): - """Refcard should document common workflows""" - refcard_path = DOCS_DIR / "reference" / "REFCARD-TEACHING.md" - content = refcard_path.read_text() - - assert "## Common Workflows" in content, "Missing workflows section" - assert "Weekly Content Update" in content, "Missing weekly update workflow" - - def test_refcard_has_troubleshooting(self): - """Refcard should have troubleshooting quick fixes""" - refcard_path = DOCS_DIR / "reference" / "REFCARD-TEACHING.md" - content = refcard_path.read_text() - - assert "## Troubleshooting" in content, "Missing troubleshooting section" - assert "Not Detecting Teaching Mode" in content, "Missing detection troubleshooting" - - def test_demo_tape_is_valid_vhs(self): - """VHS demo tape should have valid syntax""" - demo_path = DOCS_DIR / "demos" / "teaching-workflow.tape" - content = demo_path.read_text() - - # Check VHS directives - assert "Output" in content, "Missing Output directive" - assert "Set FontSize" in content, "Missing font size setting" - assert "Set Width" in content, "Missing width setting" - assert "Set Height" in content, "Missing height setting" - - # Check demo steps - assert "Type" in content, "Missing Type commands" - assert "Sleep" in content, "Missing Sleep commands" - assert "Enter" in content, "Missing Enter commands" - - def test_teaching_docs_cross_reference(self): - """Teaching docs should properly cross-reference each other""" - guide_path = DOCS_DIR / "guide" / "teaching-workflow.md" - content = guide_path.read_text() - - # Should reference other docs - assert "teaching-config-schema.md" in content, "Missing schema reference" - assert "teaching-migration.md" in content, "Missing migration reference" - assert "teaching-mode-setup.md" in content, "Missing tutorial reference" - - def test_no_broken_internal_links_in_teaching_docs(self): - """Teaching documentation should not have broken internal links""" - teaching_docs = [ - DOCS_DIR / "guide" / "teaching-workflow.md", - DOCS_DIR / "reference" / "REFCARD-TEACHING.md" - ] - - for doc_path in teaching_docs: - if not doc_path.exists(): - continue - - content = doc_path.read_text() - - # Find markdown links - link_pattern = r'\[([^\]]+)\]\(([^)]+)\)' - links = re.findall(link_pattern, content) - - for link_text, link_url in links: - # Skip external links - if link_url.startswith(('http://', 'https://', '#')): - continue - - # Check internal file links - if link_url.endswith('.md'): - # Resolve relative path - target = (doc_path.parent / link_url).resolve() - assert target.exists(), f"Broken link in {doc_path.name}: {link_url}" - - def test_teaching_utilities_have_readme(self): - """readme-teach-config / readme-semester-progress were removed as teaching - residue in the v4 command consolidation (2026-07) — no replacement needed.""" - - def test_teaching_examples_exist(self): - """Teaching config examples should exist""" - examples_dir = Path(__file__).parent.parent / "examples" - - example_path = examples_dir / "teach-config-example.yml" - assert example_path.exists(), "Missing teach-config example" - - content = example_path.read_text() - # Example uses course: root key instead of teaching: - assert "course:" in content or "teaching:" in content, "Example missing config structure" - assert "semester:" in content or "dates:" in content, "Example missing semester info" - - def test_documentation_completeness_score(self): - """Calculate documentation completeness score""" - checks = { - 'guide': (DOCS_DIR / "guide" / "teaching-workflow.md").exists(), - 'refcard': (DOCS_DIR / "reference" / "REFCARD-TEACHING.md").exists(), - 'demo': (DOCS_DIR / "demos" / "teaching-workflow.tape").exists(), - 'tutorial': (DOCS_DIR / "tutorials" / "teaching-mode-setup.md").exists(), - 'schema': (DOCS_DIR / "teaching-config-schema.md").exists(), - 'migration': (DOCS_DIR / "teaching-migration.md").exists(), - 'changelog': "## [1.22.0]" in (Path(__file__).parent.parent / "CHANGELOG.md").read_text(), - 'navigation': "teaching-workflow.md" in (Path(__file__).parent.parent / "mkdocs.yml").read_text() - } - - score = sum(checks.values()) / len(checks) * 100 - print(f"\n📊 Documentation Completeness: {score:.0f}%") - - assert score == 100, f"Documentation incomplete: {score:.0f}%" - - -def run_tests(): - """Run all documentation tests""" - import pytest - import sys - - # Run tests with verbose output - exit_code = pytest.main([__file__, '-v', '--tb=short']) - sys.exit(exit_code) - - -if __name__ == '__main__': - run_tests() diff --git a/tests/test_teaching_integration.py b/tests/test_teaching_integration.py deleted file mode 100644 index 51ed2b334..000000000 --- a/tests/test_teaching_integration.py +++ /dev/null @@ -1,332 +0,0 @@ -#!/usr/bin/env python3 -""" -Teaching Workflow Integration Tests -==================================== -Comprehensive end-to-end tests for the teaching workflow implementation. - -Tests cover: -- End-to-end workflow scenarios -- Error scenarios and edge cases -- Cross-component integration -- Performance benchmarks -- Validation completeness - -Run with: python tests/test_teaching_integration.py -""" - -import json -import os -import shutil -import sys -import tempfile -import time -from datetime import datetime, timedelta -from pathlib import Path -from typing import List, Optional, Dict, Any - -import pytest - -# Add parent directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from commands.utils.teach_config import load_teach_config, validate_config -from commands.utils.teaching_validation import validate_teaching_content -from commands.utils.semester_progress import calculate_current_week as _calculate_current_week -from utils.detect_teaching_mode import detect_teaching_mode - -pytestmark = [pytest.mark.integration, pytest.mark.teaching] - - -# ─── Test Fixtures ──────────────────────────────────────────────────────────── - -def get_fixture_path(name: str) -> Path: - """Get path to a test fixture.""" - return Path(__file__).parent / "fixtures" / "teaching" / name - - -def calculate_semester_progress(cwd: str) -> Optional[Dict[str, any]]: - """ - Wrapper for calculate_current_week that loads config first. - - Args: - cwd: Path to teaching project - - Returns: - Progress dictionary or None if config not found - """ - try: - config = load_teach_config(cwd) - if not config: - return None - return _calculate_current_week(config) - except Exception: - return None - - -# ─── End-to-End Workflow Tests ─────────────────────────────────────────────── - -def test_e2e_minimal_course(): - """Test complete workflow with minimal course configuration.""" - fixture = get_fixture_path("minimal") - - # Step 1: Detection - is_teaching, method = detect_teaching_mode(str(fixture)) - assert is_teaching, "Failed to detect minimal teaching project" - - # Step 2: Load config - config = load_teach_config(str(fixture)) - assert config, "Failed to load config" - assert config['course']['number'] == "TEST 100", f"Wrong course code: {config['course']['number']}" - - # Step 3: Validate - errors = validate_config(config) - assert not errors, f"Validation errors: {errors}" - - # Step 4: Calculate progress - progress = calculate_semester_progress(str(fixture)) - assert progress, "Failed to calculate progress" - - -def test_e2e_full_course(): - """Test complete workflow with full course configuration.""" - fixture = get_fixture_path("stat-545") - - # Step 1: Detection - is_teaching, method = detect_teaching_mode(str(fixture)) - assert is_teaching, "Failed to detect full teaching project" - - # Step 2: Load config - config = load_teach_config(str(fixture)) - assert config, "Failed to load config" - - # Check optional sections - assert 'teaching_assistants' in config and len(config['teaching_assistants']) == 2, \ - f"Expected 2 TAs, got {len(config.get('teaching_assistants', []))}" - - # Step 3: Validate - errors = validate_config(config) - assert not errors, f"Validation errors: {errors}" - - # Step 4: Check break handling - breaks = config['dates'].get('breaks', []) - assert len(breaks) == 2, f"Expected 2 breaks, got {len(breaks)}" - - -def test_e2e_summer_session(): - """Test workflow with summer session (compressed, no breaks).""" - fixture = get_fixture_path("summer") - - # Detection - is_teaching, method = detect_teaching_mode(str(fixture)) - assert is_teaching, "Failed to detect summer teaching project" - - # Load config - config = load_teach_config(str(fixture)) - assert config, "Failed to load config" - - # Verify no breaks - breaks = config['dates'].get('breaks', []) - assert not breaks, "Summer session should have no breaks" - - # Validate - errors = validate_config(config) - assert not errors, f"Validation errors: {errors}" - - -# ─── Error Scenario Tests ───────────────────────────────────────────────────── - -def test_error_missing_config(): - """Test handling of missing teach-config.yml.""" - with tempfile.TemporaryDirectory() as tmpdir: - tmp_path = Path(tmpdir) - - # Create teaching project without config - (tmp_path / "syllabus.qmd").write_text("# Syllabus") - (tmp_path / "schedule.qmd").write_text("# Schedule") - - # Should detect as teaching - is_teaching, method = detect_teaching_mode(str(tmp_path)) - assert is_teaching, "Should detect teaching project even without config" - - # Config load should return None - config = load_teach_config(str(tmp_path)) - assert config is None, "Should return None for missing config" - - -def test_error_invalid_yaml(): - """Test handling of invalid YAML syntax.""" - with tempfile.TemporaryDirectory() as tmpdir: - tmp_path = Path(tmpdir) - - # Create config with truly invalid YAML syntax - config_content = """--- -course: - number: "TEST 100" - title: "Test Course - semester: Spring - year: 2026 - invalid_indent: - broken: true -""" - (tmp_path / "teach-config.yml").write_text(config_content) - - # Should fail to load due to malformed YAML - config = load_teach_config(str(tmp_path)) - assert config is None, "Should return None for invalid YAML" - - -def test_error_missing_required_fields(): - """Test validation of missing required fields.""" - with tempfile.TemporaryDirectory() as tmpdir: - tmp_path = Path(tmpdir) - - # Create config missing required fields - # Has 'course' but missing 'number', 'semester', 'year' - # Has 'dates' but missing 'end' - config_content = """--- -course: - title: "Incomplete Course" - # Missing number, semester, year - -dates: - start: "2026-01-01" - # Missing end -""" - (tmp_path / "teach-config.yml").write_text(config_content) - - try: - config = load_teach_config(str(tmp_path)) - except ValueError: - # load_teach_config raises ValueError on validation failure - return # Test passes - - if not config: - # OK if it fails to load - return # Test passes - - # If it loads, validation should fail - errors = validate_config(config) - assert errors, "Should report errors for missing required fields" - - -# ─── Edge Case Tests ────────────────────────────────────────────────────────── - -def test_edge_before_semester(): - """Test progress calculation before semester starts.""" - with tempfile.TemporaryDirectory() as tmpdir: - tmp_path = Path(tmpdir) - - # Create future course - future_start = (datetime.now() + timedelta(days=30)).strftime("%Y-%m-%d") - future_end = (datetime.now() + timedelta(days=150)).strftime("%Y-%m-%d") - - config_content = f"""--- -course: - number: "TEST 100" - title: "Future Course" - semester: "Spring" - year: 2027 - -dates: - start: "{future_start}" - end: "{future_end}" -""" - (tmp_path / "teach-config.yml").write_text(config_content) - - progress = calculate_semester_progress(str(tmp_path)) - assert progress, "Failed to calculate progress for future course" - - # Week should be 0 or negative, percentage 0 - assert progress.get('current_week', 1) <= 0, \ - f"Current week should be <=0, got {progress.get('current_week')}" - - -def test_edge_after_semester(): - """Test progress calculation after semester ends.""" - with tempfile.TemporaryDirectory() as tmpdir: - tmp_path = Path(tmpdir) - - # Create past course - past_start = (datetime.now() - timedelta(days=150)).strftime("%Y-%m-%d") - past_end = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d") - - config_content = f"""--- -course: - number: "TEST 100" - title: "Past Course" - semester: "Fall" - year: 2025 - -dates: - start: "{past_start}" - end: "{past_end}" -""" - (tmp_path / "teach-config.yml").write_text(config_content) - - progress = calculate_semester_progress(str(tmp_path)) - assert progress, "Failed to calculate progress for past course" - - # Should indicate semester is over via percent_complete == 100 - percent = progress.get('percent_complete', 0) - current_week = progress.get('current_week', 0) - total_weeks = progress.get('total_weeks', 0) - assert percent >= 100.0 and current_week == total_weeks, \ - f"Should be 100% complete, got {percent}% (week {current_week}/{total_weeks})" - - -# ─── Performance Benchmarks ─────────────────────────────────────────────────── - -def test_benchmark_detection(): - """Benchmark: Teaching mode detection should be < 100ms.""" - fixture = get_fixture_path("stat-545") - - start = time.time() - is_teaching, method = detect_teaching_mode(str(fixture)) - duration = (time.time() - start) * 1000 - - assert is_teaching, "Failed to detect teaching project" - - target_ms = 100 - assert duration < target_ms, f"{duration:.2f}ms (target: <{target_ms}ms)" - - -def test_benchmark_config_parsing(): - """Benchmark: Config parsing should be < 200ms.""" - fixture = get_fixture_path("stat-545") - - start = time.time() - config = load_teach_config(str(fixture)) - duration = (time.time() - start) * 1000 - - assert config, "Failed to load config" - - target_ms = 200 - assert duration < target_ms, f"{duration:.2f}ms (target: <{target_ms}ms)" - - -def test_benchmark_validation(): - """Benchmark: Full validation should be < 5s.""" - fixture = get_fixture_path("stat-545") - - start = time.time() - results = validate_teaching_content(str(fixture)) - duration = (time.time() - start) * 1000 - - assert results, "Validation failed to return results" - - target_ms = 5000 - assert duration < target_ms, f"{duration:.2f}ms (target: <{target_ms}ms)" - - -def test_benchmark_progress(): - """Benchmark: Progress calculation should be < 100ms.""" - fixture = get_fixture_path("stat-545") - - start = time.time() - progress = calculate_semester_progress(str(fixture)) - duration = (time.time() - start) * 1000 - - assert progress, "Failed to calculate progress" - - target_ms = 100 - assert duration < target_ms, f"{duration:.2f}ms (target: <{target_ms}ms)" diff --git a/tests/test_teaching_mode.py b/tests/test_teaching_mode.py deleted file mode 100644 index 902bc5900..000000000 --- a/tests/test_teaching_mode.py +++ /dev/null @@ -1,354 +0,0 @@ -#!/usr/bin/env python3 -""" -Unit tests for teaching mode detection utility. - -Tests all three detection strategies with priority ordering: -1. Config file detection -2. Metadata detection -3. Structure detection - -Run with: python3 -m pytest tests/test_teaching_mode.py -v -""" - -import os -import sys -import tempfile -import shutil -from pathlib import Path - -import pytest - -# Add parent directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - -pytestmark = [pytest.mark.unit, pytest.mark.teaching] - -from utils.detect_teaching_mode import detect_teaching_mode - - -class TestTeachingModeDetection: - """Test suite for teaching mode detection""" - - @pytest.fixture - def temp_project(self): - """Create a temporary project directory""" - temp_dir = tempfile.mkdtemp() - yield Path(temp_dir) - shutil.rmtree(temp_dir) - - def test_config_detection_priority_1(self, temp_project): - """Test Priority 1: .flow/teach-config.yml detection""" - # Create .flow/teach-config.yml - flow_dir = temp_project / ".flow" - flow_dir.mkdir() - config_file = flow_dir / "teach-config.yml" - config_file.write_text("course: STAT 440\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - assert is_teaching is True - assert method == "config" - - def test_metadata_detection_priority_2(self, temp_project): - """Test Priority 2: _quarto.yml teaching: true detection""" - # Create _quarto.yml with teaching: true - quarto_file = temp_project / "_quarto.yml" - quarto_file.write_text("teaching: true\nproject:\n type: website\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - assert is_teaching is True - assert method == "metadata" - - def test_structure_detection_priority_3_with_dir(self, temp_project): - """Test Priority 3: Structure detection with syllabus/ directory""" - # Create syllabus/ directory - syllabus_dir = temp_project / "syllabus" - syllabus_dir.mkdir() - - # Create schedule.qmd - schedule_file = temp_project / "schedule.qmd" - schedule_file.write_text("# Course Schedule\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - assert is_teaching is True - assert method == "structure" - - def test_structure_detection_priority_3_with_file(self, temp_project): - """Test Priority 3: Structure detection with syllabus.qmd file""" - # Create syllabus.qmd file - syllabus_file = temp_project / "syllabus.qmd" - syllabus_file.write_text("# Course Syllabus\n") - - # Create schedule.qmd - schedule_file = temp_project / "schedule.qmd" - schedule_file.write_text("# Course Schedule\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - assert is_teaching is True - assert method == "structure" - - def test_priority_order_config_over_metadata(self, temp_project): - """Test that config detection takes priority over metadata""" - # Create both config and metadata - flow_dir = temp_project / ".flow" - flow_dir.mkdir() - config_file = flow_dir / "teach-config.yml" - config_file.write_text("course: STAT 440\n") - - quarto_file = temp_project / "_quarto.yml" - quarto_file.write_text("teaching: true\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - # Should detect via config (Priority 1), not metadata - assert is_teaching is True - assert method == "config" - - def test_priority_order_config_over_structure(self, temp_project): - """Test that config detection takes priority over structure""" - # Create config, syllabus, and schedule - flow_dir = temp_project / ".flow" - flow_dir.mkdir() - config_file = flow_dir / "teach-config.yml" - config_file.write_text("course: STAT 440\n") - - syllabus_dir = temp_project / "syllabus" - syllabus_dir.mkdir() - - schedule_file = temp_project / "schedule.qmd" - schedule_file.write_text("# Schedule\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - # Should detect via config (Priority 1) - assert is_teaching is True - assert method == "config" - - def test_priority_order_metadata_over_structure(self, temp_project): - """Test that metadata detection takes priority over structure""" - # Create metadata, syllabus, and schedule - quarto_file = temp_project / "_quarto.yml" - quarto_file.write_text("teaching: true\n") - - syllabus_dir = temp_project / "syllabus" - syllabus_dir.mkdir() - - schedule_file = temp_project / "schedule.qmd" - schedule_file.write_text("# Schedule\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - # Should detect via metadata (Priority 2) - assert is_teaching is True - assert method == "metadata" - - def test_no_teaching_mode_detected(self, temp_project): - """Test negative case: no teaching mode indicators""" - # Create a non-teaching project (e.g., research project) - readme = temp_project / "README.md" - readme.write_text("# Research Project\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - assert is_teaching is False - assert method is None - - def test_structure_missing_schedule(self, temp_project): - """Test structure detection fails without schedule.qmd""" - # Create only syllabus directory, no schedule - syllabus_dir = temp_project / "syllabus" - syllabus_dir.mkdir() - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - assert is_teaching is False - assert method is None - - def test_structure_missing_syllabus(self, temp_project): - """Test structure detection fails without syllabus""" - # Create only schedule.qmd, no syllabus - schedule_file = temp_project / "schedule.qmd" - schedule_file.write_text("# Schedule\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - assert is_teaching is False - assert method is None - - def test_metadata_teaching_false(self, temp_project): - """Test that teaching: false in metadata is not detected""" - quarto_file = temp_project / "_quarto.yml" - quarto_file.write_text("teaching: false\nproject:\n type: website\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - assert is_teaching is False - assert method is None - - def test_metadata_teaching_string_true(self, temp_project): - """Test that teaching: 'true' (string) is not detected as true""" - quarto_file = temp_project / "_quarto.yml" - quarto_file.write_text("teaching: 'true'\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - # YAML should parse 'true' as string, not boolean - # Only boolean true should be detected - assert is_teaching is False - assert method is None - - def test_metadata_no_teaching_field(self, temp_project): - """Test _quarto.yml without teaching field""" - quarto_file = temp_project / "_quarto.yml" - quarto_file.write_text("project:\n type: website\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - assert is_teaching is False - assert method is None - - def test_default_cwd(self, temp_project, monkeypatch): - """Test that default cwd='.' uses current directory""" - # Change to temp project directory - monkeypatch.chdir(temp_project) - - # Create config file - flow_dir = temp_project / ".flow" - flow_dir.mkdir() - config_file = flow_dir / "teach-config.yml" - config_file.write_text("course: TEST\n") - - # Call without arguments (should use current directory) - is_teaching, method = detect_teaching_mode() - - assert is_teaching is True - assert method == "config" - - def test_invalid_yaml_in_quarto(self, temp_project): - """Test graceful handling of invalid YAML in _quarto.yml""" - quarto_file = temp_project / "_quarto.yml" - quarto_file.write_text("teaching: true\n invalid: yaml: structure:\n") - - # Should not crash, should return False - is_teaching, method = detect_teaching_mode(str(temp_project)) - - # Result depends on YAML parser behavior - # Either detects or fails gracefully - assert isinstance(is_teaching, bool) - assert method in ["metadata", None] - - def test_empty_quarto_file(self, temp_project): - """Test empty _quarto.yml file""" - quarto_file = temp_project / "_quarto.yml" - quarto_file.write_text("") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - assert is_teaching is False - assert method is None - - def test_nonexistent_directory(self): - """Test with nonexistent directory path""" - nonexistent = "/tmp/nonexistent-project-xyz123" - - is_teaching, method = detect_teaching_mode(nonexistent) - - # Should handle gracefully - assert is_teaching is False - assert method is None - - -class TestEdgeCases: - """Test edge cases and error handling""" - - @pytest.fixture - def temp_project(self): - """Create a temporary project directory""" - temp_dir = tempfile.mkdtemp() - yield Path(temp_dir) - shutil.rmtree(temp_dir) - - def test_symlink_to_syllabus(self, temp_project): - """Test that symlinks to syllabus directory work""" - # Create actual syllabus directory elsewhere - real_syllabus = temp_project / "real_syllabus" - real_syllabus.mkdir() - - # Create symlink - syllabus_link = temp_project / "syllabus" - syllabus_link.symlink_to(real_syllabus) - - # Create schedule - schedule_file = temp_project / "schedule.qmd" - schedule_file.write_text("# Schedule\n") - - is_teaching, method = detect_teaching_mode(str(temp_project)) - - assert is_teaching is True - assert method == "structure" - - def test_relative_path_input(self, temp_project, monkeypatch): - """Test with relative path input""" - # Change to parent of temp_project - parent = temp_project.parent - monkeypatch.chdir(parent) - - # Create config - flow_dir = temp_project / ".flow" - flow_dir.mkdir() - config_file = flow_dir / "teach-config.yml" - config_file.write_text("course: TEST\n") - - # Use relative path - rel_path = temp_project.name - is_teaching, method = detect_teaching_mode(rel_path) - - assert is_teaching is True - assert method == "config" - - -class TestIntegration: - """Integration tests with realistic project structures""" - - @pytest.fixture - def realistic_teaching_project(self): - """Create a realistic teaching project""" - temp_dir = tempfile.mkdtemp() - project = Path(temp_dir) - - # Create typical teaching project structure - (project / ".flow").mkdir() - (project / ".flow" / "teach-config.yml").write_text("course: STAT 440\n") - - (project / "_quarto.yml").write_text( - "project:\n" - " type: website\n" - "teaching: true\n" - ) - - (project / "syllabus").mkdir() - (project / "syllabus" / "index.qmd").write_text("# Syllabus\n") - - (project / "schedule.qmd").write_text("# Schedule\n") - - (project / "lectures").mkdir() - (project / "assignments").mkdir() - - yield project - shutil.rmtree(temp_dir) - - def test_realistic_teaching_project(self, realistic_teaching_project): - """Test detection on realistic teaching project""" - is_teaching, method = detect_teaching_mode(str(realistic_teaching_project)) - - assert is_teaching is True - # Should detect via config (highest priority) - assert method == "config" - - -if __name__ == "__main__": - # Run tests with pytest - pytest.main([__file__, "-v", "--tb=short"]) diff --git a/tests/test_teaching_validation.py b/tests/test_teaching_validation.py deleted file mode 100644 index e6b615b92..000000000 --- a/tests/test_teaching_validation.py +++ /dev/null @@ -1,666 +0,0 @@ -"""Unit tests for teaching content validation. - -Tests all validation functions with mock course structures: -- Syllabus validation (complete and incomplete) -- Schedule validation (complete and with gaps) -- Assignment validation (all exist and some missing) -- ValidationResult logic and formatting -""" - -import os -import sys -import tempfile -import unittest -from pathlib import Path - -import pytest - -# Add parent directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - -pytestmark = [pytest.mark.unit, pytest.mark.teaching] - -from commands.utils.teaching_validation import ( - ValidationResult, - validate_syllabus, - validate_schedule, - validate_assignments, - validate_teaching_content, -) - - -class TestValidationResult(unittest.TestCase): - """Test ValidationResult dataclass.""" - - def test_can_publish_no_errors(self): - """Should allow publish when no errors.""" - result = ValidationResult( - valid=True, - errors=[], - warnings=["Some warning"], - checks={"test": True} - ) - self.assertTrue(result.can_publish()) - - def test_can_publish_with_errors(self): - """Should block publish when errors exist.""" - result = ValidationResult( - valid=False, - errors=["Critical error"], - warnings=[], - checks={"test": False} - ) - self.assertFalse(result.can_publish()) - - def test_format_report_success(self): - """Should format success report correctly.""" - result = ValidationResult( - valid=True, - errors=[], - warnings=["Minor issue"], - checks={ - "Check 1": True, - "Check 2": True, - } - ) - report = result.format_report() - - self.assertIn("READY TO PUBLISH", report) - self.assertIn("2/2 checks passed", report) - self.assertIn("Minor issue", report) - self.assertIn("✓", report) - - def test_format_report_errors(self): - """Should format error report correctly.""" - result = ValidationResult( - valid=False, - errors=["Error 1", "Error 2"], - warnings=["Warning 1"], - checks={ - "Check 1": True, - "Check 2": False, - } - ) - report = result.format_report() - - self.assertIn("BLOCKED", report) - self.assertIn("Error 1", report) - self.assertIn("Error 2", report) - self.assertIn("Warning 1", report) - self.assertIn("2 error(s) blocking", report) - - -class TestValidateSyllabus(unittest.TestCase): - """Test syllabus validation.""" - - def setUp(self): - """Create temporary directory for tests.""" - self.test_dir = tempfile.mkdtemp() - - def tearDown(self): - """Clean up temporary directory.""" - import shutil - shutil.rmtree(self.test_dir) - - def test_no_syllabus(self): - """Should return all False when no syllabus exists.""" - result = validate_syllabus(self.test_dir) - - self.assertFalse(result['grading']) - self.assertFalse(result['policies']) - self.assertFalse(result['objectives']) - self.assertFalse(result['schedule']) - - def test_complete_syllabus(self): - """Should find all sections in complete syllabus.""" - syllabus_content = """ -# Course Syllabus - -## Learning Objectives -Students will learn statistical methods. - -## Course Policies -Attendance is required. - -## Grading -- Homework: 40% -- Exams: 60% - -## Schedule -Week 1: Introduction - """ - - syllabus_path = Path(self.test_dir) / 'syllabus.qmd' - syllabus_path.write_text(syllabus_content) - - result = validate_syllabus(self.test_dir) - - self.assertTrue(result['grading']) - self.assertTrue(result['policies']) - self.assertTrue(result['objectives']) - self.assertTrue(result['schedule']) - - def test_partial_syllabus(self): - """Should detect missing sections.""" - syllabus_content = """ -# Course Syllabus - -## Grading Policy -Homework counts for everything. - -## Learning Goals -Learn statistics. - """ - - syllabus_path = Path(self.test_dir) / 'syllabus.qmd' - syllabus_path.write_text(syllabus_content) - - result = validate_syllabus(self.test_dir) - - self.assertTrue(result['grading']) - self.assertTrue(result['objectives']) - self.assertFalse(result['policies']) - self.assertFalse(result['schedule']) - - def test_syllabus_subdirectory(self): - """Should find syllabus in subdirectory.""" - syllabus_dir = Path(self.test_dir) / 'syllabus' - syllabus_dir.mkdir() - - syllabus_content = """ -# Syllabus - -## Assessment -Graded on participation. - """ - - (syllabus_dir / 'index.qmd').write_text(syllabus_content) - - result = validate_syllabus(self.test_dir) - - self.assertTrue(result['grading']) - - def test_case_insensitive_matching(self): - """Should match section headers case-insensitively.""" - syllabus_content = """ -# SYLLABUS - -## GRADING POLICY -All caps should work. - -## course POLICIES -Mixed case too. - """ - - syllabus_path = Path(self.test_dir) / 'syllabus.qmd' - syllabus_path.write_text(syllabus_content) - - result = validate_syllabus(self.test_dir) - - self.assertTrue(result['grading']) - self.assertTrue(result['policies']) - - -class TestValidateSchedule(unittest.TestCase): - """Test schedule validation.""" - - def setUp(self): - """Create temporary directory for tests.""" - self.test_dir = tempfile.mkdtemp() - - def tearDown(self): - """Clean up temporary directory.""" - import shutil - shutil.rmtree(self.test_dir) - - def test_no_schedule(self): - """Should return empty result when no schedule exists.""" - result = validate_schedule(self.test_dir) - - self.assertEqual(result['total_weeks'], 0) - self.assertEqual(result['complete_weeks'], 0) - self.assertEqual(result['gaps'], []) - - def test_complete_schedule(self): - """Should validate complete schedule with no gaps.""" - schedule_content = """ -# Course Schedule - -## Week 1 -Introduction to statistics. Read Chapter 1. -Assignment: HW 1 due Friday. - -## Week 2 -Descriptive statistics. Read Chapter 2. -Assignment: HW 2 due next Friday. - -## Week 3 -Probability basics. Read Chapter 3. -Quiz on Friday covering Weeks 1-2. - """ - - schedule_path = Path(self.test_dir) / 'schedule.qmd' - schedule_path.write_text(schedule_content) - - result = validate_schedule(self.test_dir) - - self.assertEqual(result['total_weeks'], 3) - self.assertEqual(result['complete_weeks'], 3) - self.assertEqual(result['gaps'], []) - - def test_schedule_with_gaps(self): - """Should detect weeks with missing content.""" - schedule_content = """ -# Course Schedule - -## Week 1 -Introduction to statistics. Read Chapter 1. - -## Week 2 - -## Week 3 -Probability basics. Read Chapter 3. - -## Week 4 - """ - - schedule_path = Path(self.test_dir) / 'schedule.qmd' - schedule_path.write_text(schedule_content) - - result = validate_schedule(self.test_dir) - - self.assertEqual(result['total_weeks'], 4) - self.assertEqual(result['complete_weeks'], 2) - self.assertIn(2, result['gaps']) - self.assertIn(4, result['gaps']) - - def test_flexible_week_headers(self): - """Should match various week header formats.""" - schedule_content = """ -# Schedule - -## Week 1 -Content here that is long enough to pass validation. - -### Week 2: -More content that is also long enough to pass. - -#### Week 3 - Topic Name -Even more content with sufficient length to pass validation. - """ - - schedule_path = Path(self.test_dir) / 'schedule.qmd' - schedule_path.write_text(schedule_content) - - result = validate_schedule(self.test_dir) - - self.assertEqual(result['total_weeks'], 3) - self.assertEqual(result['complete_weeks'], 3) - - def test_schedule_subdirectory(self): - """Should find schedule in subdirectory.""" - schedule_dir = Path(self.test_dir) / 'schedule' - schedule_dir.mkdir() - - schedule_content = """ -## Week 1 -Some content here that is long enough to count. - """ - - (schedule_dir / 'index.qmd').write_text(schedule_content) - - result = validate_schedule(self.test_dir) - - self.assertEqual(result['total_weeks'], 1) - self.assertEqual(result['complete_weeks'], 1) - - -class TestValidateAssignments(unittest.TestCase): - """Test assignment validation.""" - - def setUp(self): - """Create temporary directory for tests.""" - self.test_dir = tempfile.mkdtemp() - - def tearDown(self): - """Clean up temporary directory.""" - import shutil - shutil.rmtree(self.test_dir) - - def test_no_schedule(self): - """Should return empty result when no schedule exists.""" - result = validate_assignments(self.test_dir) - - self.assertEqual(result['referenced'], []) - self.assertEqual(result['missing'], []) - self.assertEqual(result['found'], []) - - def test_all_assignments_exist(self): - """Should find all referenced assignments.""" - schedule_content = """ -# Schedule - -## Week 1 -Complete HW 1 by Friday. - -## Week 2 -Submit Assignment 2 next week. - -## Week 3 -Homework 3 due soon. - """ - - schedule_path = Path(self.test_dir) / 'schedule.qmd' - schedule_path.write_text(schedule_content) - - # Create assignment files - assignments_dir = Path(self.test_dir) / 'assignments' - assignments_dir.mkdir() - - (assignments_dir / 'hw-1.qmd').write_text("# HW 1") - (assignments_dir / 'hw-2.qmd').write_text("# HW 2") - (assignments_dir / 'hw-3.qmd').write_text("# HW 3") - - result = validate_assignments(self.test_dir) - - self.assertEqual(len(result['referenced']), 3) - self.assertIn("HW 1", result['referenced']) - self.assertIn("HW 2", result['referenced']) - self.assertIn("HW 3", result['referenced']) - self.assertEqual(len(result['found']), 3) - self.assertEqual(len(result['missing']), 0) - - def test_some_assignments_missing(self): - """Should detect missing assignment files.""" - schedule_content = """ -# Schedule - -## Week 1 -HW 1 due Friday. - -## Week 2 -HW 2 due Friday. - -## Week 3 -HW 3 due Friday. - """ - - schedule_path = Path(self.test_dir) / 'schedule.qmd' - schedule_path.write_text(schedule_content) - - # Create only HW 1 and HW 3 - assignments_dir = Path(self.test_dir) / 'assignments' - assignments_dir.mkdir() - - (assignments_dir / 'hw-1.qmd').write_text("# HW 1") - (assignments_dir / 'hw-3.qmd').write_text("# HW 3") - - result = validate_assignments(self.test_dir) - - self.assertEqual(len(result['referenced']), 3) - self.assertEqual(len(result['found']), 2) - self.assertIn("HW 2", result['missing']) - - def test_various_assignment_formats(self): - """Should match different assignment reference formats.""" - schedule_content = """ -# Schedule - -Week 1: HW1 due -Week 2: HW 2 due -Week 3: Assignment 3 due -Week 4: Homework 4 due - """ - - schedule_path = Path(self.test_dir) / 'schedule.qmd' - schedule_path.write_text(schedule_content) - - result = validate_assignments(self.test_dir) - - self.assertEqual(len(result['referenced']), 4) - # All should be missing since we didn't create files - self.assertEqual(len(result['missing']), 4) - - def test_assignment_subdirectories(self): - """Should find assignments in subdirectories.""" - schedule_content = """ -HW 1 is available. - """ - - schedule_path = Path(self.test_dir) / 'schedule.qmd' - schedule_path.write_text(schedule_content) - - # Create assignment in subdirectory - assignments_dir = Path(self.test_dir) / 'assignments' / 'hw-1' - assignments_dir.mkdir(parents=True) - (assignments_dir / 'index.qmd').write_text("# HW 1") - - result = validate_assignments(self.test_dir) - - self.assertIn("HW 1", result['found']) - - -class TestValidateTeachingContent(unittest.TestCase): - """Test comprehensive teaching content validation.""" - - def setUp(self): - """Create temporary directory for tests.""" - self.test_dir = tempfile.mkdtemp() - - def tearDown(self): - """Clean up temporary directory.""" - import shutil - shutil.rmtree(self.test_dir) - - def test_complete_valid_course(self): - """Should pass validation for complete course.""" - # Create complete syllabus - syllabus_content = """ -# Syllabus - -## Learning Objectives -Students will master statistics. - -## Course Policies -Be respectful and on time. - -## Grading -Homework 50%, Exams 50%. - -## Schedule -See full schedule page. - """ - (Path(self.test_dir) / 'syllabus.qmd').write_text(syllabus_content) - - # Create complete schedule - schedule_content = """ -## Week 1 -Introduction. Read Chapter 1. HW 1 due Friday. - -## Week 2 -More content. Read Chapter 2. HW 2 due Friday. - """ - (Path(self.test_dir) / 'schedule.qmd').write_text(schedule_content) - - # Create assignments - assignments_dir = Path(self.test_dir) / 'assignments' - assignments_dir.mkdir() - (assignments_dir / 'hw-1.qmd').write_text("# HW 1") - (assignments_dir / 'hw-2.qmd').write_text("# HW 2") - - result = validate_teaching_content(self.test_dir) - - self.assertTrue(result.can_publish()) - self.assertEqual(len(result.errors), 0) - # Warnings allowed (none in this case) - self.assertTrue(result.valid) - - def test_missing_syllabus_sections(self): - """Should error on missing syllabus sections.""" - # Incomplete syllabus - syllabus_content = """ -# Syllabus - -## Grading -Homework counts. - """ - (Path(self.test_dir) / 'syllabus.qmd').write_text(syllabus_content) - - # Complete schedule - schedule_content = """ -## Week 1 -Content here that is long enough. - """ - (Path(self.test_dir) / 'schedule.qmd').write_text(schedule_content) - - result = validate_teaching_content(self.test_dir) - - self.assertFalse(result.can_publish()) - self.assertGreater(len(result.errors), 0) - self.assertIn("Syllabus missing required sections", result.errors[0]) - - def test_incomplete_schedule(self): - """Should error on incomplete schedule weeks.""" - # Complete syllabus - syllabus_content = """ -# Syllabus - -## Objectives -Learn stats. - -## Policies -Attendance required. - -## Grading -Homework 100%. - -## Schedule -See schedule page. - """ - (Path(self.test_dir) / 'syllabus.qmd').write_text(syllabus_content) - - # Incomplete schedule - schedule_content = """ -## Week 1 -Good content here. - -## Week 2 - -## Week 3 -More good content. - """ - (Path(self.test_dir) / 'schedule.qmd').write_text(schedule_content) - - result = validate_teaching_content(self.test_dir) - - self.assertFalse(result.can_publish()) - self.assertGreater(len(result.errors), 0) - self.assertTrue(any("incomplete weeks" in e for e in result.errors)) - - def test_missing_assignments_warning(self): - """Should warn (not error) on missing assignments.""" - # Complete syllabus - syllabus_content = """ -# Syllabus - -## Objectives -Learn. - -## Policies -Rules. - -## Grading -Grades. - -## Schedule -Times. - """ - (Path(self.test_dir) / 'syllabus.qmd').write_text(syllabus_content) - - # Schedule referencing assignments - schedule_content = """ -## Week 1 -Complete HW 1 by Friday. Lots of content here. - -## Week 2 -Complete HW 2 by Friday. More content here. - """ - (Path(self.test_dir) / 'schedule.qmd').write_text(schedule_content) - - # Don't create assignment files - - result = validate_teaching_content(self.test_dir) - - # Should still be publishable (warnings don't block) - self.assertTrue(result.can_publish()) - # But should have warnings - self.assertGreater(len(result.warnings), 0) - self.assertTrue(any("Missing assignment files" in w for w in result.warnings)) - - def test_checks_populated(self): - """Should populate detailed checks dict.""" - # Minimal valid course - syllabus_content = """ -# Syllabus -## Objectives -Learn. -## Policies -Rules. -## Grading -Grades. -## Schedule -Times. - """ - (Path(self.test_dir) / 'syllabus.qmd').write_text(syllabus_content) - - schedule_content = """ -## Week 1 -Content with sufficient length to pass validation. - """ - (Path(self.test_dir) / 'schedule.qmd').write_text(schedule_content) - - result = validate_teaching_content(self.test_dir) - - # Should have checks for syllabus sections - self.assertIn('Syllabus: grading', result.checks) - self.assertIn('Syllabus: policies', result.checks) - self.assertIn('Syllabus: objectives', result.checks) - self.assertIn('Syllabus: schedule', result.checks) - - # Should have schedule checks - self.assertIn('Schedule: exists', result.checks) - - def test_format_report(self): - """Should generate formatted report.""" - # Course with one error and one warning - syllabus_content = """ -# Syllabus -## Grading -Grades. - """ - (Path(self.test_dir) / 'syllabus.qmd').write_text(syllabus_content) - - schedule_content = """ -## Week 1 -HW 1 due Friday. Good content here. - """ - (Path(self.test_dir) / 'schedule.qmd').write_text(schedule_content) - - result = validate_teaching_content(self.test_dir) - report = result.format_report() - - # Should be blocked - self.assertIn("BLOCKED", report) - - # Should show errors - self.assertIn("ERRORS", report) - - # Should show warnings - self.assertIn("WARNINGS", report) - - # Should show checks - self.assertIn("DETAILED CHECKS", report) - - -if __name__ == '__main__': - unittest.main() diff --git a/utils/detect_teaching_mode.py b/utils/detect_teaching_mode.py deleted file mode 100644 index 2dfbd1ee8..000000000 --- a/utils/detect_teaching_mode.py +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env python3 -""" -Teaching Mode Detection Utility for Craft Commands - -Detects whether a project is in teaching mode using multiple strategies with -priority-based fallback. Used by workflow commands to determine context. - -Detection Priority: - 1. Config file (.flow/teach-config.yml) - 2. Metadata (_quarto.yml teaching: true) - 3. Project structure (syllabus/ + schedule.qmd) - -Usage: - from utils.detect_teaching_mode import detect_teaching_mode - - is_teaching, method = detect_teaching_mode() - if is_teaching: - print(f"Teaching mode detected via: {method}") - else: - print("Not in teaching mode") - - # Or with custom directory - is_teaching, method = detect_teaching_mode("/path/to/project") -""" - -import os -from pathlib import Path -from typing import Tuple, Optional - -try: - import yaml - YAML_AVAILABLE = True -except ImportError: - YAML_AVAILABLE = False - - -def detect_teaching_mode(cwd: str = ".") -> Tuple[bool, Optional[str]]: - """ - Detect if current project is in teaching mode. - - Uses a priority-based detection system: - 1. Config file: Check for .flow/teach-config.yml - 2. Metadata: Check _quarto.yml for teaching: true - 3. Structure: Check for syllabus/ (or syllabus.qmd) AND schedule.qmd - - Args: - cwd: Working directory to check (default: current directory) - - Returns: - Tuple of (is_teaching: bool, detection_method: str | None) - - is_teaching: True if teaching mode detected - - detection_method: One of "config", "metadata", "structure", or None - - Examples: - >>> # Project with .flow/teach-config.yml - >>> is_teaching, method = detect_teaching_mode("/path/to/course") - >>> print(is_teaching, method) - True config - - >>> # Project with _quarto.yml teaching: true - >>> is_teaching, method = detect_teaching_mode("/path/to/course") - >>> print(is_teaching, method) - True metadata - - >>> # Project with syllabus/ and schedule.qmd - >>> is_teaching, method = detect_teaching_mode("/path/to/course") - >>> print(is_teaching, method) - True structure - - >>> # Non-teaching project - >>> is_teaching, method = detect_teaching_mode("/path/to/research") - >>> print(is_teaching, method) - False None - """ - project_path = Path(cwd).resolve() - - # Priority 1: Check for .flow/teach-config.yml - config_file = project_path / ".flow" / "teach-config.yml" - if config_file.exists(): - return (True, "config") - - # Priority 2: Check _quarto.yml for teaching: true - quarto_file = project_path / "_quarto.yml" - if quarto_file.exists(): - if _check_quarto_metadata(quarto_file): - return (True, "metadata") - - # Priority 3: Check project structure - if _check_project_structure(project_path): - return (True, "structure") - - return (False, None) - - -def _check_quarto_metadata(quarto_file: Path) -> bool: - """ - Check if _quarto.yml contains teaching: true. - - Args: - quarto_file: Path to _quarto.yml file - - Returns: - True if teaching: true is found, False otherwise - """ - if not YAML_AVAILABLE: - # Fallback: Simple text search if PyYAML not available - try: - content = quarto_file.read_text() - # Look for "teaching: true" (with or without quotes) - return "teaching:" in content and "true" in content.lower() - except Exception: - return False - - try: - with open(quarto_file, 'r') as f: - data = yaml.safe_load(f) - if data and isinstance(data, dict): - return data.get('teaching', False) is True - except Exception: - return False - - return False - - -def _check_project_structure(project_path: Path) -> bool: - """ - Check if project has teaching structure. - - Requires BOTH: - - syllabus/ directory OR syllabus.qmd file - - schedule.qmd file - - Args: - project_path: Path to project directory - - Returns: - True if both conditions are met, False otherwise - """ - # Check for syllabus/ directory or syllabus.qmd file - has_syllabus = ( - (project_path / "syllabus").is_dir() or - (project_path / "syllabus.qmd").is_file() - ) - - # Check for schedule.qmd file - has_schedule = (project_path / "schedule.qmd").is_file() - - return has_syllabus and has_schedule - - -# Example usage and testing -if __name__ == "__main__": - import sys - - # Test current directory - is_teaching, method = detect_teaching_mode() - print(f"Current directory:") - print(f" Teaching mode: {is_teaching}") - print(f" Detection method: {method}") - - # Test with command line argument - if len(sys.argv) > 1: - test_path = sys.argv[1] - is_teaching, method = detect_teaching_mode(test_path) - print(f"\nTest directory: {test_path}") - print(f" Teaching mode: {is_teaching}") - print(f" Detection method: {method}") From ceee138be1b8308e9c2593c5a4c7f8b8e4abd3fb Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Sun, 9 Aug 2026 00:09:18 -0600 Subject: [PATCH 13/24] =?UTF-8?q?docs(.status):=20session=20recap=20?= =?UTF-8?q?=E2=80=94=20OpenCode=20MCP=20bridges,=20delegation=20SPEC,=20te?= =?UTF-8?q?aching=20removal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .STATUS | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/.STATUS b/.STATUS index e0d054ca9..2c8d37b24 100644 --- a/.STATUS +++ b/.STATUS @@ -3,7 +3,55 @@ version: 4.5.0 progress: 100 release_date: 2026-08-07 milestone: **v4.5.0 SHIPPED 2026-08-07** — PR #329 (dev→main merge, tag v4.5.0, MINOR): new `repo-triage` skill (41st skill) batch-grounds open GitHub issues and stale worktrees/branches against current repo state, offering confirmed (never automatic) deletion/closure — built via brainstorm→grill (`GRILL-repo-triage-2026-08-07.md`, 2 factual corrections)→dispatched ORCHESTRATE worktree, code-reviewed (8 findings, all fixed pre-merge: jq-null pr_state bug, swallowed git-diff failures, not-merged-branch passthrough, tracked ORCHESTRATE artifact, stale skill-count prose, duplicated evidence-normalization + fence-extraction logic consolidated into `classifier_loader.py`, cross-repo file-check bug), merged PR #328 squash to dev first. Also shipped: 8 new ADHD-friendly ecosystem-tool cheat-sheet tutorials (Codex, Remember, i-have-adhd, Token Optimizer, Security Guidance, Claude HUD, Dropbox, Agent Skills) under a new nested `Ecosystem Tool Cheat Sheets` mkdocs subsection (`SPEC-ecosystem-tool-tutorials-2026-08-07.md`), plus a `docs/commands/hub.md` nav-completeness fix (`TUTORIAL-opencode-plugins.md` was missing from mkdocs.yml). Pre-flight found 2 non-blocking local-only failures (not regressions, CI confirmed green on the same commit): `test_branch_guard_dogfood.py` (the installed hook is a symlink to the sibling `cc-config` repo, out of scope per cross-repo permission boundary) and `test_no_live_legacy_check_skill_references` (was walking gitignored `.remember/` via raw filesystem `rglob()` — fixed to scope to `git ls-files`, genuine bug, fixed pre-release). Full suite 2632 passed/0 failed (post-fix); `pre-release-check.sh 4.5.0` clean; docs-staleness GREEN; live site + Homebrew formula + aggregator sync (claude-plugins PR #46, auto-merged) all confirmed v4.5.0; `verify-surfaces.sh` needed a local `homebrew-tap` `git pull` + `brew upgrade` + `claude plugin update` refresh before reporting ALIGNED (known pattern — the script reads the local tap checkout, not live GitHub). KNOWN, NOT FIXED: `aggregator-sync.yml` returns 404 by that exact workflow name in `claude-plugins` (the release skill's own docs name a workflow that doesn't exist under that filename) — propagation still worked via some other mechanism (PR #46 auto-merged), doc/workflow-name discrepancy left unswept, out of scope for this release. Release: https://github.com/Data-Wise/craft/releases/tag/v4.5.0 · PR: https://github.com/Data-Wise/craft/pull/329, https://github.com/Data-Wise/craft/pull/328. **Post-release same-day (2026-08-07):** doc audit found + fixed 3 stale-prose bugs the count-consistency gate can't catch (`REFCARD.md`'s version-box date/highlight + skill count, `skills-agents.md`'s "8 specialized agents", `docs/index.md`'s 5× "47 commands" + missing real install instructions — Quick Start only had the dev-only local-plugins symlink, added Homebrew + Data-Wise marketplace paths); added a `triage-your-repo` cookbook recipe for the repo-triage skill. Brainstormed → spec'd → adversarially reviewed a gate fix for this exact blind spot (`SPEC-doc-staleness-prose-gaps-2026-08-07.md`: 2 new Phase 7 checks, release-date claims + structured-line-scoped count prose; review killed a 3rd check that was falsified by its own target and caught 90+ false positives in the 2nd, both fixed pre-build; not yet implemented). Cross-repo: `homebrew-tap#211` fixed a stale `/craft:git:unprotect` caveat reference (command removed in v4) — first push tripped Formula Drift Guard (content changed, no revision bump), fixed with `revision: 1`; merged, verified live via `brew upgrade` (4.5.0 → 4.5.0_1, new caveat text confirmed). Follow-up noted, not built: `homebrew-tap`'s own `manifest.json` caveats prose has the identical staleness blind spot, out of scope for the craft-only SPEC. Prior: **v4.4.2 SHIPPED 2026-07-27** — PR #318 (dev→main merge, tag v4.4.2, PATCH): the one fix merged to dev since v4.4.1 (PR #317, closes #316). `preflight-check` skill made reachable by its declared identity — it lived at the pre-rename `skills/check/` location while declaring `name: preflight-check`, so directory-based skill discovery couldn't find it; moved to `skills/preflight-check/`, `/craft:check`'s public argument contract unchanged. New recursive fixture-backed identity tests inventory every command/skill directory-vs-frontmatter mismatch and command/skill name collision plugin-wide, rejecting new drift; 8 pre-existing mismatches + 3 intentional shim collisions ledgered as owned debt through v5.0.0. Test automation now targets stable Python 3.14 (`.python-version` pin, 5 GitHub Actions workflows aligned, drift-guard policy test) — the local-interpreter assertion in that test was found too brittle on review (hard-failed for any contributor not on 3.14) and fixed to skip instead before merge. `docs/NEWS.md`/`docs/index.md`'s own v4.4.2 release-note prose initially contained the literal `skills/check/` string, which the new #317 regression guard (`test_no_live_legacy_check_skill_references`) correctly caught as a repo-wide scan, not scoped to code — rephrased without the literal path, second commit. Full suite 2627 passed/0 failed/52 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.2` clean (10/10); docs-staleness GREEN; live site + Homebrew formula (fresh sha256) + aggregator sync + Craft MCP Release all confirmed v4.4.2; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.2 · PR: https://github.com/Data-Wise/craft/pull/318, https://github.com/Data-Wise/craft/pull/317. Prior: **v4.4.1 SHIPPED 2026-07-26** — PR #315 (dev→main merge, tag v4.4.1, PATCH): post-4.4.0 fixes only, no features. `scripts/branch-guard.sh`'s `2>&1`/`1>&2` false-positive (a command like `git commit -m "...(7 -> 9)" 2>&1` misdetected as file creation) was fixed live on the installed hook earlier this session but never ported to the repo's canonical copy, leaving `test_repo_copy_matches_installed` red and the bug reintroducible on reinstall — ported with 2 regression tests (implements `GRILL-rtk-hook-safety-close-out-2026-07-26.md` decision #3). `docs/help/refine-flag.md` and `docs/reference/COMMAND-PARAMETERS.md` fixed for PR #313's 9-command declarer set (was still 7, missing `/craft:plan`/`/craft:smart-help`) — new dogfood test guards against recurrence. `docs/index.md`'s "Latest" callout and `docs/NEWS.md` synced (both still described v4.3.0's content under a v4.4.0 label; NEWS.md had no v4.4.0 entry at all). Also triaged 7 remaining open issues (#228, #213, #199, #170, #169, #133, #108): closed #108/#213/#133, re-scope comments on #228/#170/#169, confirmed #199 valid. Full suite 2654 passed/0 failed/16 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.1` clean; docs-staleness GREEN; live site + Homebrew formula confirmed v4.4.1; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.1 · PR: https://github.com/Data-Wise/craft/pull/315. Prior: **v4.4.0 SHIPPED 2026-07-26** — PR #314 (dev→main merge, tag v4.4.0, MINOR): the one `feat:` commit since v4.3.1. `smart-help` declares `--refine` (D7, PR #313) — opt-in, default-OFF, closing the last dangling item on #268's prompt-refiner umbrella. `prompt-refiner`'s Default Policy table made exhaustive and re-keyed on command file path — fixed 3 stale display names, added 2 live callers it was silently missing (`commands/plan.md`, `commands/arch/plan.md`); a new dogfood test (`test_refine_default_policy_table_exhaustive`) guards against future drift. #268's other two proposed items (named destinations, `--scope global`) and #266 (multi-round grill mode) were grilled then killed on adversarial review before implementation — see `docs/specs/GRILL-prompt-refiner-remaining-2026-07-26.md`'s amendment; #266 closed pointing at savant's `edit-loop` (shipped 2026-07-13), which already solves the observed pattern with tooling craft structurally lacks. Full suite 2618 passed/0 failed/49 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.0` clean (10/10); docs-staleness GREEN; live site + Homebrew formula + aggregator sync all confirmed v4.4.0. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.0 · PR: https://github.com/Data-Wise/craft/pull/314, https://github.com/Data-Wise/craft/pull/313. Prior: **v4.3.1 SHIPPED 2026-07-25** — PR #312 (dev→main merge, tag v4.3.1, PATCH): two bug fixes merged to dev since v4.3.0, both traced to the same session. `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281) surfaced in the guard-bypass-marker confirms themselves (#309, PR #310) — the documented "Claude writes `.claude/allow-once`" one-shot flow was circular for the one case where the approved action IS creating/editing that marker; added the env-var hint to the three marker-specific confirms, reconciled both branch-guard reference docs. Bare-`vX.Y.Z` version-grep bug class swept repo-wide (PR #311) — the same false positive that broke the Homebrew release gate twice (v4.2.0, v4.3.0; fixed for `docs/index.md` in #308) also affected `docs.yml`'s live-site poll, `verify-surfaces.sh`'s `resolve_docs_site`, `version-sync-precommit.sh`'s blocking `.STATUS` pre-commit check, and manual release-verification docs — all re-anchored to the actual version signal instead of the first bare match, each with a regression test verified via planted-defect positive control. Full suite 2650 passed/0 failed/16 skipped/1 xfailed (tracked)/1 xpassed (known perf flake); `pre-release-check.sh 4.3.1` clean (10/10); docs-staleness GREEN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.1 · PR: https://github.com/Data-Wise/craft/pull/312, https://github.com/Data-Wise/craft/pull/310, https://github.com/Data-Wise/craft/pull/311. Prior: **v4.3.0 SHIPPED 2026-07-25** — PR #307 (dev→main merge, tag v4.3.0): `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281, PR #305) closing the guard-bypass-marker creation deadlock, reusing the #168 env-var pre-authorization pattern; new advisory-only `reference-scope-guard.sh` hook (#286, PR #306) warning on non-conforming filenames under `~/.claude/reference/`, sharing `install-guards.sh`/`guards.json` with `branch-guard`/`no-switch-guard`. Full suite 2648 passed/0 failed. **Homebrew Release recurrence:** the exact same `pre-release-check.sh` false positive that broke v4.2.0's automated tap push (worked around manually then) broke v4.3.0's too — `docs/index.md`'s version check grepped the first bare `vX.Y.Z` string in the file, landing on a historical "since v4.0.0" migration note instead of the actual version badge. Fixed at the source this time (PR #308, fast-tracked dev→main): matched the badge pattern specifically (`version-X.Y.Z`), same as the adjacent README.md check. Re-dispatched `homebrew-release.yml` via `workflow_dispatch --ref main -f version=4.3.0` (the original `release`-event run had checked out the tag commit before the fix existed, so a plain rerun would have failed identically) — all 3 jobs green, tap formula confirmed live (`v4.3.0.tar.gz`, fresh sha256). Craft MCP Release + Aggregator Sync both green from the original release event. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.0 · PR: https://github.com/Data-Wise/craft/pull/307, https://github.com/Data-Wise/craft/pull/308. Prior: **v4.2.0 SHIPPED 2026-07-19** — PR #296 (dev→main merge, tag v4.2.0): **BREAKING:** `/craft:done` renamed to `/craft:finish` (ADR-006) — matches the existing zsh `finish` alias, no back-compat shim, command count unchanged (48, a rename not an addition); `/craft:restore` spec/report status corrected DRAFT→SHIPPED (had already shipped in v4.1.0 when written). Full suite 2649 passed/1 failed (a genuine broken-link regression from the rename sweep, `docs/commands/brief.md` still linked pre-rename `done.md` — caught by the test run itself, fixed same-commit)/10 skipped. Also merged to dev this cycle (not yet in a tagged release): PR #297 (`utils/docs_detector.py`'s `_extract_command_name()` truncated multi-hyphen command names past a 500-char read window, bumped to 2000), PR #298 (extends `/craft:finish` Step 1.10.5 with a `~/.claude/projects//memory/`+`.remember/` staleness check, 14-day threshold, report-only), and a `docs/reports/` folder established as the `/savant:report` output convention (moved `REPORT-craft-restore-2026-07-17.md` there, added to `scripts/config/exclusions.txt` alongside `docs/specs/`). **Homebrew:** v4.2.0's automated tap-formula push failed — `homebrew-tap`'s `main` gained branch protection (PR + 2 required checks) since `homebrew-release.yml` was last touched, so its direct `git push origin main` was rejected (GH006); recovered manually via `homebrew-tap`#164, which surfaced 2 more CI-gate bugs fixed directly in that repo (#165: `check-revision-bump.sh` false-positived on URL-tag-versioned formulas like craft's, no explicit `version` field to read; #170: the ".STATUS conflict markers" required check was path-filtered to only `.STATUS` changes, so it could never fire — and the PR stayed BLOCKED forever — on formula-only PRs). craft's own workflow fixed to match (PR #299: push a bot branch + open/auto-merge a PR instead of pushing directly), landing independently from a background-dispatched session whose worktree had gone stale after only completing 1 of the 2 related craft/homebrew-tap fixes — recovered by inspecting the worktree directly rather than trusting the dispatch's completion state. Release: https://github.com/Data-Wise/craft/releases/tag/v4.2.0 · PR: https://github.com/Data-Wise/craft/pull/296. Prior: **v4.1.0 SHIPPED 2026-07-17** — PR #295 (dev→main merge, tag v4.1.0): `/craft:restore` (NEW root command, combines `dev/git` skill's mode-aware git-activity recap with `adhd-workflow`'s `.STATUS`-based session recap into one read-only entry point; replaces the deleted `git-recap`/`recap` commands; no `--sync`; a cross-plugin `/restore` dispatcher to `savant:restore` was investigated and explicitly descoped — router can't dispatch cross-plugin) + `/craft:refine` confirm-flow fix (a 2026-07-01 design — fenced refined-prompt block + 4-way confirm: Execute now/Copy for elsewhere/Edit first/Skip — was locked but never implemented; now shipped across all 5 `--refine` callers + standalone, closing the "asks for acceptance without printing first" bug) + full post-v4.0.0 doc-staleness cleanup (47→48 count-drift sweep across ~35 files, mermaid health score 82.3→100 on new diagrams, `docs-staleness-check.sh` RED→GREEN). New architecture docs: `docs/architecture/craft-restore-pipeline.md`, `docs/architecture/prompt-refiner-pipeline.md`. Homebrew formula PR data-wise/homebrew-tap#153 open (tested locally: `brew style`/`brew audit --strict`/`brew install` all clean, 48 commands). Full suite 2650 passed/0 failed. Release: https://github.com/Data-Wise/craft/releases/tag/v4.1.0 · PR: https://github.com/Data-Wise/craft/pull/295. Prior: **v4.0.0 SHIPPED 2026-07-16** — PR #294 (dev→main merge-commit `678e4d4a`, tag v4.0.0): craft docs-authoring surface extracted to the standalone `folio` plugin (folio v1.0.0 released FIRST, per the rollback runbook's ordering). Counts 94→47 commands / 45→40 skills / 8→2 agents. Homebrew formula regenerated with real values (sha256, command_count=47) via homebrew-release.yml (3/3 jobs green); `brew audit`/`brew upgrade` verified clean for both craft and folio formulas. Post-release docs sweep (same session): commands/hub.md + its docs/ mirror had drifted badly (stale example counts, 2 stray text artifacts, a main-menu ASCII template whose header counts didn't match listed items and referenced commands moved to folio or folded into skills); rewrote to match the real 47-command roster. API-REFERENCE-COMMANDS.md (1850 lines, last synced v1.24.0) converted to a thin index into docs/commands/*.md + docs/REFCARD.md rather than re-syncing a third documentation surface indefinitely; DEPENDENCY-ARCHITECTURE.md left untouched (its craft-vs-folio ownership question is still open, test_integration_dependency_system.py:38). Full pytest suite 2641 passed/0 failed. KNOWN, NOT FIXED: docs/REFCARD.md has the same staleness pattern across multiple sections (Site/Distribution/CI/CD subtotals, 20+ stale `/craft:git:worktree` examples for a command folded into the dev/git skill) — flagged, not yet swept. Folio's Aggregator Sync workflow fails by design (folio is a new plugin, never added to the shared Data-Wise aggregator 's marketplace.json — aggregator-sync.sh deliberately refuses to silently add unknown plugins; adding folio there is a separate, reviewed decision). CP-4 tasks/ cleanup: craft's tasks/ deleted directly on dev (unprotected); folio's tasks/session-plan-T2.4-CP2.md removed via PRs #15 (dev) and #16 (main) rather than a direct-branch delete (blocked by the permission classifier). Release: https://github.com/Data-Wise/craft/releases/tag/v4.0.0 · PR: https://github.com/Data-Wise/craft/pull/294. Prior: **v4 FOLIO SPLIT MERGED to dev 2026-07-16** — PR #290 (merge-commit `4af10f27d`, phase history preserved for the eventual v4 release tag): craft's docs-authoring surface extracted to the standalone `folio` plugin. **Counts 95/45/8 → 47/40/2.** NOT yet released (v2.61.2 still the current tag); `feature/folio-split` worktree + branch PRESERVED — Phase 4 (folio `/folio:do` + coordinated release, folio first / craft tag LAST) rides it. Phase 3.6 (router consolidations) CLOSED at **1 router of 5 planned**: `code:audit` shipped shrunk 5 cmds→2 (the `audit-router` skill, D12); `orch` (D11), `arch` (D6), and `ci` (D13) all DROPPED after reading the command bodies in full; `plan:feature` stayed excluded (pre-existing D2 lock). Root cause of the 4 drops, recorded in `docs/specs/GRILL-phase-3-6-router-consolidation-2026-07-15.md`: **the grill locked load-bearing decisions from line counts and flag names without reading the bodies** — every family that looked cohesive from surface metrics collapsed on a full read. D13 also withdrew its own planned `repo`-flag normalization as a premise artifact (no validator enforces cross-family flag consistency; the distinguishing test is *find the thing that fails if it's wrong*). Real defects fixed: 4 `category:` metadata mismatches (`ci/{fix,local}.md` declared `code`; `orch/{drive,workflow}.md` declared `orchestrate` against a nonexistent dir) — flagged by `help_file_validator._check_category_mismatch`, live-wired via `docs_update_orchestrator` → `/craft:docs:update`; a repo-wide sweep found the `orch` pair (pre-existing on dev). **Repo-wide category mismatches now 0.** T3.4 command floor 60→40 (`ci.yml:92`): the floor is a mass-deletion guardrail and had gone stale TWICE (86→60) while the real count fell 94→70→47, leaving it ABOVE the surface and hard-failing CI every run; rationale now commented in-file. `tests/test_git_shim_correctness.sh` INVERTED (not deleted) — it asserted `commands/git/*.md` were thin shims, but T3.5.2 completed that migration by deleting all 11, making the assertions unsatisfiable (7 FAIL on the branch, 21/21 on dev); it now guards the migration's END STATE (shims stay deleted · Operations 1–12 survive in `skills/dev/git/SKILL.md` · `issue-check` stays live · no unsanctioned command reappears), verified by 3 planted defects. `/craft:git:issue-check` KEPT as a live command (NOT folded into the git skill, reversing the initial plan): `tests/test_issue_check_unit.py` extracts its `classify_issue()` block from `commands/git/issue-check.md` and `exec()`s it, so the command file is the classifier's single source of truth — the same constraint as `commands/ci/triage.md` (D13). Git's directory-rename detection had silently relocated dev's new command into `skills/dev/git/references/` during the merge, which would have broken that test. Merge resolved 34 conflicts (~30 count-cascade, dev 95/45/8 vs branch 46/40/2) — every tracked count recomputed mechanically via `bump-version.sh --counts-only` rather than hand-resolved; the guard-script conflicts expected in `no-switch-guard.sh`/`install-guards.sh`/`version-check.sh` did NOT materialise. Prior: **PR #291 MERGED to dev 2026-07-16** (squash `88b221da9`): `ci-bash-suites` validator (`.claude-plugin/skills/validation/ci-bash-suites.md`) — closes the **gate-divergence** gap that caused two of #290's three CI blockers. `/craft:check` never ran the shell suites CI invokes directly because pytest doesn't collect them, so the local gate reported green for a surface it never touched; T3.5.2 shipped a regression citing "full pytest 2575 passed" while a CI-required bash suite had been red for days. Derives its suite list FROM `.github/workflows/*.yml` rather than hardcoding (craft has 22 `tests/*.sh`, CI runs **6** — the parse corrected a 4-suite assumption held all session); advisory in default mode, blocking in `release`, per the release-gated validator contract (`CRAFT_MODE` can't distinguish `--for pr` from `--for commit`). Verified by replaying the REAL T3.5.2 bug (pre-fix suite restored from `d41de18ee`) → caught. No count cascade (validators sit outside the counted surface). Also 2026-07-16: two stale claims fixed in `docs/commands.md` (`5034aef7d`) — a dry-run tip saying "46 commands" (the old TOTAL pasted into a dry-run claim; now 27) and "46 commands for documentation generation, validation, and site management" (now 2 — that sentence described a surface craft no longer has). Local `~/.claude/hooks/branch-guard.sh` re-synced via `install-guards.sh` (#290 merged the v4 hook text into dev, so the installed hook predated the repo and was advising a DELETED command, `/craft:git:guard disable branch-guard`) — this cleared the last known local test failure; dogfood 51 passed/0 failed. ⚠️ KNOWN, NOT FIXED: `docs/commands.md:10`'s "17 Categories" list is stale (`discovery-usage` demoted, `orchestrate`→`orch`, no `workflow` dir) but "category" has two defensible definitions that disagree — frontmatter-declared gives 10 (…`smart`), directories give a different 10 (…`utils`), and the current list matches neither; left for a decision rather than guessed at. Prior: Guard-hardening arc SHIPPED to dev 2026-07-15 — PR #287 (cumulative-cwd cd-target resolution, both guard hooks), PR #288 (guards.json write-race mkdir-lock, both writers), PR #289 (plan-orchestrator dispatch hardening); full detail in the 2026-07-15 Session Recap below. Counts unchanged 95/45/8; NOT yet released. Prior: PR #283 SHIPPED to dev 2026-07-14 (squash `057ea6785`): `/craft:git:issue-check ` — before implementing a fix requested by an open GitHub issue, checks whether the issue's premise still holds against current code (structured verdict: valid/moot/unclear, always cited evidence, never cached, advisory-only). Also gates `/craft:orch:drive` with a cheap `#NNN`-citation pre-filter and corrects `/recap`'s pointer copy. Built via `/craft:brainstorm` → `/craft:grill` (9 locked decisions in `docs/specs/GRILL-github-attention-triage-2026-07-14.md`; adversarial-review pass caught and corrected a factually-wrong dogfood target — issue #199 assumed moot, actually still open) → `/craft:plan` (plan-orchestrator, `ORCHESTRATE-github-attention-triage.md`) → `orchestrate-dispatch`. Dispatched agent completed all 5 phases correctly but silently failed to commit Phase 5 or update its own checkboxes before stopping mid-sentence — caught by the hang-detection cross-check (notification text vs. ORCHESTRATE file state), not a code failure; recovered by verifying git log + a fresh full suite run, fixing 5 newly-stale "94 commands" doc refs the count-cascade tooling missed, then committing + correcting checkboxes. PR CI initially failed on a genuine regression (broken relative link in the ORCHESTRATE file's own header, fixed same session) plus the 3 already-known `test_v115_adhd_enhancements` pre-existing failures; merged via `--admin` once confirmed unrelated. Counts 94→95/45/8; NOT yet released (v2.61.2 still current tag). Prior: Docs/website audit COMMITTED to dev 2026-07-09 (`d6aaa9096`) — 264 deleted-command refs stripped across 40 files, 5 whole-docs archived to `docs/archive/deleted-commands-v3/`, `mkdocs build --strict` clean. FOLIO SPLIT PLANNED (new initiative, supersedes dropped Phase 3): grill ledger `docs/specs/GRILL-folio-split-2026-07-09.md` (5 locked: tools+docs-standards scope · caller-based cut · drop DOCS from /do+hub · dup count tooling · history-preserving subtree, craft v4/folio v1) + `docs/plans/ORCHESTRATE-folio-split.md` (5 phases, P0 gates). P0 caller-audit RAN via dynamic Workflow (7 agents, adversarially verified, partition_sound): STAYS = docs:update + docs:changelog + site:deploy-as-shell; MOVES = 26; flagged docs:claude-md:* trio as craft-internal governance (likely stays) — partition NOT yet recorded into the plan docs. EXECUTION STARTED 2026-07-09 ("build auto"): worktree ~/.git-worktrees/craft/feature-folio-split live (T1.1 done, ORCHESTRATE + tasks/ moved to branch root); T1.2 folio repo creation gated on ask. ⚠️ ACCOUNT CI: zero-CI mode active across private repos since 2026-07-09 (craft is public = unaffected/free) — see memory `github-actions-zero-ci-strategy-2026-07` + savant .STATUS for the full record. Prior: Phase 1 native-first prune MERGED to dev 2026-07-09 — PR #279 (squash `4fa44fbb`): pruned 21 dead-namespace commands (115→94, −5533 lines) + excluded dead docs (specs/plans/archive) from the built mkdocs site. ⏹ NATIVE-FIRST THINNING REFUTED: a Stage-2 dynamic workflow (3 pilots × 4-axis gate + cross-plugin routing test) returned NO-GO on Phase 2 — the `do`/`hub` router structurally can't dispatch cross-plugin, AND 3/3 pilot replacements (native `/simplify`, `/code-review`, `agent-skills:ci-cd`) aren't ≥ craft on all 4 axes (weaker/auto-apply, third-party dep, or generate nothing). Phases 2 (cascade thinning) + 3 (`folio` docs/publishing split) DROPPED; folio only revisitable on docs-maintenance merits, grilled fresh. ADR-002 rich-body gate held mid-build: 3 of 24 "safe" deletions (`git:docs:refcard`, `check:gen-validator`, `workflow:insights`) carried unique logic → restored, not deleted (caught by `test_skill_referenced_commands_exist`). Verification gap fixed: pytest passed 2676 but CI's Validate-Plugin-Structure bash suite (`test_git_shim_correctness`) asserted deleted `git/{sync,git-recap}.md` exist → shim array updated (see memory `pytest-doesnt-cover-craft-ci-bash-suites`). Counts now 94/45/8 on dev; NOT yet released — v2.61.2 still the current tag. Full record: `docs/specs/SPEC-craft-native-first-breakup-2026-07-09.md` OUTCOME block. Worktree `feature/craft-native-first-breakup` removed + remote branch deleted 2026-07-09; local branch ref left stale (branch-guard blocks force-delete of squash-merged branch — documented deadlock, harmless). Prior: v2.61.2 SHIPPED 2026-07-09 — PR #275 (dev→main merge, tag v2.61.2) + PR #276 (CI fix): docs/NEWS.md release highlights page, CHANGELOG/VERSION-HISTORY v2.61.1 section, REFCARD.md version box corrected (2.61.0→2.61.2, counts 117→115), exclusions.txt archived-doc path fixed. PATCH bump. Docs Staleness + Markdown Linting GREEN; Validate Plugin Structure + Link Validation pending (known hang). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.2. Counts unchanged 115/45/8. Prior: v2.61.0 SHIPPED 2026-07-06 — PR #271 (dev→main merge `6c3e2e95`, tag v2.61.0): do.md dead-agent-dispatch removal + 24-doc sweep, `verify-surfaces.sh --report-only`/`--version` + 2 new legs (GitHub release, docs site) + release-rollback runbook, 4 confirmed branch-guard/no-switch-guard gaps closed, quota tooling removed (all from PR #270, squashed to dev pre-release), plus post-merge docs-audit fixes: 2 dead-agent surface-forms PR #270's hyphenated-only grep missed (`docs/index.md`'s feature grid, `skills/orchestrator-resilience/SKILL.md`'s fallback-dispatch example), a docs-staleness false positive (`orch.md`'s mode-table "4 agents"), and backfilled `CHANGELOG.md`/`docs/CHANGELOG.md` `[Unreleased]` entries missed during #270's own PR prep. MINOR bump. Full suite 2707 passed/1 pre-existing failure (`test_roadmap_orchestrator_enhancements`, stale `ROADMAP.md` path predating this release — root-caused and FIXED post-release in `dc6ee28a`, corrected to `docs/archive/ROADMAP.md`; this exact failure had forced `--admin` overrides on PR #270, PR #271, and `main` directly, three releases running). Merged via `--admin` (same pre-existing failure, confirmed by log each time). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.0 · PR: https://github.com/Data-Wise/craft/pull/271. Downstream: docs site live at v2.61.0 (confirmed via curl), tap formula confirmed v2.61.0 (live raw.githubusercontent fetch, not local clone), Homebrew Release + Aggregator Sync + Craft MCP Release workflows all SUCCESS. `dev` fast-forwarded to `main`'s merge commit, pushed. Counts unchanged 115/45/8. Also this session: grilled and CLOSED the orchestrate token-usage-hooks backlog (captured 2026-07-02) with NO build — two review passes (backend/architecture + interface/CLI-contract lenses) found hook 1 infeasible (no live token-introspection API exists for a prompt-driven orchestrator session), hook 3 structurally uncomputable for an in-progress run; the one surviving feasible piece (`--token-report`) wouldn't have fixed the retroactive-analysis problem that motivated the backlog, and duplicates the accounting-tooling category this same session already removed (quota tooling) — see `docs/specs/GRILL-orchestrate-token-usage-hooks-2026-07-06.md`. Prior: v2.60.0 SHIPPED 2026-07-05 — PR #269 (dev→main merge `763c5e8c`, tag v2.60.0): command-namespace reorganization (full grill-and-implement cycle, `docs/specs/SPEC-command-namespace-reorganization-2026-07-05.md`) — root promotions `/craft:next`/`/craft:done`/`/craft:refine`/`/craft:brief`/`/craft:brainstorm` (heavy real-world use); `orchestrate`→`orch` rename across ~110 files; `ci:` consolidation (`code:ci-fix`/`code:ci-local`→`ci:fix`/`ci:local`); new `docs:generate` router (9 generators, none merged — genuinely distinct logic); new `task:` namespace; `/craft:quota` deleted (folded into `/craft:orch` pre-flight); `code:desktop-watch` deleted (was a pure redirect shim); `adhd-guide` converted to a skill reference. Pre-flight caught and fixed 8 real test failures before release: 6 stale "117 commands" claims (actual 115) across dist/docs files not covered by `bump-version.sh --counts-only`'s file list, one hardcoded pre-rename path in `test_drive_command_in_nav`, and a missing TOC on the newly-moved `adhd-guide.md` (>300 lines, tripped the skill-standards release gate). MINOR bump. Full suite 2707 passed/0 failed/5 skipped; pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.60.0; homebrew-release workflow SUCCESS (tap formula confirmed v2.60.0 via live `gh api`, NOT the stale local `~/projects/dev-tools/homebrew-tap` clone which still read 2.58.0); aggregator PR #19 auto-merged in `Data-Wise/claude-plugins`; post-release sweep ALL CLEAN. Counts 115/45/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.60.0 · PR: https://github.com/Data-Wise/craft/pull/269. Prior: v2.59.0 SHIPPED 2026-07-04 — PR #260 (dev→main merge, tag v2.59.0): `plugin-audit` skill (44→45, cross-plugin command/skill collision detector, schema + false-positive fixes before merge, #237) + docs-site staleness hardening H1/H2/H3 (`docs.yml` live-site post-deploy version poll, stale-count guard extended to all of `docs/**/*.md`, #259). Counts 116/45/8 (later corrected to 115 in v2.60.0 after the `resume.md` deletion cascade settled). Prior: v2.58.0 SHIPPED 2026-07-02 — PR #251 (dev→main merge, tag v2.58.0): distribution-surface hardening — curl-pipe installer fixed to clone current `main` instead of a frozen v1.16.0 mirror (D1, 11 contract tests); homebrew-tap copy-based install (no symlinks); net-new `mcp/` TS MCP server (craft-mcp Desktop bridge, 3 read-only tools, DXT/.mcpb); esbuild audit-clear. Counts 116/44/8. Prior: v2.57.0 SHIPPED 2026-07-01 — PR #242 (dev→main merge `ec5797e6`, tag v2.57.0): grill attack-angles (#238 — 7 project-agnostic adversarial axes, angle 3 reads target project's own CLAUDE.md/ADRs at runtime; skill count 43→44) + orchestrate-family wave-1 (#239 — deleted fictional 523-line `resume.md` session-teleportation command, command count 117→116; thin-shimmed `orchestrate:plan` to `plan-orchestrator` skill) + doc-impact rubric extension (#241 — 4 new scored types Tutorial/API/Cookbook/Architecture-doc with tiered thresholds + arch-doc double-count subtraction rule + advisory Site Consistency checklist) + orchestrate-dispatch mode (#240 — 3rd `--output` value dispatching a background Agent from the live planning session, with confirm-before-dispatch gate, scoped concurrency cap, failure/hang detection at 2×-effort-estimate window, resumability, scoped `.STATUS` auto-write). MINOR bump (4 feat/refactor PRs). #240/#241 were background-agent dispatches using the very pattern #240 formalizes — monitoring protocol caught 1 self-introduced broken-link regression (a cross-tree link into `~/.claude/...` from this session's own BRAINSTORM doc, fixed on dev) + recovered a killed agent's completed-but-uncommitted Phase 3 work. A CHANGELOG merge-order conflict between #240/#241 (both added `[Unreleased]` entries) was resolved by rebase, keeping both. Pre-release-check initially reported doc-coverage as BLOCKING due to a macOS bash-3.2 `mapfile` incompatibility (false positive — confirmed PASS, 0 gaps, via Homebrew bash 5.x/zsh). Full suite 2072 passed/7 failed/13 errors (documented env-caused baseline, not regressions); pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.57.0; homebrew-release workflow SUCCESS (formula confirmed v2.57.0, desc "116 commands"); post-release sweep ALL CLEAN. Counts 116/44/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.57.0 · PR: https://github.com/Data-Wise/craft/pull/242. Prior: v2.56.0 SHIPPED 2026-07-01 — PR #234 (dev→main merge `43beb7b3`, tag v2.56.0): token-usage reduction (PR #232 squash `e6c24a27` — orchestrator model pinning, `/refine` 631→42 lines, `/brainstorm` redesign, new `command-skill-token-efficiency` skill) + release-tracking hardening (namespace-refactor token-cost go/no-go measured 68.3% reduction; broken `/usage` scheduled-trigger replaced with `ccusage`+`claude-monitor`; 2 cross-spec factual bugs fixed via a second grill pass). MINOR bump (feat: #232). All CI checks GREEN on first pass — no `--admin` needed (Validate Plugin Structure resolved cleanly this time). Full suite 2089 passed; pre-release-check PASSED; main CI GREEN; live site confirmed v2.56.0; homebrew-release workflow SUCCESS; post-release sweep ALL CLEAN. Counts 117/43/8. Prior: v2.54.0 SHIPPED 2026-06-27 — `/craft:workflow:brief` (117th command): 3-line action block generator (Next step / Watch out for / Connects to) with `--plan`/`--verbose`/`--show-context` flags; `--brief` integrated into `/craft:do` Step 5.5. 29 e2e + 12 dogfood tests. Main CI GREEN; docs deployed; homebrew tap updated (117 commands); post-release sweep CLEAN (getting-started.md fixed); all BLOCK surfaces aligned. Counts: 117 / 39 / 8. Prior: v2.53.0 SHIPPED 2026-06-26 — multi-surface-aware release (surface registry + aggregator CI propagation + Cowork verify leg + `/craft:dist:surfaces`, #218/#184) + docs-staleness fix (docs/plans/ excluded from count-consistency scan). MINOR bump (feat). All tests pass; main CI GREEN; docs deployed; homebrew tap updated (116 commands); post-release sweep ALL CLEAN; aggregator sync wired (all 4 satellite repos). Counts 116/39/8. Prior: v2.52.0 SHIPPED 2026-06-26 — interactive-by-default commands (#215) + default-on test/docs scaffolding (#216) (PR #217 dev→main merge `c5f704f3`, tag v2.52.0, `--admin` on stuck Validate-Plugin-Structure): grill/orchestrate AskUserQuestion model (Recommended-first + per-option consequence) + `--yes`/`--non-interactive`; refine default-on for brainstorm/do/plan:feature/grill (opt-in elsewhere) + `--yes` cascade + orchestrate Step 0.5 structured model; AND brainstorm/plan:feature/grill emit a tier-inferred test-plan + doc-scorer Documentation section BY DEFAULT (`--no-tests`/`--no-docs`; arch:plan/spec-review opt-in via `--tests`/`--docs`), logic in skills (brainstorm-insights/plan-orchestrator, deprecation-trap-safe + guarded), doc-scorer REUSED, lifecycle-split, count-cascade-excluded. Built spec→grill→plan→subagent-driven-TDD (2 plans, 7+8 tasks, per-task spec/quality + opus whole-branch reviews; gates caught a dropped-phrase regression, a refine over-flip, a decorative deprecation-guard sentinel, and a MEMORY.md over-compaction that dropped 37/102 pointers → all fixed). MINOR bump (4 feat). Full suite 1990 passed; pre-release-check PASSED; main CI GREEN; docs deployed (live site 2.52.0); Homebrew Release auto-updated tap; verify-surfaces 6/6 ALIGNED (refreshed local lag: brew formula 2.51→2.52 via `--formula`, marketplace-update-THEN-plugin-update for the Code pin); post-release sweep ALL CLEAN. Counts 115/39/8. Prior: v2.51.0 SHIPPED 2026-06-25 — governance retro (#205) + skill-standards release gate (PRs #211/#212→dev, #214 dev→main merge `180181f8`, tag v2.51.0; CLOSED #205 + #138 via default-branch auto-close): D1 **R09 status-not-drift** governance check (`governance/checks/status_drift.py`, warn) — audits `.STATUS` `version:` vs manifest + SHIPPED/tagged vX.Y.Z vs git tag, fixture `--selftest` + LIVE in `pre-release-check.sh` (this very release was self-gated by it: ".STATUS: version + release claims match" ✓); D3 ADR-003 (release-drift advisory not hard-gate + P7 freeze); skill-standards validator graduated to a **release-tier gate** (`CRAFT_MODE=release` blocks, advisory below) + fixed a latent CRAFT_MODE no-op (LLM-prose command never exported the gating var → gate+version-check silently dead). MINOR bump (2 feat: #211/#212). Full suite 1970 passed; pre-release-check PASSED; main CI GREEN; **verify-surfaces 6/6 ALIGNED** (after refreshing local lag: brew upgrade 2.50→2.51, `claude plugin update` 2.50→2.51, pulled stale `~/projects/dev-tools/homebrew-tap` checkout — the script reads that local clone FIRST, was the only "blocker"); post-release sweep ALL CLEAN. Counts 115/39/8. Process: merged #211/#212 (conflict-free despite branches behind dev — `--stat` looked conflicted but they authored ZERO changes to `.STATUS`/`CLAUDE.md`/`governance.md`; verified via `merge-tree --write-tree`), pushed a local-only gate branch first (data-loss risk), `git:clean` removed 2 worktrees (11+2 squash-merged branch refs left dangling — branch-guard blocks `-D`, harmless). Prior: v2.50.0 SHIPPED 2026-06-25 — v2.49.x issues sprint (PRs #206/#207/#208→dev, #209 dev→main --admin on phantom Validate Plugin Structure, tag v2.50.0): advisory skill-standards validator in `/craft:check` (hot-reload, never fails); homebrew dist-gates `verify_caveats.py`+`post_install_check.py` wired into `/release` Steps 10b/10c (advisory, strict via HOMEBREW_GATE_STRICT) + 10d (BLOCKING aggregator-sync + Cowork verify) (#200/#199); SessionEnd insights facet hook + idempotent installer, per-session-id dedup (#183); ADR-001 Workflow-vs-worktree (#171); +14 e2e/dogfood tests pinning locked decisions. Built via craft:grill (10 decisions D1–D10) → superpowers writing-plans (4 TDD tracks) → 2 dynamic Workflows (impl+verify, ran from dev session under branch-guard bypass, re-protected after). MINOR bump (3 feat: commits). Full suite 1958 passed; pre-release-check PASSED; main CI GREEN; tap+brew+Code+marketplace all 2.50.0 (verify-surfaces 6/6 ALIGNED); post-release sweep CLEAN. KEY INCIDENTS (recovered): (1) #206 squash folded UNPUSHED local-dev base commits → #207/#208 phantom conflicts → rebased --onto origin/dev to untangle (memory squash-folds-unpushed-dev-base-commits; push dev before PRing off it); (2) GitHub comma-list `Closes #200, #199, #183, #171` only auto-closed #200 (+#171 via Track-0 trailer) — each issue needs its OWN keyword; closed #183/#199 manually. Counts 115/39/8. Prior: v2.49.0 SHIPPED 2026-06-23 — skill-standards auditor + /done memory/settings + ADR-002 + release re-split (PR #203 auditor→dev, #204 release dev→main --admin, tag v2.49.0): `/craft:code:skill-standards` batch-audits `skills/**/SKILL.md` vs a vendored copy of Anthropic's standards (score+exit 0/1/2, `--json`/`--markdown`, `--fix` = version-tag-strip + frontmatter-normalize + TOC-stub, never prose; `--refresh-standards`); `/done` Memory Optimize (Step 1.12) + Settings Sync (Step 1.10.5); ADR-002 consolidated `/done` into the `adhd-workflow` skill (`references/done.md` SoT + thin shim); 4 oversized skills split to `references/` (release re-split loss-gated after a lossy first attempt, 0 lines lost) → 39 skills 100/100. Counts 114→115. Release: dev was unsynced with main (v2.48.0 release merge commits) → dev-wins sync merge resolved the PR #204 conflict; full suite 1929 passed; main CI GREEN; docs deployed; tap synced. POST-RELEASE TOOLING (homebrew-tap + savant): root-caused the local Code-registered lag — the formula `post_install` ran `claude plugin update` WITHOUT first `claude plugin marketplace update local-plugins` (no-op on a stale cache); fixed in `homebrew-tap/generator/generate.py` for ALL claude-plugin formulas; reconciled manifest-drift across 6 formulas (rforge-orchestrator's `deprecate!` was at risk of silent un-deprecation; himalaya-mcp 12 skills/22 MCP tools; scholar 28→34; craft `{command_count}` single-source token) gated by regen==committed; added a Formula Drift Guard CI workflow (regen-vs-committed, first run GREEN); savant `CLAUDE.md` 19→25 skills. Memories: post-install-marketplace-refresh-before-update, deprecated-command-rich-body-trap, skill-authoring-tooling-landscape. Prior: v2.48.0 SHIPPED 2026-06-23 (grill + orchestrate Step 0.5 Clarify). Prior: v2.47.0 SHIPPED 2026-06-21 — Governance roadmap COMPLETE (PR #196/#197→dev, #198→main, tag v2.47.0): R04 automated as content-drift (manual→checks/no_drifted_copy.py — installed SKILL.md must be byte-identical to canon; DISTINCT from R07 version-pin, catches a hand-edited body even when the version matches; gates session, vacuous-skip otherwise) + #184 release pre-flight advisory (pre-release-check.sh runs run_rules.py --json, prints RED count, NEVER blocks — gentle-ramp; TestReleaseGuard184 invariant) + live-flow test coverage (e2e soak lifecycle hook→ledger→promote-check + R04-in-audit; dogfood ledger edges). Governance suite 47→59. KEY FINDING (memory r04-content-drift-not-filetype): naïve "real-dir=copy" heuristic was dead (installs are real dirs, no symlinks) → content-drift; always fire a new WARN checker on the live env first (a false positive poisons the soak ledger). #198 merged CLEAN (no --admin). Homebrew tap synced to v2.47.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Governance now: R01–R04+R06 automated · R05 advisory · R07 external · R08; 5 surfaces (visibility·prevention·promotion·cross-repo·release-advisory). Prior: v2.46.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #3 (PR #194→dev, #195→main, tag v2.46.0): soak-then-flip promotion + cross-repo wrapper, completing Phase 2 (visibility · prevention · promotion · cross-repo). soak.py = LOCAL gitignored STATE.json ledger (first_seen/last_seen/last_red per rule) fed by the SessionStart hook each audit; run_rules.py --promote-check (--window 14d, --state) lists warn rules soaked clean long enough to recommend a human warn→error flip (machinery recommends, human promotes; advisory exit 0). run.sh = cross-repo wrapper (consumers invoke the one installed engine, cwd-portable, no drift, R07). 8 dogfood tests (injected today → no flake), 47 governance suite green. Bundled #193: test_performance_cached_run → xfail(strict=False) (wall-clock microbenchmark flaked on shared CI, forced --admin; now a soft signal) — and #195 MERGED CLEAN with NO --admin (the xfail paid off). Homebrew tap synced to v2.46.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. R04 automation + #184 release-pre-flight guard remain deferred. Prior: v2.45.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #2 (PR #190→dev, #192→main, tag v2.45.0): SessionStart visibility hook (governance/session_hook.py) — audits the live ~/.claude/skills tree at session open and injects a compact RED-only summary into context via SessionStart additionalContext; silent when clean, mtime-cached, no-op where skills tree absent, never breaks a session on error. Visibility not prevention (SessionStart hooks inject context, can't block). Completes the three-surface model: pre-commit + CI (prevention) · SessionStart (visibility). Implemented direct-to-worktree from the dev session (worktree-path-aware). #192 admin-merged (strict up-to-date BEHIND from the v2.44.0 merge-commit divergence; PR-context full suite GREEN). Homebrew tap synced to v2.45.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Global install of the hook DEFERRED to post-v2.45.0 (point at released plugin path; docs in governance/README.md). PR #3 (soak-then-flip + cross-repo wrapper) + R04 deferred. Also: removed leaked ORCHESTRATE-guard-suite.md (PR #191). Prior: v2.44.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #1 (PR #188→dev, #189→main, tag v2.44.0): R03 automated (manual→script — checks/no_private_in_public_marketplace.py scans a marketplace manifest, fails on a public-marketplace ref to a private/PII repo, denylist {savant}, dual-mode file/dir + good/bad fixtures; closes the last error-rule enforcement gap), pre-commit governance-gate hook (^governance/ → selftest + render --check drift) + CI gate in ci.yml (with explicit "CI does NOT evaluate R01/R07 live-env" note), engine {marketplace} substitution through audit()+selftest() + --marketplace. Built from a draft→adversarial-verify→ultra-finalize workflow chain (verified packet); implemented directly into the worktree from the dev session (branch-guard is worktree-path-aware — corrected a stale assumption). Also corrected stale "not yet wired" governance docs. #189 admin-merged (only failure was the flaky test_performance_cached_run timing test, unrelated to the diff; PR-context full suite GREEN). Homebrew tap synced to v2.44.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 PR #2 (SessionStart hook + soak-then-flip + cross-repo wrapper) NEXT; R04 deferred. Prior: v2.43.0 SHIPPED 2026-06-20 — Skill-Ecosystem Governance (Phase 0) (PR #185→dev, #187→main, tag v2.43.0): policy-as-code under governance/ — RULES.yaml (8 skill-location rules, single source of truth), run_rules.py (audit + --selftest meta-validation, fail-closed), render_rules.py (CLAUDE.md block generation + --check drift gate), portable checkers + good/bad fixtures, guide + Plugin Release Runbook, 24 e2e/dogfood tests + governance pytest marker. PR #185 review hardening (all 5 issues): fail-closed gating (broken checker on error rule exits 1), recursive broken-symlink detection, R01 gates session (not ci) + visible canon-skip, --check exit-2, --selftest surfaces external rules, UTF-8. Homebrew tap synced to v2.43.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 enforced-gates roadmap spec open in PR #186. Prior: v2.42.0 SHIPPED 2026-06-19 — /craft:code:fewer-prompts curated read-only Bash allowlist (PR #181). Prior: v2.41.1 SHIPPED 2026-06-19 — Documentation Coverage (PR #180): 25 tutorial stubs for all Phase 8 commands + broken link fix in TUTORIAL-smart-help.md. Prior: v2.41.0 SHIPPED 2026-06-19 — token-efficiency (quota gate, engine flag, parity runbook). v2.40.0 SHIPPED 2026-06-19 — Guard Suite (PR #174): /craft:git:guard + guards.json registry & ADHD toggle UX, no-switch-guard promoted to scripts/, guard-consistency validator, install-guards.sh. Destructive-restore reconciled to no-switch-guard; branch-guard rm-rf-.git/reset--hard softened to confirm (catastrophic ops stay NON-muteable). 111 commands. Review found 1 CRITICAL (catastrophic-bypass) + 6 issues — ALL fixed pre-merge; +5 registry tests; ci.yml now installs both guards + runs the no-switch suite. Prior: v2.39.0 SHIPPED 2026-06-19 — doc-coverage enforcement (PR #172→dev, #173→main). Prior: v2.38.2 SHIPPED 2026-06-15 — multi-surface release arc (3 patches). v2.38.0: multi-surface engine (verify-surfaces Step 13.6, cache-prune Step 13.7, Data-Wise aggregator marketplace) + branch-guard draft=dev (PRs #158/#159/#160/#161). Adversarial review found 3 HIGH + 6 MEDIUM/LOW bugs → v2.38.1 (PR #163: branch-aware dogfood tests fixed RED main CI; cache-prune data-loss; verify-surfaces git-tag no-op) → v2.38.2 (PR #165: corrupt-surface blocks, symlink GC, arg-parse, subshell). All 3 → main, main CI GREEN, tap + aggregator (Data-Wise/claude-plugins) pinned to 2.38.2, post-release sweep CLEAN. Counts: 112 commands / 39 skills / 8 agents. Prior: v2.37.0 SHIPPED 2026-06-13 — drift-prevention chain + CI triage/watch. Bundled brainstorm items 1-7 (PRs #151/#152/#153/#154/#155): #1 strict-markers guard + #4 workflow recipe discoverability; #7 bump-version categorical-subtotal sweep; #2 CI count-drift tripwire (README live counts); #5 /craft:ci:triage + #6 /craft:ci:watch. PR #156 → main 8c21a54d (merge commit), release tag v2.37.0. Downstream all GREEN: main CI verified, homebrew-release.yml updated tap to v2.37.0 (desc "112 commands", url v2.37.0.tar.gz). Post-release sweep ALL CLEAN (drift tooling paid off). Counts: 112 commands / 39 skills / 8 agents. Prior: v2.36.0 SHIPPED 2026-06-13 (PR #150 → main eb7e143d). -last_session: 2026-08-07 +last_session: 2026-08-08 + +## Session Recap — 2026-08-08 — OpenCode MCP bridges + codex/opencode delegation SPEC; teaching-mode removed + +Set up + smoke-tested two Claude-Code-side MCP bridges to OpenCode (`opencode` npm +package, `opencode-async` local build) via direct ndjson stdio probes — user-registered +(`claude mcp add ... -s user`). Documented in new +`docs/tutorials/TUTORIAL-opencode-mcp-plugin.md` (bridge comparison, setup, gotchas, +routing table, mandatory-Monitor-when-delegating section), wired into mkdocs.yml. + +Ran `/craft:brainstorm` with real web research on codex/opencode delegation patterns +→ `BRAINSTORM-codex-opencode-delegation-2026-08-08.md`. Dogfooded the routing rule via +adversarial review of the session's own uncommitted diff on BOTH backends (`codex exec +review --json --uncommitted` and OpenCode/deepseek-v4-pro via `opencode_run`), each +monitored live for token usage; findings from both consolidated into the BRAINSTORM. +Authored `SPEC-codex-opencode-delegation-2026-08-08.md`, then ADHD-simplified it +(TL;DR, routing table, mandatory monitoring recipe). Ran `/craft:grill` (5 branches, +`GRILL-codex-opencode-delegation-2026-08-08.md`) — caught a real design flaw (implied +reuse of the separate `codex` plugin's `codex-rescue` agent, which craft can't call) +plus default-OFF policy (D7) and dogfood-test enforcement (D8); locked decisions +applied back into the SPEC. Final scope narrowed per user request (D9) to exactly 2 +use cases building now — `arch:review --delegate` (adversarial review) and brainstorm +research escalation — with `code:debug`/`code:refactor`/`code:test-gen`/`ci:triage` +explicitly parked, not cancelled. Committed + pushed `9bd5744ff` (6 files). + +Backgrounded an audit of whether the mkdocs "📚 Teaching" section was dead; verdict +REMOVE (teaching-mode commands pruned in the v4 command cuts, no live callers left). +Executed the removal: 34 files (5 insertions / 9567 deletions) — deleted +`docs/teaching/`, teaching tutorials/schema/migration docs, `commands/utils/ +teach_config.py` + 2 sibling teaching utils, `utils/detect_teaching_mode.py`, and 9 +teaching-only test files; stripped teaching cross-links/sections from README.md, +REFCARD.md, `docs/guide/integration-testing.md` (Category 3 removed, counts +renumbered), `commands/test.md`, `pyproject.toml`. A broader `grep -rl` sweep during +execution caught real dead files the background audit had missed (`test_site_publish.py`, +`examples/test_parser.py`, `MANUAL-TESTING-CHECKLIST.md`, 2 more test files) — all +removed in the same batch. `mkdocs build --strict` clean, markdownlint clean, +`validate-counts.sh` unaffected (48/41/2 — teaching was never a counted surface). Full +suite: 2462 passed / 20 failed (all pre-existing, unrelated — confirmed against dev +baseline before dismissing, per `pre-pr-testing.md`) / 15 skipped / 1 xfailed / 1 +xpassed. Committed + pushed `9b53e387e`. + +Re-verified doc health end of session: markdownlint (639 files) 0 errors, `mkdocs +build --strict` clean (only pre-existing INFO-level links into intentionally-excluded +`specs/`/`orch/` dirs). + +**Deferred, not built this session:** the actual `--delegate` flag implementation for +`arch:review` and brainstorm (SPEC's remaining acceptance criteria — scope was +explicitly grill+narrow only, D9). The 4 parked delegation use cases stay parked +until revisited. ## Session Recap — 2026-08-01 — craft-mcp bundling committed; cwd-marker bug found From 4965f127d10a57966b4313e3d23c0ef54ccbbd9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 05:35:54 +0000 Subject: [PATCH 14/24] chore(deps-dev): bump the mcp-dev-dependencies group (#333) Bumps the mcp-dev-dependencies group in /mcp with 2 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [esbuild](https://github.com/evanw/esbuild). Updates `@types/node` from 26.1.2 to 26.2.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `esbuild` from 0.28.1 to 0.28.2 - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.28.1...v0.28.2) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 26.2.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: mcp-dev-dependencies - dependency-name: esbuild dependency-version: 0.28.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: mcp-dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mcp/package-lock.json | 224 +++++++++++++++++++++--------------------- mcp/package.json | 4 +- 2 files changed, 114 insertions(+), 114 deletions(-) diff --git a/mcp/package-lock.json b/mcp/package-lock.json index 4c9c3f22c..94f2a123e 100644 --- a/mcp/package-lock.json +++ b/mcp/package-lock.json @@ -11,15 +11,15 @@ "@modelcontextprotocol/sdk": "^1.30.0" }, "devDependencies": { - "@types/node": "^26.1.2", - "esbuild": "^0.28.1", + "@types/node": "^26.2.0", + "esbuild": "^0.28.2", "typescript": "^7.0.2" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ "ppc64" ], @@ -34,9 +34,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ "arm" ], @@ -51,9 +51,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ "arm64" ], @@ -68,9 +68,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ "x64" ], @@ -85,9 +85,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -102,9 +102,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], @@ -119,9 +119,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], @@ -136,9 +136,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], @@ -153,9 +153,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], @@ -170,9 +170,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], @@ -187,9 +187,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ "ia32" ], @@ -204,9 +204,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ "loong64" ], @@ -221,9 +221,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ "mips64el" ], @@ -238,9 +238,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], @@ -255,9 +255,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], @@ -272,9 +272,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], @@ -289,9 +289,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], @@ -306,9 +306,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], @@ -323,9 +323,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ "x64" ], @@ -340,9 +340,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ "arm64" ], @@ -357,9 +357,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ "x64" ], @@ -374,9 +374,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "cpu": [ "arm64" ], @@ -391,9 +391,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], @@ -408,9 +408,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ "arm64" ], @@ -425,9 +425,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ "ia32" ], @@ -442,9 +442,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ "x64" ], @@ -511,9 +511,9 @@ } }, "node_modules/@types/node": { - "version": "26.1.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", - "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", "dev": true, "license": "MIT", "dependencies": { @@ -1138,9 +1138,9 @@ } }, "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1151,32 +1151,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/escape-html": { diff --git a/mcp/package.json b/mcp/package.json index 8ccdcdbb9..bae41123c 100644 --- a/mcp/package.json +++ b/mcp/package.json @@ -14,8 +14,8 @@ "@modelcontextprotocol/sdk": "^1.30.0" }, "devDependencies": { - "@types/node": "^26.1.2", - "esbuild": "^0.28.1", + "@types/node": "^26.2.0", + "esbuild": "^0.28.2", "typescript": "^7.0.2" } } From 7db7b565c8cb2f71a1e28568eb0132eea54eff3e Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Sat, 15 Aug 2026 13:25:27 -0700 Subject: [PATCH 15/24] docs: fix stale counts in CLAUDE.md and README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLAUDE.md's Project Structure table still claimed 8 agent definitions; the real count has been 2 since v4.0.0. The row sits outside the bump-version.sh count cascade, whose CLAUDE.md pattern only matches the bold "**N agents**" form, so it survived every version bump since. README.md's tagline claimed "1994 tests passing" — stale, unguarded by any validator, and unbackable next to a suite with known pre-existing failures. Dropped the hard number rather than substituting another one that will drift the same way. Verified: docs-staleness-check.sh GREEN, validate-counts.sh 4/4, markdownlint 0 errors. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b2c58913a..0f3dc0519 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,7 +85,7 @@ performance) · `release` (<300s, thorough). Example: `/craft:code:lint debug`. |-----------|---------| | `commands/` | 48 commands (auto-discovered, one `.md` per command) | | `skills/` | 41 skills (`SKILL.md` pattern) | -| `agents/` | 8 agent definitions | +| `agents/` | 2 agent definitions | | `tests/` | Unit, e2e, dogfood, and integration test suites | | `scripts/` | Release, validation, and maintenance scripts | | `utils/` | Python utilities (`claude_md_sync`, `validate-counts`, etc.) | diff --git a/README.md b/README.md index d07ed76f8..dd8af6027 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ > [MIGRATION-v4.md](docs/MIGRATION-v4.md) for the old-command → new-location table. > > **v2.36.0 — Deterministic Workflow Engine** 🚀 -> **48 commands** | **41 skills** | **2 agents** | **1994 tests passing** +> **48 commands** | **41 skills** | **2 agents** > New `/craft:orch:workflow` mode runs coded, fixed-control-flow programs (`parallel`/`pipeline`/`verify`) with schema-gated agents, data-driven fan-out, a run-wide concurrency semaphore, and cached/resumable replay. Three orchestration modes now: improvise (`/craft:orch`), drive (`:drive`), and workflow (`:workflow`). A comprehensive production-ready toolkit for Claude Code featuring smart orchestration, ADHD-friendly workflows, multi-agent coordination, and complete documentation coverage. From 18949cf3d0c8db9cca96d9583f308a23c40feb43 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Sat, 15 Aug 2026 13:31:44 -0700 Subject: [PATCH 16/24] docs: refresh README v4.5.0 callout; amend prose-gaps SPEC with ADR + harness README's highlight block still advertised v2.36.0's Deterministic Workflow Engine five minors after v4.5.0 shipped. Replaced with the v4.5.0 headline from docs/NEWS.md (repo-triage skill + 8 ecosystem-tool tutorials). SPEC-doc-staleness-prose-gaps amended with: - Amendment section recording two live findings from a currency read on 7db7b565c. E1: Phase 7's scan pattern is strictly plural, so CLAUDE.md's "8 agent definitions" went unseen for five minors -- the singular form must fold into the shared matcher, not sit on top of it. E2: NEWS.md and .STATUS disagree on the v4.5.0 release date because the GitHub release crossed the UTC boundary, so check 1 needs a single authority plus a one-day window or it flags a correct repo on first run. - ADR-007 requirement: records the commitment to pattern-scoped prose gating over Vale/semantic/LLM-in-CI, the rejected whole-file git-touch proxy and why it was falsified, the severity posture, and the revisit trigger. - Test-harness design: 9 fixtures across clean/defect/falsepos, a table-driven runner asserting on --json output, and a rule that a check without a defect fixture is a rejected change. Acceptance criteria extended by 5 items to cover all of the above. Verified: markdownlint 0 errors, docs-staleness-check.sh GREEN. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 4 +- ...PEC-doc-staleness-prose-gaps-2026-08-07.md | 133 +++++++++++++++++- 2 files changed, 133 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dd8af6027..9ae6934d0 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ > **Docs/publishing commands moved to [`folio`](https://github.com/Data-Wise/folio)** — see > [MIGRATION-v4.md](docs/MIGRATION-v4.md) for the old-command → new-location table. > -> **v2.36.0 — Deterministic Workflow Engine** 🚀 +> **v4.5.0 — repo-triage skill + ecosystem-tool tutorials** 🚀 > **48 commands** | **41 skills** | **2 agents** -> New `/craft:orch:workflow` mode runs coded, fixed-control-flow programs (`parallel`/`pipeline`/`verify`) with schema-gated agents, data-driven fan-out, a run-wide concurrency semaphore, and cached/resumable replay. Three orchestration modes now: improvise (`/craft:orch`), drive (`:drive`), and workflow (`:workflow`). +> New `repo-triage` skill batch-grounds open GitHub issues and stale worktrees/branches against current repo state, offering confirmed (never automatic) deletion/closure with itemized evidence and sorting the remainder into grill-ready / plan-ready / defer buckets. Plus 8 ecosystem-tool cheat-sheet tutorials (Codex, Remember, ADHD Mode, Token Optimizer, Security Guidance, Claude HUD, Dropbox, Agent Skills). See [NEWS.md](docs/NEWS.md). A comprehensive production-ready toolkit for Claude Code featuring smart orchestration, ADHD-friendly workflows, multi-agent coordination, and complete documentation coverage. diff --git a/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md index abbfd78b8..db1bf6c3f 100644 --- a/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md +++ b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md @@ -3,7 +3,46 @@ **Generated:** 2026-08-07 **Context:** Craft Plugin v4.5.0 — extends `scripts/docs-staleness-check.sh` Phase 7 **Sources:** [`BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md`](BRAINSTORM-doc-staleness-prose-gaps-2026-08-07.md) (6 locked decisions, external research) -**Status:** draft — revised post adversarial review (see "Review Outcome" below) +**Status:** draft — revised post adversarial review (see "Review Outcome" below); amended +2026-08-15 with an ADR requirement, a test-harness design, and two new pieces of live evidence +(see "Amendment" below) + +--- + +## Amendment (2026-08-15) + +Two real staleness bugs surfaced during a `/savant:restore` currency read on `dev` at +`7db7b565c`. Both sharpen this SPEC's design rather than changing its scope. + +### E1 — Phase 7's existing regex misses the singular noun form + +`CLAUDE.md`'s Project Structure table row for the `agents/` directory read +**8 agent definitions** while the real count had been 2 since v4.0.0. Phase 7 reported **GREEN** over it for five minors. Cause: +the scan pattern is `\b[0-9]+ ${ctype}\b` where `ctype ∈ {commands, skills, agents}` — strictly +plural (`scripts/docs-staleness-check.sh:345`). `8 agent definitions` never matched. The 40% +minimum threshold (line 302) was not the cause; 8 clears it. + +This is not a check-2 line-shape case — it is a **pre-existing gap in the check-2 predecessor**. +Fold the singular alternation into the shared matcher rather than shipping check 2 on top of a +pattern that still can't see half the noun forms. + +### E2 — "release date" has two defensible authorities + +`docs/NEWS.md` claims `**Released:** 2026-08-08` for v4.5.0; `.STATUS` has +`release_date: 2026-08-07`. Neither is wrong — the GitHub release published at +`2026-08-08T03:44:25Z`, which is 2026-08-07 21:44 local. Check 1 as written ("must match +`.STATUS`'s `release_date:`") would flag this as RED on its first run against a correct repo. + +Check 1 must therefore pick **one** authority and normalize: compare against the annotated git +tag's local date (`git for-each-ref --format='%(creatordate:short)' refs/tags/vX.Y.Z`), and +accept a ±1-day window against `.STATUS`'s `release_date:` to absorb the UTC boundary. A +same-day-either-side match is not staleness. + +### Severity divergence to resolve at build time + +Existing Phase 7 findings are emitted at severity `warning` (`add_finding 7 "warning" ...`, +line 341) while this SPEC specifies checks 1–2 as blocking RED. Whichever way this lands, it is +an intentional choice and belongs in the ADR below, not in a silent code default. --- @@ -113,9 +152,99 @@ reach `homebrew-tap` (out of scope per D2 — craft only). If this pattern prove consider a parallel prose-accuracy check in `homebrew-tap` itself (different repo, different owner of that decision) — not assumed, not scheduled. +## ADR — ADR-007: pattern-scoped prose gating in the existing staleness script + +This SPEC commits craft to a position it has not written down anywhere: that documentation +*prose* is gated by narrow, hand-authored line-shape patterns inside +`scripts/docs-staleness-check.sh`, and explicitly **not** by a prose linter (Vale), a semantic +differ, or an LLM-in-CI pass. That choice constrains every future staleness check, so it gets a +record rather than living implicitly across D1/D3/D4 in a BRAINSTORM. + +**File:** `docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md`, following the existing +ADR-001…ADR-006 format in that directory. + +Content it must record: + +| Element | Substance | +|---|---| +| **Context** | Phase 7 anchors on structured patterns and reported GREEN over four real bugs: REFCARD.md's stale release date, skills-agents.md's "8 specialized agents", CLAUDE.md's "8 agent definitions" (E1), README.md's v2.36.0 highlight block five minors after v4.5.0. | +| **Decision** | Extend Phase 7 in place with line-shape-scoped regexes. No new script (D1), no external prose tool (D4), no semantic layer (D3). Craft-only (D2). | +| **Consequences — accepted** | Coverage is exactly as good as the enumerated line shapes; every genuinely new prose shape needs a code change, not a config change. `scripts/config/exclusions.txt` grows over time (accepted tradeoff, review finding 4). | +| **Consequences — rejected alternatives** | Vale/`drift`/LLM-in-CI (D4) — rejected for dependency weight and non-determinism in a release gate. Whole-file git-log-touch proxy — rejected as *falsified*, not merely unattractive: `bump-version.sh` touches REFCARD.md every release, so the proxy reads "fresh" on the file that motivated this work. | +| **Severity posture** | Records whether checks 1–2 emit `error` (blocking) or `warning`, and why — the divergence flagged in the Amendment above. | +| **Revisit trigger** | A prose staleness bug that lands despite checks 1–2, whose shape cannot be expressed as a line-shape regex. That, and only that, reopens the D3/D4 rejection. | + +## Test Harness + +The Test Plan below names tiers; this section specifies the rig they run on. Both checks need +the same three-way verdict pattern (clean → GREEN, planted defect → RED, known false-positive +source → GREEN), and the adversarial review's three false-positive sources are currently prose +in an acceptance criterion rather than executable rows. A table-driven harness makes them +first-class. + +### Layout + +```text +tests/fixtures/prose-staleness/ + clean/ + refcard-version-box.md # correct date + counts inside a ┌─┐ box + skills-agents-tldr.md # correct "2 specialized agents" TL;DR line + claude-md-structure-table.md # correct "2 agent definitions" (E1 regression) + defect/ + refcard-stale-date.md # check 1 — date ≠ tag date + tldr-eight-agents.md # check 2 — "8 specialized agents" in a TL;DR line + structure-table-singular.md # check 2 — "8 agent definitions", singular form (E1) + falsepos/ + orch-flag-usage.md # "2 agents max" / "4 agents" mode-limit prose + fictional-plugin-tutorial.md # intentionally fictional example counts + troubleshooting-wrong-count.md # deliberately-wrong count teaching the bug +``` + +### Contract + +Each fixture is a **complete minimal markdown file**, not a snippet — the checks operate on +line context (fenced-box membership, TL;DR prefix), so a snippet would not exercise the same +code path. Every fixture carries a one-line HTML comment header stating what it proves, so a +later reader does not have to infer intent from filename alone. + +The runner (`tests/test_docs_staleness_prose.py`, alongside the existing +`tests/test_docs_staleness.py`) is a single parametrized case over a declared table: + +| Fixture | Check | Expected | Proves | +|---|---|---|---| +| `clean/refcard-version-box.md` | 1 | GREEN | no false positive on correct content | +| `defect/refcard-stale-date.md` | 1 | RED | planted-defect positive control | +| `clean/claude-md-structure-table.md` | 2 | GREEN | E1 stays fixed | +| `defect/structure-table-singular.md` | 2 | RED | E1 would be caught, not missed | +| `defect/tldr-eight-agents.md` | 2 | RED | the original review bug | +| `falsepos/*.md` (×3) | 2 | GREEN | review finding 3's sources stay unflagged | + +Invoked via `--json` against a fixture directory so the assertion reads the structured +`findings[]` array rather than parsing colored terminal output — the same reason +`docs-staleness-check.sh` already ships `--json`. + +### Harness requirements + +- **The harness must be able to fail.** Adding a check without its `defect/` row is a rejected + change; the positive control is the point (see `e2e-before-pr.md`). +- **No network, no git-history walk, no TTY.** Fixtures are self-contained files; the one git + read check 1 needs (tag date) is injected as a parameter, not shelled out to, so the suite is + hermetic and runs identically in CI. +- **Reuse, don't fork.** `scripts/config/exclusions.txt` handling, `is_file_excluded`, and + `is_pattern_excluded` are called as-is — the harness tests the real code path, not a copy. + ## Acceptance Criteria -- [ ] Checks 1 and 2 are implemented as blocking (RED) findings in Phase 7. +- [ ] Checks 1 and 2 are implemented as blocking (RED) findings in Phase 7, or the divergence + from Phase 7's existing `warning` severity is recorded as a deliberate choice in ADR-007. +- [ ] The shared count matcher handles the **singular** noun form (`N agent definitions`), not + only the plural (E1) — with `clean/` + `defect/` fixtures proving both directions. +- [ ] Check 1 resolves the release-date authority per E2 (git tag local date, ±1-day window + against `.STATUS`) and does **not** flag the current `docs/NEWS.md` v4.5.0 entry. +- [ ] `docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md` exists and records every row + of the ADR table above, including the rejected alternatives and the revisit trigger. +- [ ] The test harness exists at `tests/fixtures/prose-staleness/` with all 9 fixtures and a + table-driven runner asserting against `--json` output. - [ ] Check 2's line-shape scoping is itself tested against the 3 false-positive sources the review found (`docs/guide/orch-flag-usage.md`, the fictional-plugin tutorial, the intentional-bug troubleshooting page) — must NOT flag any of them. From 5e83237f7a8356ea873f714d887d97bcf62a49b3 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Sat, 15 Aug 2026 13:52:58 -0700 Subject: [PATCH 17/24] =?UTF-8?q?docs(.status):=20refresh=20Active=20Workt?= =?UTF-8?q?rees=20=E2=80=94=20repo-triage=20merged,=20prose-gaps=20live?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo-triage row still read DISPATCHED 2026-08-07 after that work shipped in v4.5.0 and its worktree was removed. Marked merged with its PR links and added the feature/doc-staleness-prose-gaps worktree created this session. Co-Authored-By: Claude Opus 5 (1M context) --- .STATUS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.STATUS b/.STATUS index 2c8d37b24..36579334c 100644 --- a/.STATUS +++ b/.STATUS @@ -709,7 +709,8 @@ next occurrence, not a date. | Branch | Path | Purpose | |--------|------|---------| -| `feature/repo-triage` | `~/.git-worktrees/craft/feature-repo-triage` | DISPATCHED 2026-08-07. New `repo-triage` skill (batch issue/worktree/branch triage, thin orchestrator over `issue-check` + `dev/git` Op 4/5). 6-phase ORCHESTRATE, `orchestrate-dispatch` mode. Phase 0 (`dev/git` Op 5 `--dry-run` + output contract) blocks Phases 2-4. Planning: `docs/specs/BRAINSTORM-repo-triage-2026-08-07.md`, `docs/specs/GRILL-repo-triage-2026-08-07.md` (amended post-review), `docs/specs/REVIEW-repo-triage-2026-08-07.md`. | +| `feature/doc-staleness-prose-gaps` | `~/.git-worktrees/craft/feature-doc-staleness-prose-gaps` | LIVE, created 2026-08-15. Implements `SPEC-doc-staleness-prose-gaps-2026-08-07.md` — 2 prose checks in `docs-staleness-check.sh` Phase 7 (release-date claims vs git tag ±1 day; count prose scoped to 4 structured line shapes, singular noun form included), ADR-007, and a 11-fixture table-driven harness (`tests/fixtures/prose-staleness/` + `tests/test_docs_staleness_prose.py`). | +| _(prior, MERGED)_ | — | `feature/repo-triage` → shipped in v4.5.0 (PR [#328](https://github.com/Data-Wise/craft/pull/328) squash to dev, released via [#329](https://github.com/Data-Wise/craft/pull/329)). Worktree removed. Original dispatch note: DISPATCHED 2026-08-07. New `repo-triage` skill (batch issue/worktree/branch triage, thin orchestrator over `issue-check` + `dev/git` Op 4/5). 6-phase ORCHESTRATE, `orchestrate-dispatch` mode. Phase 0 (`dev/git` Op 5 `--dry-run` + output contract) blocks Phases 2-4. Planning: `docs/specs/BRAINSTORM-repo-triage-2026-08-07.md`, `docs/specs/GRILL-repo-triage-2026-08-07.md` (amended post-review), `docs/specs/REVIEW-repo-triage-2026-08-07.md`. | | _(prior, MERGED)_ | — | `feature/prerelease-version-check-hardening` → dev via [#311](https://github.com/Data-Wise/craft/pull/311) (squash), 2026-07-25. Audited the repo for the "grep first bare vX.Y.Z string" bug class that broke the Homebrew release gate twice (v4.2.0, v4.3.0, fixed for `docs/index.md` in #308) — found and fixed 4 more live sites: `.github/workflows/docs.yml`'s live-site poll, `verify-surfaces.sh`'s `resolve_docs_site` (explicitly reused docs.yml's mechanism), `version-sync-precommit.sh`'s `.STATUS` check (a blocking pre-commit hook), and the manual verification steps in `skills/release/SKILL.md`/`downstream-verification.md`; all anchored to the actual version signal (badge slug / `^version:` field) instead of the first bare match. Confirmed 3 other sites already safe, left unchanged. New regression tests for each fix, each verified against a planted-defect positive control. Closes the recurrence risk flagged after v4.3.0. Worktree removed, local + remote branch deleted. | | _(prior, MERGED)_ | — | `feature/issue-309-branch-guard-docs` → dev via [#310](https://github.com/Data-Wise/craft/pull/310) (squash), 2026-07-25. The documented "Claude writes `.claude/allow-once`" one-shot bypass flow is circular when the approved action *is* creating/editing that marker itself (every tool path intercepts it with its own `[CONFIRM]`); added the `CRAFT_GUARD_ALLOW_DEV_EDIT` env-var hint (#281) to the 3 marker-specific confirms only, and reconciled `REFCARD-BRANCH-GUARD.md`/`branch-guard-smart-mode.md` with a caveat pointing at `skills/dev/git/SKILL.md` Operation 10. Code review caught the negative regression test was checking the wrong verbosity encounter (2nd-call brief mode never renders suggestions regardless of content, so it passed vacuously); fixed to assert against the 1st-encounter full-verbosity capture, verified via planted-defect positive control. Closes #309. Worktree removed, local + remote branch deleted. | | _(prior, MERGED)_ | — | `feature/issue-286-reference-scope-guard` → dev via [#306](https://github.com/Data-Wise/craft/pull/306) (squash), 2026-07-25. New advisory-only PreToolUse hook `reference-scope-guard.sh` warning on non-conforming filenames written under `~/.claude/reference/`; wired into `install-guards.sh`; 13-case e2e suite. Closes #286. Worktree removed, local + remote branch deleted. | From b5de1ae39f6af2fac616eda7e122496245fc3f53 Mon Sep 17 00:00:00 2001 From: Davood Tofighi Date: Sat, 15 Aug 2026 17:26:19 -0600 Subject: [PATCH 18/24] feat(docs-staleness): prose checks for release dates and shaped count lines (#334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(docs-staleness): prose checks for release dates and shaped count lines Implements SPEC-doc-staleness-prose-gaps-2026-08-07 and records the approach as ADR-007. Phase 7 anchored on structured tokens only, so four real bugs shipped while it reported GREEN: REFCARD.md's wrong release date beside a right version number, skills-agents.md's "8 specialized agents" two lines above its own correct "2", CLAUDE.md's "8 agent definitions" for five minors, and README.md headlining v2.36.0 at v4.5.0. Two checks, both severity warning: - Release-date claims within 4 lines of the current version token must fall within a day of that version's git tag date. The window is not slack: the GitHub release for v4.5.0 published 2026-08-08T03:44Z against a 2026-08-07 tag, so a stricter check would flag a correct repo on its first run. - Count prose is checked inside four structured line shapes only (version box, TL;DR opener, bolded count-summary badge, structure-table row), where the singular noun form is safe to match. The broad scan stays plural-only and unscoped -- widening it would reopen the 90+ false positives the adversarial review closed. Harness: 11 fixtures across clean / planted-defect / known-false-positive, plus a table-driven runner asserting on --json findings. Positive controls verified by mutation rather than assumed -- removing the 40% floor fails 3 tests, dropping the singular alternation fails exactly the E1 defect test. Three findings from the build itself, all folded back into the SPEC: - The 40%-of-expected floor must apply to shaped lines too. Without it the first build flagged five legitimate counts on dev: category subtotals in reference boxes, a bolded subset count, a narrative count about another plugin. All five are now falsepos fixtures. - Matching TL;DR anywhere on a line flagged ADR-007's own context table, which quotes the line the ADR exists to explain. The shape now requires the line to open with TL;DR. - The first build ran the script at 36s against a dev baseline of 8.0s, blowing the 30s timeout in test_pre_release_check_runs. Cause was ~1300 per-file awk spawns plus ~21000 greps over every line of every box block. Now one awk pass over the file list, an in-awk pre-filter so only count-bearing lines reach bash, and the date window computed once. 8.7s. Full suite in-tree: 1 failed / 2460 passed / 49 skipped. The one failure is a pre-existing broken link in REVIEW-repo-triage-2026-08-07.md, reproduced on the dev baseline (20 failed / 2462 passed; dev's 19 branch-guard failures are skips on a feature branch). docs-staleness GREEN, validate-counts 4/4, markdownlint 0 errors, mkdocs build --strict clean. Co-Authored-By: Claude Opus 5 (1M context) * fix(docs-staleness): unparseable release-date authority must be vacuous Self-review of #334 found it. compute_release_date_window exits silently when the tag date will not parse, leaving an empty accept-window -- and an empty window matches nothing, so every release-date claim in the repo was flagged at once. One bad input became a repo-wide false-positive storm. The no-tag case was already handled (skip); an unparseable tag was not. Both now take the same path: the check is vacuous unless the authority actually parsed. Regression test uses the clean/ fixture, which is correct against a real tag date, so any finding it sees comes from the broken authority alone. Positive control verified by reverting the guard: the test fails with "release date '2026-08-08' for v4.5.0 (tag: not-a-date)". Co-Authored-By: Claude Opus 5 (1M context) * docs(specs): repoint REVIEW-repo-triage at the archived GRILL b1c4426e4 archived GRILL-repo-triage-2026-08-07.md to docs/specs/_archive/ without updating its one referrer, so test_no_broken_links has failed on every branch since. It is the sole failure in the dev baseline and the only thing red on this PR. Unrelated to this branch's subject, fixed here because a dev-side fix would not reach this PR's CI run. The other file archived in the same commit (SPEC-ecosystem-tool-tutorials) has no referrers -- checked, nothing to fix. Co-Authored-By: Claude Opus 5 (1M context) * fix(docs-staleness): release-date window was one line short of its contract Found by /code-review on #334, confirmed empirically before acting. The win-- decrement runs on the version-mention line itself, so win = 4 scanned that line plus only 3 more. Both the inline comment and docs/reference/REFCARD-DOCS-STALENESS.md advertise 4 following lines, and the gap silently reads GREEN rather than flagging -- a layout as ordinary as heading / blank / Type / blank / Released fell straight through it. win = 5 restores the documented window. New defect fixture places a stale date at exactly that far edge; positive control verified by reverting to 4, which fails only that case: FAILED test_prose_staleness_fixture[defect-release-date-far-edge] - planted defect went undetected 1 failed, 14 passed Co-Authored-By: Claude Opus 5 (1M context) * fix(docs-staleness): pass 2 [f]ix applied nothing while reporting success pass2_interactive_review's [f]ix branch printed "-> Fixed" and incremented TOTAL_FIXED without touching the file. It is the same reports-success-changes-nothing bug pass 1 was fixed for earlier (see the BSD-sed note now on apply_line_fix), left behind in the other pass. Extracted pass 1's python applier into apply_line_fix and pointed both passes at it, so the two cannot drift apart again. It echoes "true" only when the file actually changed, and "false" for a non-substitution fix_detail, a pattern that matches nothing, or an out-of-range line -- Phase 8's doc-coverage findings carry a `doc-coverage:surface:cmd` marker rather than a substitution and must never be executed. Both prose checks now emit a real substitution as their fix_detail instead of a human-readable "old -> new" note, so confirming one in pass 2 actually edits the file. They stay routed to pass 2 rather than pass 1: the surrounding prose is hand-authored, so a human should see the line before the number changes under it. The substitution swaps only the digits, leaving "agent definitions" intact. Two tests pin the applier's contract, including that a "false" result leaves the file byte-identical. Positive control verified by mutating apply_line_fix to report success without writing -- the truthfulness test fails. The pass 2 branch itself is not directly covered: it requires a TTY. Its 8 lines of fix_detail parsing mirror pass 1's. Co-Authored-By: Claude Opus 5 (1M context) * docs: sync SPEC, ADR-007, REFCARD and changelogs with the four post-build fixes All four planning/internal docs were written in bd957237b and none was updated for the fixes that landed after it, so each described the first build rather than what ships. SPEC: new "Post-build findings" table recording all four bugs and, for each, which mechanism caught it -- the existing suite's timeout, self-review, /code-review, and reading adjacent code. No single review mechanism found more than one, which is the point of recording it. Notes that two of the four are the same failure mode (reporting a result never established) and that the pass-2 bug is a recurrence of one pass 1 was already fixed for. Adds the fix-routing decision, the 11th fixture, and 4 acceptance criteria. ADR-007: two architectural rules the fixes established, both stated generally for checks added later. A failed authority makes a check vacuous, never universal -- absence of a usable comparison is not evidence of drift, and the failure modes are not symmetric (a skipped check costs one missed bug; a check that fires on every document costs the gate its credibility). And fixes must be applied, not announced -- one shared applier, because patching the second site would have left a third. REFCARD: corrects the window to "the version line or the 4 lines below it", documents the vacuous-on-unusable-authority rule, and records that choosing [f]ix now really edits the file, including when it will decline. CHANGELOG (both): the three script fixes plus the archived-GRILL link repoint. Verified: markdownlint 0 errors, docs-staleness GREEN, validate-counts 4/4, mkdocs build --strict clean. Co-Authored-By: Claude Opus 5 (1M context) * docs(specs): spec the 9 prose-check defects found reviewing #334 Every finding reproduced against the real script before being written down -- F1/F2/F6 by constructing input, F5/F7 by reading the consuming code. Two are verified as changing the design rather than just the code: F5: ADR-007 justified `warning` over `error` by claiming both severities fail the release gate identically. pre-release-check.sh:280 is headed "warn-only, does not block release" and swallows the exit code, and docs-quality.yml sets continue-on-error -- while skills/release/SKILL.md:143 says RED blocks and YELLOW proceeds. The label is the only gate, so the severity choice IS the block decision the ADR said it was not. F7: docs-quality.yml checks out without fetch-tags, so no tag exists and check 1 silently skips on every CI run; and release writes the NEWS/REFCARD dates before the tag is created. The check may only ever fire on a dev machine that already pulled the tag. Groups the nine into four root causes rather than listing them flat, because patching nine sites individually leaves the classes open -- notably C3, where the pass-2 [e] branch is the third instance of the same reports-success-changes-nothing bug, introduced by #334's own change of the finding's `file` field to path:lineno. Nine decisions left open with tradeoffs rather than pre-resolved, for grilling. Co-Authored-By: Claude Opus 5 (1M context) * docs(specs): grill the prose-check hardening spec — 5 branches locked Two branches were reframed by evidence gathered during the grill, not argument. D1 (authority): dropped the git tag for cross-file consistency. The tag is absent in both environments the check exists to protect -- release runs Step 3b (write doc dates) -> Step 3b.5 (staleness gate) -> Step 8 (create tag), so the tag does not exist when the gate runs; and docs-quality.yml checks out without fetch-tags. As built, check 1 fires only on a dev machine that already pulled the tag. Comparing claims against each other needs no authority and works in both places. D2 (severity): split, check 1 RED and check 2 YELLOW. Verified that no consumer reads the exit status -- pre-release-check.sh swallows it with || true, docs-quality.yml sets continue-on-error -- while skills/release/SKILL.md:143 blocks on the RED label. The label is the only gate, so severity IS the block decision ADR-007 claimed it was not. Severity now follows demonstrated precision per-check rather than per-phase. D3: fix the noun boundary, anchor the substitution on the full matched span, and floor at max(2, 40%) -- all three, because F1's agent half is caused by the floor being 0 for agents, and a prose-corrupting fix is worse than a false warning. D4: fix the [e]xclude no-op at the record level across Phase 7 and Phase 9. Correction: this was relayed as a regression from #334 and is not -- dev already has add_finding with "${file}:${lineno}" at 341, 497 and 548. Pre-existing, two phases wide, and a live violation of the rule ADR-007 states. D5: amend #334 rather than merge-then-harden. craft's docs are hyphen-heavy, so shipping first means dev carries a --fix that can rewrite prose. Spec updated where the grill overtook it: status, the C3 correction, and scope (Phase 9 in, tag/fetch-tags out). Co-Authored-By: Claude Opus 5 (1M context) * docs(plan): ORCHESTRATE for prose-check hardening + 6th grill branch Routed by /craft:plan tier detection: SPEC present, GRILL present, no ORCHESTRATE -> plan-orchestrator (tier 4). Worktree already exists, so no new one was created. Six phases mapped to the locked decisions, sequential, one commit each: matcher/payload/floor (D3), check 1 to cross-file consistency (D1, D6), severity split and the ADR correction (D2), exclusion round-trip at the record level (D4), the two low findings plus doc sync, then verify and update PR #334 (D5). Authoring the plan surfaced a gap the grill left: the spec listed 9 decisions and the grill locked 5, so the release-date window discipline (spec D4 / finding F3) was never resolved. D1 settled where the authority comes from, not how claims are collected -- so prose like "Upgrading to v4.5.0 is a drop-in change" would still open a window and turn a nearby unrelated date into a claim for that version. The false positive survived the redesign in a new place. Resolved as ledger D6: the window opens only when the version token appears in a heading or a version-box line. Structural rather than numeric -- shrinking the window narrows the class without closing it, and requiring version and date on one line would miss the real NEWS.md layout the check exists for. D2 raises the stakes, since check 1 is now RED and every surviving false positive blocks a release. Friction Prevention records the traps this work already hit once: the awk pre-filter and bash matcher are two regexes expressing one rule and must move together, and the 30s test_pre_release_check_runs budget is the only thing that caught the last perf regression. No implementation this session -- plan artifact only, per the confirm gate. Co-Authored-By: Claude Opus 5 (1M context) * docs(plan): grill the ORCHESTRATE — 5 plan-level branches, 4 found defects Grilling the plan rather than the design. Four of the five branches found something wrong with the plan; only one confirmed it. D7 phase order: the record change moves to FIRST. As written, Phases 1-3 authored add_finding calls and tests against the old signature and Phase 4 then rewrote all 12 call sites (Phase 6 x2, 7 x3, 8 x3, 9 x4) -- every prose finding written twice, and the mechanical 12-site sweep landing last, where a missed site is hardest to spot against three phases of other changes. D8 fixtures: "delete ... or rewrite" narrowed to rewrite-only. D6 keeps a window, so the off-by-one class is still live and defect/release-date-far-edge.md is its only positive control -- the exact bug /code-review caught. The plan permitted deleting it with an "or". Generalized into Friction Prevention: never delete a positive control to make a redesign easier. D9 normalize the claim sites. Evidence gathered while grilling: craft has exactly two release-date claims for the current version and they already disagree -- NEWS.md 2026-08-08 (UTC) vs REFCARD.md 2026-08-07 (tag-local). Both reachable under D6, so check 1 fires on craft today and only the one-day tolerance keeps it GREEN. With D2 making it RED, the repo would sit permanently one day from a blocked release with no prior warning. D10: Phase 6 gains the .STATUS refresh and the at-merge ORCHESTRATE deletion. Both required by craft's CLAUDE.md, neither enforced -- not gitignored, not in exclusions.txt, no test. D11: RED promotion is evidence-gated. Check 1 is redesigned and promoted to release-blocking in the same PR, so it ships as warning and is promoted in Phase 6 only after running clean over the real docs with the transcript quoted. Does not reverse D2 -- makes RED earned rather than assumed. Estimate corrected 2h -> ~3h; the original was wishful about Phase 1. Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: Claude Opus 5 (1M context) --- CHANGELOG.md | 36 ++ ORCHESTRATE-prose-check-hardening.md | 181 +++++++++ docs/CHANGELOG.md | 36 ++ ...7-pattern-scoped-prose-staleness-gating.md | 136 +++++++ docs/reference/REFCARD-DOCS-STALENESS.md | 60 ++- .../GRILL-prose-check-hardening-2026-08-15.md | 232 +++++++++++ docs/specs/REVIEW-repo-triage-2026-08-07.md | 2 +- ...PEC-doc-staleness-prose-gaps-2026-08-07.md | 178 +++++++-- .../SPEC-prose-check-hardening-2026-08-15.md | 137 +++++++ mkdocs.yml | 1 + scripts/docs-staleness-check.sh | 376 +++++++++++++++--- .../clean/release-date-utc-boundary.md | 10 + .../clean/structure-table-correct.md | 10 + .../prose-staleness/clean/tldr-correct.md | 7 + .../clean/version-box-correct.md | 13 + .../defect/release-date-far-edge.md | 12 + .../defect/structure-table-singular.md | 9 + .../defect/tldr-eight-agents.md | 7 + .../defect/version-box-stale-date.md | 12 + .../falsepos/category-subtotal-box.md | 13 + .../falsepos/mode-limit-prose.md | 10 + .../falsepos/subset-bold-count.md | 10 + .../falsepos/tldr-mentioned-not-claimed.md | 13 + tests/test_docs_staleness_prose.py | 264 ++++++++++++ 24 files changed, 1666 insertions(+), 99 deletions(-) create mode 100644 ORCHESTRATE-prose-check-hardening.md create mode 100644 docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md create mode 100644 docs/specs/GRILL-prose-check-hardening-2026-08-15.md create mode 100644 docs/specs/SPEC-prose-check-hardening-2026-08-15.md create mode 100644 tests/fixtures/prose-staleness/clean/release-date-utc-boundary.md create mode 100644 tests/fixtures/prose-staleness/clean/structure-table-correct.md create mode 100644 tests/fixtures/prose-staleness/clean/tldr-correct.md create mode 100644 tests/fixtures/prose-staleness/clean/version-box-correct.md create mode 100644 tests/fixtures/prose-staleness/defect/release-date-far-edge.md create mode 100644 tests/fixtures/prose-staleness/defect/structure-table-singular.md create mode 100644 tests/fixtures/prose-staleness/defect/tldr-eight-agents.md create mode 100644 tests/fixtures/prose-staleness/defect/version-box-stale-date.md create mode 100644 tests/fixtures/prose-staleness/falsepos/category-subtotal-box.md create mode 100644 tests/fixtures/prose-staleness/falsepos/mode-limit-prose.md create mode 100644 tests/fixtures/prose-staleness/falsepos/subset-bold-count.md create mode 100644 tests/fixtures/prose-staleness/falsepos/tldr-mentioned-not-claimed.md create mode 100644 tests/test_docs_staleness_prose.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f9729581b..b583d7725 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [Unreleased] + +### Added + +- **Two prose-staleness checks in `docs-staleness-check.sh` Phase 7** + ([SPEC](docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md), + [ADR-007](docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md)) — a release-date check + (claims near the current version token must be within a day of that version's git tag) and a + count-prose check scoped to four structured line shapes (version box, TL;DR line, bolded + count-summary badge, structure-table row). The count check reads the **singular** noun form + too, closing the gap that let `CLAUDE.md`'s "8 agent definitions" read GREEN for five minors. + Both emit `warning`, per ADR-007's gentle-ramp rationale. +- **Prose-staleness test harness** — `tests/fixtures/prose-staleness/` (10 fixtures across + clean / planted-defect / known-false-positive) and `tests/test_docs_staleness_prose.py`, a + table-driven runner asserting on `--json` findings. Includes a guard that every check keeps a + planted-defect fixture, and a live-repo assertion that `count_consistency` stays clean. + +### Fixed + +- **`docs-staleness-check.sh` pass 2 `[f]ix` applied nothing while reporting success** — it + printed `-> Fixed` and incremented the counter without touching the file. Pass 1 had the same + bug fixed earlier (BSD `sed -i` exits 0 on no match); the sibling pass was left behind. Both now + share one `apply_line_fix`, which reports success only when the file actually changed and + refuses to execute a non-substitution `fix_detail`. +- **Release-date window was one line short of its documented contract** — the decrement ran on the + version-mention line itself, so it scanned that line plus only 3 more instead of 4. +- **An unparseable release-date authority flagged every claim in the repo** instead of skipping. + The check is now vacuous unless its authority parsed, matching the no-tag case. +- `docs/specs/REVIEW-repo-triage-2026-08-07.md` pointed at a GRILL doc that `b1c4426e4` had + archived, failing `test_no_broken_links` on every branch since. +- `CLAUDE.md`'s Project Structure table claimed 8 agent definitions (actual: 2 since v4.0.0); + `README.md`'s highlight block still headlined v2.36.0 at v4.5.0, and its tagline carried a + stale hard test count. + +--- + ## [4.5.0] - 2026-08-08 ### Added diff --git a/ORCHESTRATE-prose-check-hardening.md b/ORCHESTRATE-prose-check-hardening.md new file mode 100644 index 000000000..8fc7758eb --- /dev/null +++ b/ORCHESTRATE-prose-check-hardening.md @@ -0,0 +1,181 @@ +# ORCHESTRATE: Prose-Check Hardening + +**Spec:** [`docs/specs/SPEC-prose-check-hardening-2026-08-15.md`](docs/specs/SPEC-prose-check-hardening-2026-08-15.md) +**Grill:** [`docs/specs/GRILL-prose-check-hardening-2026-08-15.md`](docs/specs/GRILL-prose-check-hardening-2026-08-15.md) — 11 branches locked (D1–D6 spec, D7–D11 plan) +**Worktree:** `~/.git-worktrees/craft/feature-doc-staleness-prose-gaps` (exists) +**Branch:** `feature/doc-staleness-prose-gaps` · **PR:** #334 (open, CI green, **do not merge until P6**) + +## Objective + +Close the 9 defects a high-effort review found in PR #334, per the grill's locked decisions, on the +same branch — so #334 never ships a check that emits false positives or corrupts prose. + +## Phase Overview + +**Reordered per D7:** the record change goes first. As originally written, Phases 1–3 authored +`add_finding` calls and tests against the old signature and Phase 4 then rewrote all 12 call sites — +every prose finding written twice, its tests touched twice, and the mechanical sweep landing where +a missed site is likeliest. + +| # | Phase | Findings | Locked by | Est | +|---|-------|----------|-----------|-----| +| 1 | Finding record: line number as its own field | F4 | D4, D7 | 45 min | +| 2 | Matcher, fix payload, floor | F1, F2, F6 | D3 | 25 min | +| 3 | Check 1 → cross-file consistency | F3, F7 | D1, D6, D8 | 40 min | +| 4 | Severity split + ADR correction | F5 | D2, D11 | 20 min | +| 5 | Low findings, date normalization, doc sync | F8, F9 | D9 | 25 min | +| 6 | Verify, promote, update PR #334 | — | D5, D10, D11 | 30 min | + +**Estimate honesty (D11):** ~3 hours, not the 2 first written. Phase 1 alone is 12 call sites plus +JSON rendering plus both fix passes plus an end-to-end exclusion test; 25 minutes was wishful. + +Sequential. Phase 1 must complete before any other phase touches `add_finding`. + +## Phase 1 — Finding record: line number as its own field (D4, D7) + +Goes first so every later phase is written once, against the final signature, and so the +largest-blast-radius change lands against an unmodified baseline where the full suite is a clean +control. + +- [ ] `add_finding` takes line number as its own argument instead of glued into `file` +- [ ] Update all 12 call sites: Phase 6 (×2), Phase 7 (×3), Phase 8 (×3), Phase 9 (×4) +- [ ] JSON output preserves today's `file` rendering (`path:lineno`) so consumers do not break — + the split is internal +- [ ] `[e]` writes `path:pattern`, matching what `is_pattern_excluded` parses +- [ ] **E2E, not inspection:** write the exclusion, re-run, assert the finding is gone +- [ ] Mutation control: revert the record change → the round-trip test fails +- [ ] **Full suite after this phase**, not just the prose tests — this is the widest change here + +## Phase 2 — Matcher, fix payload, floor (D3) + +- [ ] Trailer: `([^a-z]|$)` → whitespace / closing punctuation / end-of-line, in the check-2 + matcher **and** awk's `hascount()` pre-filter (they must agree, or the pre-filter drops lines + the matcher would have caught) +- [ ] Floor: `expected * 40 / 100` → `max(2, expected * 40 / 100)` +- [ ] Fix payload: build the substitution from the full matched span, not the bare `N noun` prefix +- [ ] Fixture `falsepos/hyphenated-compounds.md`: `command-line`, `agent-facing`, + `skill-authoring`, `agents-only` — all GREEN +- [ ] Fixture `defect/small-count-agents.md`: a wrong agent count the zero floor used to pass only + by accident, now caught deliberately +- [ ] **Pin E1 survives:** `clean/structure-table-correct.md` and + `defect/structure-table-singular.md` pass unchanged — `8 agent definitions` has a space after + the noun and `8 ≥ max(2, 0)` +- [ ] Mutation control per change: revert the trailer → hyphen fixture fails; revert the floor → + small-count fixture fails; revert the span anchor → a fix-application test corrupts prose + +## Phase 3 — Check 1 → cross-file consistency (D1, D6, D8) + +- [ ] Window opens only on a **markdown heading** or a version-box line containing the version + token — not on any line mentioning it (D6, closes F3) +- [ ] Replace `resolve_release_date` / `compute_release_date_window` / `release_date_accepted` + with claim collection + pairwise agreement (one-day tolerance retained) +- [ ] Delete `CRAFT_RELEASE_DATE`; the harness no longer injects an authority +- [ ] Report shape names **both** disagreeing sites — a finding naming one file cannot be acted on +- [ ] Vacuous when fewer than 2 claims exist (ADR-007: report only what you can establish) +- [ ] **Rewrite, never delete, the two existing check-1 fixtures (D8).** The window still exists + under D6, so the off-by-one class is still live and `defect/release-date-far-edge.md` is its + only positive control — the exact bug `/code-review` caught. Rewrite it as: heading at line N, + disagreeing claim at the window's last line → still RED. Rewrite + `defect/version-box-stale-date.md` as an in-box disagreement, since boxes are where the + original REFCARD bug lived. +- [ ] Fixture `falsepos/version-mentioned-in-prose.md`: "Upgrading to v4.5.0 is a drop-in change" + followed by an unrelated `Released:` date → GREEN +- [ ] Fixture `defect/release-date-disagreement.md` pair; `clean/release-date-utc-boundary.md` + rewritten to a two-claim form one day apart → GREEN + +**Real claim sites, confirmed 2026-08-15** — the check must reach both: + +| Site | Shape | Reached via | +|---|---|---| +| `docs/NEWS.md:9` | `**Released:** 2026-08-08` | `## v4.5.0 …` heading two lines above | +| `docs/REFCARD.md:7` | `│ Version: 4.5.0 (released 2026-08-07)` | version-box line, claim on the same line | + +## Phase 4 — Severity split + ADR correction (D2, D11) + +- [ ] Check 1 ships as **`warning`** in this phase. Promotion to `error` happens in Phase 6, gated + on evidence (D11) — the check is being redesigned and promoted in one PR, and the only + evidence it is sound would otherwise be tests written alongside it. +- [ ] `print_phase_status` / phase-label handling copes with mixed severities in one phase +- [ ] ADR-007 Severity section: **remove** the false claim (that both severities fail the gate + identically), replace with verified behavior — `pre-release-check.sh:280` swallows the exit + code, `docs-quality.yml` sets `continue-on-error`, `skills/release/SKILL.md:143` blocks on + the RED label. Record the D11 gate as the reason RED is earned, not assumed. +- [ ] Test: a check-1 finding makes overall status RED once promoted; a check-2-only finding stays + YELLOW + +## Phase 5 — Low findings, date normalization, doc sync (D9) + +- [ ] F8: close `version-box` mode at the first line carrying no box character +- [ ] F8 fixture: unclosed `┌` followed by a `commands/` structure-table row → the row is still + measured against commands, not agents +- [ ] F9: REFCARD `tldr` row → "a line that **opens** with `TL;DR`" +- [ ] **Normalize the two claim sites (D9).** `NEWS.md` and `REFCARD.md` currently sit exactly one + day apart (2026-08-08 vs 2026-08-07), so the tolerance is the only thing keeping the repo + GREEN. Make them agree on the tag-local date and record the convention where the release + skill writes these, so the tolerance is headroom rather than load-bearing. +- [ ] Sync REFCARD, both CHANGELOGs, and the parent + `SPEC-doc-staleness-prose-gaps-2026-08-07.md` — its check-1 description is now wrong +- [ ] Mark the hardening SPEC's acceptance criteria + +## Phase 6 — Verify, promote, update PR #334 (D5, D10, D11) + +- [ ] Full suite in-tree; compare against the dev baseline (20 failed / 2462 passed) +- [ ] `docs-staleness-check.sh` GREEN; runtime under the 30s `test_pre_release_check_runs` budget +- [ ] `validate-counts.sh`, markdownlint, `mkdocs build --strict` +- [ ] **D11 promotion gate:** check 1 ran clean across every tracked `.md` and both known claim + sites, transcript quoted → promote to `error`. If not clean, it **stays `warning`** and the + promotion becomes a follow-up. Do not promote on a passing unit suite alone. +- [ ] **D10:** refresh the `.STATUS` worktree row — it still describes this branch as only the + parent SPEC and its harness, with no mention of the hardening work +- [ ] Update the PR #334 body with the hardening summary and quoted E2E transcripts +- [ ] Watch CI to green — **then stop.** Merging is a separate, explicit instruction. +- [ ] **At merge (D10):** delete `ORCHESTRATE-prose-check-hardening.md`. It is a feature-branch + working artifact, not `dev` content (craft CLAUDE.md). Nothing enforces this — not + `.gitignore`, not `exclusions.txt`, not a test — so it only happens if someone does it. + +## Friction Prevention + +- **The awk pre-filter and the bash matcher must stay in sync** (Phase 2). Two regexes expressing + one rule; if the pre-filter is tighter, lines silently never reach the matcher. This already + caused a 4× perf regression once when they were tuned independently. +- **Runtime budget is real, not theoretical.** `test_pre_release_check_runs` times out at 30s and + is the only thing that caught the last perf regression. Re-time after Phase 3. +- **Do not widen the broad plural-only scan.** Every hardening here is inside the line shapes; the + unscoped scan stays plural-only or the 90+ false positives return. +- **Phase 1 has the largest blast radius** — a record consumed by four phases, JSON output, and + both fix passes. Full suite after it, not just the prose tests. +- **Never delete a positive control to make a redesign easier** (D8). If a fixture's model is + obsolete, rewrite it so the boundary it pinned stays pinned. + +## Acceptance Criteria + +- [ ] Every finding F1–F9 has a test that **fails before its fix and passes after**, each verified + by planted mutation +- [ ] Hyphenated-compound corpus produces zero findings; real counts still caught +- [ ] `[e]xclude` round-trips end-to-end +- [ ] ADR-007's incorrect severity claim is removed, not softened +- [ ] Check 1 reaches both real claim sites (`docs/NEWS.md`, `docs/REFCARD.md`) — transcript quoted +- [ ] The two claim sites agree exactly, so the one-day tolerance is headroom (D9) +- [ ] RED promotion is evidence-gated, not assumed (D11) +- [ ] Live repo GREEN; runtime under budget; dev-baseline parity + +## Commit Strategy + +One commit per phase, conventional prefix, each naming the finding IDs it closes. Phase 1 commits +alone given its blast radius. No squashing before review — the phase boundaries are the review +units. + +## Verification + +```bash +cd ~/.git-worktrees/craft/feature-doc-staleness-prose-gaps +python3 -m pytest tests/test_docs_staleness_prose.py -q # per phase +python3 -m pytest tests/ -q # after Phase 1 and at Phase 6 +./scripts/docs-staleness-check.sh # GREEN +time ./scripts/docs-staleness-check.sh # under 30s +``` + +## Session Instructions + +Implementation continues in this session per craft's ORCHESTRATE convention (no handoff to a new +session). Every phase ends with its mutation controls demonstrated, not merely a passing suite. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e1b42e885..27b46a892 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [Unreleased] + +### Added + +- **Two prose-staleness checks in `docs-staleness-check.sh` Phase 7** + ([SPEC](specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md), + [ADR-007](adr/ADR-007-pattern-scoped-prose-staleness-gating.md)) — a release-date check + (claims near the current version token must be within a day of that version's git tag) and a + count-prose check scoped to four structured line shapes (version box, TL;DR line, bolded + count-summary badge, structure-table row). The count check reads the **singular** noun form + too, closing the gap that let `CLAUDE.md`'s "8 agent definitions" read GREEN for five minors. + Both emit `warning`, per ADR-007's gentle-ramp rationale. +- **Prose-staleness test harness** — `tests/fixtures/prose-staleness/` (10 fixtures across + clean / planted-defect / known-false-positive) and `tests/test_docs_staleness_prose.py`, a + table-driven runner asserting on `--json` findings. Includes a guard that every check keeps a + planted-defect fixture, and a live-repo assertion that `count_consistency` stays clean. + +### Fixed + +- **`docs-staleness-check.sh` pass 2 `[f]ix` applied nothing while reporting success** — it + printed `-> Fixed` and incremented the counter without touching the file. Pass 1 had the same + bug fixed earlier (BSD `sed -i` exits 0 on no match); the sibling pass was left behind. Both now + share one `apply_line_fix`, which reports success only when the file actually changed and + refuses to execute a non-substitution `fix_detail`. +- **Release-date window was one line short of its documented contract** — the decrement ran on the + version-mention line itself, so it scanned that line plus only 3 more instead of 4. +- **An unparseable release-date authority flagged every claim in the repo** instead of skipping. + The check is now vacuous unless its authority parsed, matching the no-tag case. +- `docs/specs/REVIEW-repo-triage-2026-08-07.md` pointed at a GRILL doc that `b1c4426e4` had + archived, failing `test_no_broken_links` on every branch since. +- `CLAUDE.md`'s Project Structure table claimed 8 agent definitions (actual: 2 since v4.0.0); + `README.md`'s highlight block still headlined v2.36.0 at v4.5.0, and its tagline carried a + stale hard test count. + +--- + ## [4.5.0] - 2026-08-08 ### Added diff --git a/docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md b/docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md new file mode 100644 index 000000000..7733ff6db --- /dev/null +++ b/docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md @@ -0,0 +1,136 @@ +# ADR-007: Documentation prose staleness is gated by pattern-scoped line shapes + +**Status:** Accepted +**Date:** 2026-08-15 +**Implements:** [`SPEC-doc-staleness-prose-gaps-2026-08-07.md`](../specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md) +**Related:** ADR-003 (release drift is advisory, not a hard gate) — the severity +posture below follows its gentle-ramp precedent. + +## Context + +`scripts/docs-staleness-check.sh` Phase 7 anchors on structured patterns: badge +strings (`version-X.Y.Z`), declared count lines (`N commands`). That is enough to +keep *numbers* honest and nothing else. Four real staleness bugs shipped and sat +live while Phase 7 reported GREEN over every one of them: + +| Bug | Why Phase 7 missed it | +|---|---| +| `docs/REFCARD.md` — version box with the right number, wrong release date | dates were never checked | +| `docs/skills-agents.md` — TL;DR claiming "8 specialized agents" two lines above its own correct "2" | the intervening word broke the `N agents` pattern | +| `CLAUDE.md` — Project Structure row reading "8 agent definitions" for five minors | the scan matches the plural noun only | +| `README.md` — a v2.36.0 highlight block still headlining at v4.5.0 | highlight prose was never checked | + +The obvious wide fix — search all prose for `\d+ agents?` — was tried in review +and rejected: a grep over `docs/**/*.md` returned 90+ hits, the great majority +legitimate. Orchestration mode-limit prose ("2 agents max", "4 agents"), a +tutorial built around an intentionally fictional plugin, and a troubleshooting +page that prints a wrong count *on purpose* to teach the bug all look identical +to a stale total from a regex's point of view. + +## Decision + +Extend Phase 7 in place with **line-shape-scoped** regexes. A count in free prose +is not checked; a count inside one of four enumerated structured shapes is: + +| Shape | What it is | +|---|---| +| `version-box` | lines inside a box-drawing block (`┌` … `└`) | +| `tldr` | a TL;DR summary line | +| `count-summary` | the bolded badge line (`**48 commands** \| **41 skills**`) | +| `structure-table` | a table row whose first cell names a counted directory | + +Inside those shapes only, the singular noun form is checked alongside the plural, +and the `structure-table` shape compares against the type its own first cell +names. The existing 40%-of-expected floor applies to shaped lines too — structured +shapes still carry legitimate non-totals (category subtotals in a reference box, +a bolded subset count, a narrative count about another plugin). + +Release dates are checked against the **git tag's local date** for the current +version, with a one-day window. Not `.STATUS`'s `release_date:` — the two +legitimately disagree whenever a release publishes across the UTC boundary +(v4.5.0: tag 2026-08-07, GitHub release 2026-08-08T03:44Z), and a check that +flags a correct repo on its first run does not survive its first release. + +Constraints held from the BRAINSTORM: no new script (D1), craft only (D2), no +semantic/NLP layer (D3), no external prose tool (D4). + +## Severity + +Findings are emitted at **`warning`**, matching every existing Phase 7 finding — +not `error`, which the SPEC's first draft specified. + +This is a smaller difference than it reads. `main()` exits 1 for warnings and +errors alike, so both already fail `/craft:check --for release` and +`pre-release-check.sh`; the divergence is the RED/YELLOW label, not whether the +gate blocks. Given that these are the first checks in this repo to judge *prose* +rather than structured tokens, and that the first build of check 2 produced five +false positives before the floor was added, shipping at `warning` follows +ADR-003's gentle-ramp precedent: earn `error` after the checks run clean across a +few real releases. + +## A failed authority makes a check vacuous, never universal + +Every check here compares documentation against an authority — the git tag date, `plugin.json`'s +counts. When an authority is **missing**, the check skips: no tag yet is the normal state on a +feature branch. The rule this ADR adds is that when an authority is **present but unusable**, the +check skips too. + +The first build did not do this. An unparseable tag date left an empty accept-window, and an empty +window matches nothing, so every release-date claim in the repo failed at once. One bad input +became a repo-wide false-positive storm — the loudest possible output from the least reliable +possible input. + +Stated generally, for any check added to this script later: **a check may only report a finding it +can positively establish.** Absence of a usable comparison is not evidence of drift. The failure +modes are not symmetric — a skipped check costs one missed bug, a check that fires on every +document costs the gate its credibility, and a gate nobody trusts gets bypassed. + +## Fixes must be applied, not announced + +A finding carries a `fix_detail`. Both prose checks emit `uncertain`, routing to pass 2's +interactive review rather than pass 1's auto-apply: the surrounding prose is hand-authored, so a +human should see the line before the number changes under it. The `fix_detail` is nonetheless a +real `s/…/…/` substitution rather than a human-readable note, so confirming one actually edits the +file — it swaps the digits only, leaving `agent definitions` intact. + +This is a recurrence guard, not a preference. Pass 1 shipped a version of this bug once already +(BSD `sed -i` exits 0 when nothing matched, so the script reported "Fixed: N items" having +modified nothing on every macOS run) and was fixed for it. Pass 2 kept the same bug in simpler +form — it printed `-> Fixed` and incremented the counter without calling anything at all. Patching +the second site would have left a third to find later, so the applier is now a single shared +function, `apply_line_fix`, which returns true only when the file actually changed and refuses to +execute a `fix_detail` that is not a substitution (Phase 8's `doc-coverage:surface:cmd` markers). + +Same family as the rule above: **do not report a result you have not established.** The vacuous +rule keeps the script from claiming drift it cannot demonstrate; this one keeps it from claiming a +repair it did not perform. + +## Consequences + +**Accepted:** + +- Coverage is exactly as good as the four enumerated shapes. A genuinely new + prose shape requires a code change, not a config change. +- `scripts/config/exclusions.txt` keeps growing. Raised in review, accepted: + it is already craft's mechanism for this class of exception, and shape-scoping + shrinks how fast the list has to grow. +- Two env overrides (`CRAFT_EXPECTED_*`, `CRAFT_RELEASE_DATE`) exist for test + hermeticity. Unset on every production path. + +**Rejected alternatives:** + +- **Vale / `drift` / an LLM-in-CI pass** — dependency weight, and + non-determinism in a release gate is worse than a missed prose bug. +- **A whole-file git-log-touch staleness proxy** — rejected as *falsified*, not + merely unattractive. `bump-version.sh` rewrites `docs/REFCARD.md`'s version + line on every release, so the proxy would report "recently touched, not stale" + on the very file whose staleness motivated this work. A per-line/per-span + version could work; it is real complexity for an advisory signal and is not + being built preemptively. + +## Revisit trigger + +A prose staleness bug that lands **despite** these checks and whose shape cannot +be expressed as a line-shape regex. That, and only that, reopens the D3/D4 +rejection of semantic tooling. A bug that merely needs a fifth shape is a shape, +not a reason to change approach. diff --git a/docs/reference/REFCARD-DOCS-STALENESS.md b/docs/reference/REFCARD-DOCS-STALENESS.md index a616e7c6c..57972d905 100644 --- a/docs/reference/REFCARD-DOCS-STALENESS.md +++ b/docs/reference/REFCARD-DOCS-STALENESS.md @@ -45,12 +45,62 @@ | Phase | Name | What It Checks | |-------|------|---------------| | 6 | Nav Completeness | Files in `docs/` missing from `mkdocs.yml` nav; nav entries pointing to missing files | -| 7 | Count Consistency | Stale `N commands`, `N skills`, `N agents` strings across all docs | +| 7 | Count Consistency | Stale `N commands`, `N skills`, `N agents` strings across all docs, plus the two prose checks below | | 8 | Skill/Agent Coverage | Skills and agents not listed in `docs/skills-agents.md` | | 9 | Cross-Doc Freshness | Stale version strings in REFCARDs, stale counts in "See Also" sections, `site_description` drift | --- +## Phase 7 prose checks + +Added 2026-08-15 — [ADR-007](../adr/ADR-007-pattern-scoped-prose-staleness-gating.md), +[SPEC](../specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md). Both emit `warning`. + +| Check | What It Catches | +|-------|-----------------| +| Release-date claims | A `Released: YYYY-MM-DD` on the current version's line **or the 4 lines below it**, more than one day off the version's **git tag date**. The one-day window absorbs releases published across the UTC boundary. | +| Count prose in structured lines | A stale count — **singular or plural** — inside one of four line shapes. Free prose is never checked. | + +**The release-date check is vacuous when its authority is unusable** — no tag for the current +version (normal on a feature branch), or a tag date that will not parse. It reports nothing rather +than everything: an empty accept-window would match no claim at all, turning one bad input into a +repo-wide false-positive storm. See [ADR-007](../adr/ADR-007-pattern-scoped-prose-staleness-gating.md). + +The four line shapes: + +| Shape | Matches | +|-------|---------| +| `version-box` | lines inside a `┌` … `└` box-drawing block | +| `tldr` | a line containing `TL;DR` | +| `count-summary` | the bolded badge line, e.g. `**48 commands** \| **41 skills**` | +| `structure-table` | a table row whose first cell is a counted directory, e.g. `` \| `agents/` \| `` — compared only against the type that cell names | + +Why shape-scoped: a blanket `N agents?` search over `docs/` returns 90+ hits, nearly +all legitimate (orchestration mode-limit prose, a fictional-plugin tutorial, a +troubleshooting page printing a wrong count on purpose). Shaped lines are additionally +held to the same 40%-of-expected floor as the broad scan, because boxes and badges +still carry category subtotals and subset counts. + +Both prose findings are `uncertain`, so they surface in **Pass 2** (interactive), not Pass 1's +auto-apply — the surrounding prose is hand-authored, so a human sees the line before the number +changes under it. Choosing `[f]ix` there really does edit the file; it swaps the digits only, +leaving wording like `agent definitions` intact. + +Fixtures and the table-driven runner: `tests/fixtures/prose-staleness/` + +`tests/test_docs_staleness_prose.py`. Every check has a `defect/` fixture as its +positive control; a check without one is a rejected change. + +### Test-only environment overrides + +| Variable | Effect | +|----------|--------| +| `CRAFT_EXPECTED_CMDS` / `_SKILLS` / `_AGENTS` | Declare expected counts instead of deriving them from `commands/`, `skills/`, `agents/`. Lets a fixture skip materializing 48 command files. | +| `CRAFT_RELEASE_DATE` | Supply the tag date instead of reading git, keeping the fixture suite hermetic. | + +Unset on every production path — the derived values are what actually run. + +--- + ## Traffic Light Output ```text @@ -84,7 +134,13 @@ When `--fix` is specified: | `s` | Skip -- leave unchanged | | `e` | Exclude -- add to `exclusions.txt` permanently | -Pass 2 is skipped when `--non-interactive` is set. +`f` reports `Cannot auto-fix (manual edit needed)` when the finding carries no substitution to +run -- Phase 8's doc-coverage findings, for instance. Both passes apply fixes through the same +`apply_line_fix`, which reports success only when the file actually changed; see +[ADR-007](../adr/ADR-007-pattern-scoped-prose-staleness-gating.md) on why that is one shared +function and not two. + +Pass 2 is skipped when `--non-interactive` is set, and when stdin is not a TTY (CI). --- diff --git a/docs/specs/GRILL-prose-check-hardening-2026-08-15.md b/docs/specs/GRILL-prose-check-hardening-2026-08-15.md new file mode 100644 index 000000000..3b26dd462 --- /dev/null +++ b/docs/specs/GRILL-prose-check-hardening-2026-08-15.md @@ -0,0 +1,232 @@ +# GRILL: Prose-Check Hardening + +**Date:** 2026-08-15 · **Spec:** [`SPEC-prose-check-hardening-2026-08-15.md`](SPEC-prose-check-hardening-2026-08-15.md) +**Branches resolved:** 11 (D1–D6 design · D7–D11 plan) · **Status:** locked, ready for implementation + +Interrogates the 9 defects a high-effort review found in PR #334. Two branches were reframed by +evidence gathered during the grill rather than by argument — see D1 and the correction under D4. + +--- + +## D1 — Check 1's authority: **cross-file consistency**, not the git tag + +**Locked:** drop the external authority. Compare every release-date claim for the current version +against each other and flag disagreement. The one-day window survives as the agreement tolerance +between claims, absorbing the UTC-boundary case that motivated it. + +**Why the tag was rejected — evidence, not preference.** The tag is absent in both environments +the check exists to protect: + +| Environment | Why no tag | +|---|---| +| Release gate | `skills/release/SKILL.md` runs Step 3b (write doc dates) → **Step 3b.5 (staleness gate)** → Step 8 (create tag). The tag for the version being released does not exist when the gate runs. | +| CI | `docs-quality.yml` uses `actions/checkout@v5` with no `fetch-tags`, so `git for-each-ref refs/tags/vX.Y.Z` returns empty. | + +Net: as built, check 1 fires only on a developer machine that has already pulled the tag — which +is not a gate. Cross-file consistency needs no external authority, so it works in both. + +**Accepted cost:** cannot catch a date that is uniformly wrong in every file, and says nothing +when only one claim exists. Both are strictly better than never firing. + +## D2 — Severity: **split — check 1 RED, check 2 YELLOW** + +**Locked:** severity follows demonstrated precision, per-check rather than per-phase. + +**Corrects a factually wrong claim in ADR-007.** The ADR justified `warning` by asserting that +warnings and errors both fail the release gate, so only the label differs. Verified false: + +- `scripts/pre-release-check.sh:280` — "Check 9: Docs staleness (warn-only, does not block + release)", and the call is `… || true`, discarding the exit code. +- `.github/workflows/docs-quality.yml` — `continue-on-error: true`. +- `skills/release/SKILL.md:143` — "RED findings block; YELLOW findings warn but allow proceed." + +No consumer reads the exit status. The **label is the only gate**, so the severity choice *is* the +block decision the ADR claimed it was not. ADR-007's Severity section must be rewritten with the +verified behavior — removed, not softened. + +Check 1 after D1 is near-binary (two claims agree or they do not), so it can afford to block. +Check 2 matches prose patterns and produced three false-positive defects in this PR alone +(F1, F6, and the five sub-threshold counts caught pre-merge), so it has not earned a blocker. + +## D3 — F1/F2/F6: **boundary + span-anchored substitution + real floor**, all three + +**Locked:** + +| Change | From | To | +|---|---|---| +| Noun trailer | `([^a-z]\|$)` | whitespace / closing punctuation / end-of-line | +| Fix payload | substitution built from the bare `N noun` prefix | anchored on the full matched span | +| Floor | `expected * 40 / 100` | `max(2, expected * 40 / 100)` | + +**Why all three, not just the trailer.** F1's agent half is *caused* by F6 — the floor is +`$((2 * 40 / 100))` == 0, so the guard ADR-007 and two code comments cite as what makes shaped +lines safe does not exist for the smallest count type. Fixing the trailer alone leaves that root +cause live. And F2 corrupts the author's prose, a worse outcome than any false warning, so the +substitution is anchored independently of the matcher: a future boundary regression then produces +a wrong warning rather than a wrong edit. + +**Must stay pinned:** the E1 case (`8 agent definitions`) survives both changes — the noun is +followed by a space, and `8 ≥ max(2, 0)`. Existing `clean/structure-table-correct.md` and +`defect/structure-table-singular.md` already cover it; they must keep passing unchanged. + +## D4 — F4 `[e]xclude` no-op: **fix at the record level, both phases** + +**Correction made during the grill.** This was initially reported, and relayed, as a regression +introduced by PR #334's change of the finding's `file` field to `path:lineno`. It is not. +`git show dev:scripts/docs-staleness-check.sh` has `add_finding 7 "warning" "${file}:${lineno}"` +at line 341 and the same shape in Phase 9 at 497 and 548. `[e]xclude` has been a no-op for **all** +Phase 7 and Phase 9 findings, predating this PR. + +**Locked:** carry the line number as its own field instead of glued into `file`, so `[e]` writes +`docs/x.md:30 command` and `is_pattern_excluded` can match it. + +**Why the larger blast radius is accepted.** The bug prints "Excluded (added to exclusions.txt)" +and the finding returns on the next run — a live instance of the exact +reports-success-changes-nothing class ADR-007 states as a rule. Shipping that rule while the same +file violates it twice is worse than either fixing it or not writing the rule. Fixing it in the +`[e]` branch alone would leave the underlying ambiguity (is `file` a path or a location?) for the +next person. + +## D5 — Delivery: **amend PR #334 before merging** + +**Locked:** all fixes land on `feature/doc-staleness-prose-gaps`; nothing merges until the checks +are correct. + +**Why not merge-then-harden.** As it stands the check false-positives on any hyphenated compound, +and pass 2's `[f]` would rewrite the author's prose around it. craft's own docs are hyphen-heavy, +so "merge now, harden in #335" means `dev` carries a `--fix` that can mangle documents for as long +as the follow-up takes. The PR has not shipped and nothing depends on it, so the cost of holding is +only review surface. + +**Accepted cost:** #334 becomes a large single review, and the D4 record change touches all four +phases. + +## D6 — Window discipline: **the version token must be in a heading** + +**Added 2026-08-15, after the initial 5 branches.** Surfaced while authoring +`ORCHESTRATE-prose-check-hardening.md`: the spec listed 9 decisions and the grill locked 5, leaving +the release-date **window discipline** (spec D4 / finding F3) unresolved. D1 settled where the +authority comes from, not how claims are collected — so the false positive survived the redesign +in a new place. + +**Locked:** the window opens only when the version token appears in a markdown heading or a +version-box line. Prose mentioning the version — "Upgrading to v4.5.0 is a drop-in change" — does +not open it, so a nearby unrelated date is never collected as a claim for that version. + +**Why structural over numeric.** Shrinking the window (spec D4c) narrows the class without closing +it: a date two lines under a prose mention still false-positives. Requiring version and date on the +same line (D4b) closes it completely but misses the real `docs/NEWS.md` layout, where the heading +carries the version and the date sits two lines below — i.e. it would not catch the bug the check +exists for. A heading requirement excludes running prose by construction, and every site carrying +a real release entry today is a heading or a box. + +**Raised stakes:** D2 makes check 1 RED, so each surviving false positive blocks a release. That +rules out spec D4d (accept and exclude case-by-case). + +--- + +## Plan-level branches — grilling `ORCHESTRATE-prose-check-hardening.md` + +D1–D6 interrogated the *design*. These interrogate the *plan to build it*, and four of the five +found defects in the plan rather than confirming it. + +### D7 — Phase order: **the record change goes first** + +**Locked:** reorder to record-change → matcher → check 1 → severity → low findings → verify. + +As written, Phases 1–3 authored `add_finding` calls and tests against the old signature and Phase 4 +then rewrote all **12 call sites** (Phase 6 ×2, Phase 7 ×3, Phase 8 ×3, Phase 9 ×4). Every prose +finding would be written twice and its tests touched twice, with the mechanical 12-site sweep +landing last — where a missed site is likeliest and hardest to spot against three phases of other +changes. Going first also lands the widest change against an unmodified baseline, so the full suite +is a clean control. + +**Accepted cost:** the largest-blast-radius change precedes the bug fixes, so stopping halfway +leaves the HIGH findings open. + +### D8 — Fixtures: **rewrite, never delete** + +**Locked:** the plan's "delete … or rewrite them" is narrowed to rewrite-only. + +`defect/release-date-far-edge.md` is the positive control for the off-by-one `/code-review` caught. +D6 keeps a window (a heading opens it, claims are collected below), so **the off-by-one class is +still live** — deleting the fixture would re-open a bug a reviewer already found once, and the plan +permitted exactly that with an "or". Rewritten as: heading at line N, disagreeing claim at the +window's last line → still RED. `defect/version-box-stale-date.md` becomes an in-box disagreement, +since boxes are where the original REFCARD bug lived. + +**Generalized into Friction Prevention:** never delete a positive control to make a redesign +easier; if its model is obsolete, rewrite it so the boundary it pinned stays pinned. + +### D9 — Normalize the two claim sites + +**Locked:** make `docs/NEWS.md` and `docs/REFCARD.md` agree exactly, and record the convention. + +Evidence gathered while grilling the plan — craft has exactly two release-date claim sites for the +current version, and they already disagree: + +| Site | Claim | +|---|---| +| `docs/NEWS.md:9` | `**Released:** 2026-08-08` (UTC date) | +| `docs/REFCARD.md:7` | `│ Version: 4.5.0 (released 2026-08-07)` (tag-local date) | + +Both are reachable under D6, so check 1 fires on craft today and **only the one-day tolerance keeps +it GREEN**. With D2 making check 1 RED, the repo would sit permanently one day from a blocked +release, with no warning beforehand because one day always reads GREEN. Normalizing returns the +tolerance to headroom; it still exists for the real UTC-boundary cause D1 preserved. + +**Rejected:** widening the tolerance to 2 days buys headroom by blunting the check; dropping it +entirely throws away the UTC handling D1 deliberately kept. + +### D10 — Phase 6 must carry the workflow steps + +**Locked:** Phase 6 refreshes the `.STATUS` worktree row, and the ORCHESTRATE file is deleted at +merge. + +Both are required by craft's own `CLAUDE.md` and **neither is enforced** — `ORCHESTRATE-*.md` is +not gitignored, not in `exclusions.txt`, and no test guards it. The `.STATUS` row still describes +this branch as only the parent SPEC and its harness. Shipping a doc-staleness feature while leaving +a stale status row and a working artifact on `dev` would be its own small joke. + +### D11 — RED promotion is **evidence-gated** + +**Locked:** check 1 ships as `warning` in the severity phase; promotion to `error` happens in +Phase 6 only after it runs clean across every tracked `.md` and both known claim sites, transcript +quoted. If not clean, it stays `warning` and promotion becomes a follow-up. + +Check 1 is being *redesigned* (D1, D6) and *promoted to release-blocking* (D2) in the same PR. The +only evidence it is sound would otherwise be tests written alongside it by the same author in the +same sitting. A false-positive class in a freshly-redesigned blocking check is discovered at the +worst possible moment — mid-release. + +This does not reverse D2; it makes RED earned rather than assumed. + +**Also recorded:** the plan's original 2-hour estimate was optimistic. Phase 1 alone is 12 call +sites plus JSON rendering plus both fix passes plus an end-to-end exclusion test. Revised to ~3 +hours. + +--- + +## Not branched (no genuine alternative) + +- **F8** — unclosed `┌` leaks `version-box` mode into the rest of the file, losing the + `structure-table` type restriction. Fix: close the box on the first line carrying no box + character. +- **F9** — REFCARD says `tldr` matches "a line containing `TL;DR`"; the regex requires the line to + *open* with it. The regex is deliberate (`falsepos/tldr-mentioned-not-claimed.md` depends on + it). Fix the doc. + +## Open questions + +- **D1 leaves single-claim files unguarded.** If only `docs/NEWS.md` carries a release date for the + current version, cross-file consistency says nothing. Worth revisiting once a second claim site + is guaranteed — not blocking, since the status quo catches nothing anywhere. +- **Phase 9's severity is untouched.** D2 sets per-check severity in Phase 7 only; whether Phase 9 + should follow is unexamined. +- **The floor's 40% constant is still unjustified.** D3 puts a minimum under it but does not ask + whether 40% was ever the right shape. Inherited from the pre-existing broad scan. + +## Next step + +`/craft:plan docs/specs/SPEC-prose-check-hardening-2026-08-15.md` → `plan-orchestrator` → +`ORCHESTRATE-*.md`, then implement on `feature/doc-staleness-prose-gaps` per D5. diff --git a/docs/specs/REVIEW-repo-triage-2026-08-07.md b/docs/specs/REVIEW-repo-triage-2026-08-07.md index 060a21de5..a8a53552a 100644 --- a/docs/specs/REVIEW-repo-triage-2026-08-07.md +++ b/docs/specs/REVIEW-repo-triage-2026-08-07.md @@ -1,6 +1,6 @@ # ADVERSARIAL REVIEW: Repo Triage -**Date:** 2026-08-07 · **Reviewed:** [`BRAINSTORM-repo-triage-2026-08-07.md`](BRAINSTORM-repo-triage-2026-08-07.md), [`GRILL-repo-triage-2026-08-07.md`](GRILL-repo-triage-2026-08-07.md) +**Date:** 2026-08-07 · **Reviewed:** [`BRAINSTORM-repo-triage-2026-08-07.md`](BRAINSTORM-repo-triage-2026-08-07.md), [`GRILL-repo-triage-2026-08-07.md`](_archive/GRILL-repo-triage-2026-08-07.md) **Method:** 4 independent parallel lenses (backend/systems, frontend/interaction, architecture, ADHD-friendly cognitive-load), each grounded against the real codebase, not just the planning docs. ## Verdict: 2 of the GRILL's 8 locked decisions rest on factually wrong premises diff --git a/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md index db1bf6c3f..88073d681 100644 --- a/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md +++ b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md @@ -22,9 +22,16 @@ the scan pattern is `\b[0-9]+ ${ctype}\b` where `ctype ∈ {commands, skills, ag plural (`scripts/docs-staleness-check.sh:345`). `8 agent definitions` never matched. The 40% minimum threshold (line 302) was not the cause; 8 clears it. -This is not a check-2 line-shape case — it is a **pre-existing gap in the check-2 predecessor**. -Fold the singular alternation into the shared matcher rather than shipping check 2 on top of a -pattern that still can't see half the noun forms. +**Resolution (revised during build).** The first reading of this finding was "fold the singular +alternation into the shared matcher" — i.e. widen the broad scan at line 345. That reopens +exactly the false-positive surface review finding 3 closed, and adds singular forms of it +("the 4 agent limit") on top. + +The singular form is therefore checked **only inside check 2's line shapes**, and the broad scan +is left plural-only. `CLAUDE.md`'s Project Structure row is itself a structured shape — a table +row whose first cell names a counted directory — so E1 is caught by adding a **fourth line +shape** (`structure-table`), not by widening anything. That row's count is compared only against +the type its own first cell names, so a `commands/` row is never measured against the agent count. ### E2 — "release date" has two defensible authorities @@ -38,11 +45,54 @@ tag's local date (`git for-each-ref --format='%(creatordate:short)' refs/tags/vX accept a ±1-day window against `.STATUS`'s `release_date:` to absorb the UTC boundary. A same-day-either-side match is not staleness. -### Severity divergence to resolve at build time +### Severity — resolved: `warning` Existing Phase 7 findings are emitted at severity `warning` (`add_finding 7 "warning" ...`, -line 341) while this SPEC specifies checks 1–2 as blocking RED. Whichever way this lands, it is -an intentional choice and belongs in the ADR below, not in a silent code default. +line 341) while this SPEC's first draft specified checks 1–2 as blocking RED. + +**Resolved to `warning`**, per ADR-007. `main()` exits 1 for warnings and errors alike, so both +already fail `/craft:check --for release` and `pre-release-check.sh` — the divergence is the +RED/YELLOW label, not whether the gate blocks. These are the first checks in this repo to judge +prose rather than structured tokens, and the first build of check 2 produced five false +positives before the 40% floor was added; ADR-003's gentle-ramp precedent applies. `error` is +earned after the checks run clean across a few real releases. + +### Post-build findings (2026-08-15) — four bugs, four different catchers + +Every one of these was in the first build and none was caught by the gate that "should" have. +Recorded because the pattern is the point: no single review mechanism found more than one. + +| # | Bug | Caught by | Fix | +|---|---|---|---| +| B1 | Script ran at 36s against a dev baseline of 8.0s — ~1300 per-file `awk` spawns plus ~21000 `grep` calls over every line of every box block | the **existing suite** — a 30s timeout in `test_pre_release_check_runs` | one `awk` pass over the file list, an in-awk `hascount()` pre-filter, date window computed once. 8.7s | +| B2 | An unparseable authority date left an empty accept-window, and an empty window matches nothing — so **every** release-date claim in the repo was flagged at once | **self-review** of the PR | the check is vacuous unless the authority parsed, same posture as the no-tag case | +| B3 | `win = 4` scanned the version line plus only **3** more, because `win--` runs on the version line itself — one short of the 4 following lines this SPEC and the REFCARD both advertise | **`/code-review`** | `win = 5`; defect fixture placed at exactly the far edge | +| B4 | `pass2_interactive_review`'s `[f]ix` printed `-> Fixed` and incremented `TOTAL_FIXED` without touching the file | **reading adjacent code** while tracing where findings are routed | pass 1's applier extracted to `apply_line_fix`, shared by both passes | + +Two of these are the same failure mode wearing different clothes — **a check that reports a +result it did not actually establish**. B2 flags without evidence; B4 claims a fix it never made. +B4 is also a *recurrence*: pass 1 had this exact bug (BSD `sed -i` exiting 0 on no match) and was +fixed for it; the sibling pass was left behind. Sharing one applier is what closes the class, not +patching the second site. + +### Fix routing, decided here + +Both prose checks emit `uncertain`, so they land in pass 2's interactive review rather than pass +1's auto-apply. The surrounding prose is hand-authored — a human should see the line before the +number changes under it. But the `fix_detail` is a real `s/…/…/` substitution, not a +human-readable note, so confirming one actually edits the file; it swaps the digits only, leaving +`agent definitions` intact. Phase 8's doc-coverage findings carry a `doc-coverage:surface:cmd` +marker instead, and `apply_line_fix` refuses to execute anything that is not a substitution. + +### Build-time finding: the floor applies to shaped lines too + +The first build of check 2 ran without the broad scan's 40%-of-expected floor, on the assumption +that structured shapes are self-limiting. They are not. Five legitimate counts were flagged on +`dev` immediately: category subtotals inside reference boxes (`SMART (4 commands)`, +`Code (12 commands)`), a bolded subset count (`` `--refine` is declared on **9 commands** ``), and +a narrative count about a different plugin (`kept shipping **0 skills**`). Shape membership +narrows *where* to look; the floor is still what separates a total from a subtotal. All five are +now `falsepos/` fixtures. --- @@ -187,19 +237,30 @@ first-class. ```text tests/fixtures/prose-staleness/ clean/ - refcard-version-box.md # correct date + counts inside a ┌─┐ box - skills-agents-tldr.md # correct "2 specialized agents" TL;DR line - claude-md-structure-table.md # correct "2 agent definitions" (E1 regression) + version-box-correct.md # correct date + counts inside a ┌─┐ box + tldr-correct.md # correct "2 specialized agents" TL;DR line + structure-table-correct.md # correct "2 agent definitions" (E1 regression) + release-date-utc-boundary.md # date 1 day off the tag — the UTC case (E2) defect/ - refcard-stale-date.md # check 1 — date ≠ tag date + version-box-stale-date.md # check 1 — date well off the tag + release-date-far-edge.md # check 1 — stale date at the window's last line (B3) tldr-eight-agents.md # check 2 — "8 specialized agents" in a TL;DR line structure-table-singular.md # check 2 — "8 agent definitions", singular form (E1) falsepos/ - orch-flag-usage.md # "2 agents max" / "4 agents" mode-limit prose - fictional-plugin-tutorial.md # intentionally fictional example counts - troubleshooting-wrong-count.md # deliberately-wrong count teaching the bug + category-subtotal-box.md # "(4 commands)" subtotals inside a reference box + subset-bold-count.md # "**9 commands**" subset + "**0 skills**" narrative + mode-limit-prose.md # "2 agents max" / "4 agents" mode-limit prose ``` +> **Revised during build.** The review's other two named false-positive sources (the +> fictional-plugin tutorial and the intentional-wrong-count troubleshooting page) are covered by +> path-keyed entries in `exclusions.txt`, so a fixture of them would re-prove the exclusion +> loader rather than this SPEC's new code. They were replaced by the three sources the first +> build of check 2 actually flagged, each isolating a distinct guard: the 40% floor +> (`category-subtotal-box`, `subset-bold-count`) and shape scoping plus exclusion inheritance +> (`mode-limit-prose`, written to its real path so the path-keyed exclusion applies). Ten +> fixtures, not nine. + ### Contract Each fixture is a **complete minimal markdown file**, not a snippet — the checks operate on @@ -212,51 +273,80 @@ The runner (`tests/test_docs_staleness_prose.py`, alongside the existing | Fixture | Check | Expected | Proves | |---|---|---|---| -| `clean/refcard-version-box.md` | 1 | GREEN | no false positive on correct content | -| `defect/refcard-stale-date.md` | 1 | RED | planted-defect positive control | -| `clean/claude-md-structure-table.md` | 2 | GREEN | E1 stays fixed | -| `defect/structure-table-singular.md` | 2 | RED | E1 would be caught, not missed | +| `clean/version-box-correct.md` | 1 | GREEN | no false positive on correct content | +| `clean/release-date-utc-boundary.md` | 1 | GREEN | E2: a one-day gap is the UTC boundary | +| `defect/version-box-stale-date.md` | 1 | RED | planted-defect positive control | +| `defect/release-date-far-edge.md` | 1 | RED | B3: the window reaches its documented last line | +| `clean/tldr-correct.md` | 2 | GREEN | correct counts in a TL;DR line | +| `clean/structure-table-correct.md` | 2 | GREEN | E1 stays fixed | | `defect/tldr-eight-agents.md` | 2 | RED | the original review bug | -| `falsepos/*.md` (×3) | 2 | GREEN | review finding 3's sources stay unflagged | +| `defect/structure-table-singular.md` | 2 | RED | E1 would be caught, not missed | +| `falsepos/category-subtotal-box.md` | 2 | GREEN | subtotals in a box are not the total | +| `falsepos/subset-bold-count.md` | 2 | GREEN | a bolded count can be a subset | +| `falsepos/mode-limit-prose.md` | 2 | GREEN | free prose is out of shape scope | + +Each case is run against a throwaway repo holding exactly one fixture document, invoked with +`--json` so the assertion reads the structured `findings[]` array rather than parsing colored +terminal output — the same reason `docs-staleness-check.sh` already ships `--json`. The +destination path inside that repo is declared per case, because the exclusion that protects +`mode-limit-prose` is path-keyed. -Invoked via `--json` against a fixture directory so the assertion reads the structured -`findings[]` array rather than parsing colored terminal output — the same reason -`docs-staleness-check.sh` already ships `--json`. +Two harness-level tests sit alongside the table: one asserting every check still owns a +`defect/` fixture (so a future check cannot ship happy-path-only), and one running the real +`dev` tree and asserting `count_consistency` has zero findings — the assertion that caught the +missing 40% floor. ### Harness requirements - **The harness must be able to fail.** Adding a check without its `defect/` row is a rejected change; the positive control is the point (see `e2e-before-pr.md`). - **No network, no git-history walk, no TTY.** Fixtures are self-contained files; the one git - read check 1 needs (tag date) is injected as a parameter, not shelled out to, so the suite is - hermetic and runs identically in CI. + read check 1 needs (tag date) is injected via `CRAFT_RELEASE_DATE`, and the expected counts + via `CRAFT_EXPECTED_{CMDS,SKILLS,AGENTS}`, so the suite is hermetic and no fixture has to + materialize 48 command files. Both overrides are unset on every production path. +- **Fixture authority values are pinned, not derived.** The harness hardcodes 48/41/2 and the + v4.5.0 tag date rather than reading the live repo, so a future count change cannot silently + turn a planted defect into a non-defect (an "8 agents" defect stops proving anything the day + craft ships 8 agents). - **Reuse, don't fork.** `scripts/config/exclusions.txt` handling, `is_file_excluded`, and `is_pattern_excluded` are called as-is — the harness tests the real code path, not a copy. ## Acceptance Criteria -- [ ] Checks 1 and 2 are implemented as blocking (RED) findings in Phase 7, or the divergence - from Phase 7's existing `warning` severity is recorded as a deliberate choice in ADR-007. -- [ ] The shared count matcher handles the **singular** noun form (`N agent definitions`), not - only the plural (E1) — with `clean/` + `defect/` fixtures proving both directions. -- [ ] Check 1 resolves the release-date authority per E2 (git tag local date, ±1-day window - against `.STATUS`) and does **not** flag the current `docs/NEWS.md` v4.5.0 entry. -- [ ] `docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md` exists and records every row +- [x] Checks 1 and 2 are implemented in Phase 7 at severity `warning`, with the divergence from + the draft's "blocking RED" recorded as a deliberate choice in ADR-007's Severity section. +- [x] The **singular** noun form (`N agent definitions`) is checked (E1) — inside check 2's line + shapes only, not by widening the broad scan; `clean/structure-table-correct.md` + + `defect/structure-table-singular.md` prove both directions. +- [x] Check 1 resolves the release-date authority per E2 (git tag local date, one-day window) + and does **not** flag the current `docs/NEWS.md` v4.5.0 entry — `clean/release-date-utc-boundary.md` + pins it. +- [x] `docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md` exists and records every row of the ADR table above, including the rejected alternatives and the revisit trigger. -- [ ] The test harness exists at `tests/fixtures/prose-staleness/` with all 9 fixtures and a - table-driven runner asserting against `--json` output. -- [ ] Check 2's line-shape scoping is itself tested against the 3 false-positive sources the - review found (`docs/guide/orch-flag-usage.md`, the fictional-plugin tutorial, the - intentional-bug troubleshooting page) — must NOT flag any of them. -- [ ] Regression fixtures restore today's exact bugs (REFCARD.md's stale date, - skills-agents.md's "8 specialized agents") and prove the new checks catch them - (planted-defect positive control, same pattern used for `test_no_live_legacy_check_skill_references`). -- [ ] Running the check against current (already-fixed) `dev` HEAD stays GREEN — no - false positive on the corrected content. -- [ ] `docs/reference/REFCARD-DOCS-STALENESS.md` documents the 3 new checks in its - existing check inventory. -- [ ] `CHANGELOG.md` / `docs/CHANGELOG.md` `[Unreleased]` gets a one-line entry. -- [ ] No new script, no new external dependency (D1/D4). +- [x] The test harness exists at `tests/fixtures/prose-staleness/` (11 fixtures — see the + revision note in Test Harness) with a table-driven runner asserting against `--json`. +- [x] A broken release-date authority makes the check **vacuous, never universal** (B2), pinned by + `test_unparseable_authority_date_is_vacuous_not_universal`. +- [x] The release-date window reaches the last line it documents (B3), pinned by + `defect/release-date-far-edge.md` at exactly that boundary. +- [x] A reported fix actually edits the file (B4) — `apply_line_fix` is shared by both passes and + pinned by two tests, including that a `false` result leaves the file byte-identical. +- [x] Runtime stays within the existing `test_pre_release_check_runs` budget (B1): 8.7s against a + dev baseline of 8.0s, versus 36s before the fix. +- [x] Check 2's line-shape scoping is tested against real false-positive sources — revised from + the review's 3 named files to the 3 the first build actually flagged, since two of the + originals are covered by path-keyed exclusions rather than by this SPEC's code. +- [x] Regression fixtures restore the exact bugs (stale release date, "8 specialized agents", + "8 agent definitions") and prove the new checks catch them. Positive controls verified by + two planted mutations: removing the 40% floor fails 3 tests, dropping the singular + alternation fails exactly the E1 defect test. +- [x] Running the check against current `dev` HEAD stays GREEN — asserted continuously by + `test_live_repo_stays_green_on_count_consistency`, not just checked once by hand. +- [x] `docs/reference/REFCARD-DOCS-STALENESS.md` documents the new checks, the four line shapes, + and the two test-only env overrides in its existing inventory. +- [x] `CHANGELOG.md` / `docs/CHANGELOG.md` `[Unreleased]` gets an entry. +- [x] No new script, no new external dependency (D1/D4) — the checks live inside + `docs-staleness-check.sh`, using awk and the python3 it already requires. ## Test Plan diff --git a/docs/specs/SPEC-prose-check-hardening-2026-08-15.md b/docs/specs/SPEC-prose-check-hardening-2026-08-15.md new file mode 100644 index 000000000..b70b439f9 --- /dev/null +++ b/docs/specs/SPEC-prose-check-hardening-2026-08-15.md @@ -0,0 +1,137 @@ +# Prose-Check Hardening — Spec + +**Generated:** 2026-08-15 +**Context:** Craft v4.5.0 — hardens the Phase 7 prose checks added in PR #334 +**Sources:** high-effort `/code-review` of PR #334 (9 findings), plus +[`SPEC-doc-staleness-prose-gaps-2026-08-07.md`](SPEC-doc-staleness-prose-gaps-2026-08-07.md) +and [ADR-007](../adr/ADR-007-pattern-scoped-prose-staleness-gating.md) +**Status:** grilled 2026-08-15 — 5 branches locked, ready to implement. Decisions live in +[`GRILL-prose-check-hardening-2026-08-15.md`](GRILL-prose-check-hardening-2026-08-15.md); where +this spec's option tables and the ledger differ, **the ledger wins**. + +--- + +## Problem + +PR #334 added two prose checks to `docs-staleness-check.sh` Phase 7. CI is green, the full suite +passes, and the live repo reports GREEN. An adversarial review nonetheless found 9 defects, 7 of +which are invisible to every gate the repo has, because the gates only prove the checks don't fire +on *craft's current docs* — not that they fire correctly on any other input. + +Two of the nine make the feature **worse than not having it**, and one suggests it may never fire +where it was designed to. + +## Verified findings + +Each reproduced against the real script before being written down. F1, F2, F6 reproduced by +constructing input; F5, F7 by reading the consuming code. + +| ID | Sev | Defect | Evidence | +|----|-----|--------|----------| +| F1 | HIGH | The singular matcher's `([^a-z]\|$)` trailer accepts `-`, so hyphenated compounds read as counts | `30 command-line entry points` → `'30 command' (expected 48)`; `3 agent-facing surfaces` → `'3 agent' (expected 2)` | +| F2 | HIGH | Accepting the offered fix **corrupts that prose**: the substitution is built from the bare `N noun` prefix | `[f]ix` rewrites `30 command-line entry points` → `48 command-line entry points` | +| F3 | MED | Release-date window is proximity-only — any line mentioning the version opens it, with no requirement the date belongs to that version | an upgrade guide saying "Upgrading to v4.5.0 is a drop-in change" claims the next `Released:` date within 4 lines | +| F4 | MED | Pass 2 `[e]xclude` is a no-op for every Phase 7 prose finding | `file` is now `path:lineno`, so the entry written is `docs/x.md:3:30 command`; `is_pattern_excluded` splits on the first colon and can never match | +| F5 | MED | ADR-007's stated basis for `warning` over `error` is factually wrong | `pre-release-check.sh:280` is headed "warn-only, does not block release" and swallows the exit with `\|\| true`; `docs-quality.yml` sets `continue-on-error: true`; `skills/release/SKILL.md:143` says "RED findings block; YELLOW findings warn" | +| F6 | MED | The 40%-of-expected floor is **0** for agents | `$((2 * 40 / 100))` == 0, so the guard ADR-007 cites as what makes shaped lines safe is absent for the smallest count type | +| F7 | MED | Check 1 is vacuous in both automated environments it targets | `docs-quality.yml` uses `actions/checkout@v5` with no `fetch-tags`, so no tag exists → silent skip on every CI run; and release writes NEWS/REFCARD dates *before* the tag is created | +| F8 | LOW | An unclosed `┌` puts the rest of the file in `version-box` mode, losing the `structure-table` type restriction | a `commands/` row then gets measured against the agent count | +| F9 | LOW | REFCARD says `tldr` matches "a line containing `TL;DR`"; the regex requires the line to *open* with it | doc contradicts implementation | + +### Root causes, not nine unrelated bugs + +Grouping matters more than the count — patching nine sites individually would leave the classes open. + +- **C1 — "one more character class" reasoning.** F1 and F8 are both boundary conditions on a + regex written for the cases in front of it. F1 assumed word-final; F8 assumed boxes close. +- **C2 — a guard cited but not verified.** F6: ADR-007 and two code comments claim the 40% floor + makes shaped lines safe. Nobody evaluated it for the smallest count. F1's agent half is a + *consequence* of F6, not an independent bug. +- **C3 — reporting a result never established.** F2 and F4, both the same family PR #334 already + fixed once in `[f]`. **Corrected during the grill:** F4 was first written up here as introduced + by this PR's change of `file` to `path:lineno`. It is not — + `git show dev:scripts/docs-staleness-check.sh` already has `add_finding 7 "warning" + "${file}:${lineno}"` at line 341, and the same shape in Phase 9 at 497 and 548. `[e]xclude` has + been a no-op for **all** Phase 7 and Phase 9 findings, predating this PR. +- **C4 — the check was never exercised where it runs.** F3, F5, F7: proximity heuristic never + tested against adversarial prose; severity chosen from an unverified claim about consumers; + authority never checked for availability in CI or at release time. + +## Decisions required + +These are open. Options are listed with the tradeoff, not pre-resolved. + +| # | Decision | Options | +|---|----------|---------| +| D1 | F1 trailer fix | (a) `([^a-z-]\|$)` — minimal; (b) also exclude `'` and Unicode dashes; (c) require the noun be followed by whitespace/punctuation via an explicit allowlist | +| D2 | F6 floor for small counts | (a) `max(2, 40%)`; (b) a fixed absolute floor per type; (c) drop the floor inside shapes and rely on tighter shapes instead | +| D3 | F2 substitution safety | (a) anchor the substitution with the same trailing guard as D1; (b) include the full matched span incl. trailing char; (c) stop offering a fix for prose findings and route to manual edit only | +| D4 | F3 window discipline | (a) require the version token be in a **heading** line; (b) require the date line itself to name the version; (c) keep proximity but shrink to 2 lines; (d) accept the false-positive class and exclude case-by-case | +| D5 | F5 severity, re-decided on correct facts | (a) `error` (RED) — the label *is* the gate per `skills/release/SKILL.md:143`; (b) keep `warning` and correct the ADR's reasoning; (c) `error` for check 1, `warning` for check 2 | +| D6 | F7 CI availability | (a) add `fetch-tags: true` to `docs-quality.yml`; (b) change the authority to `.STATUS`'s `release_date:` with the same one-day window; (c) accept vacuity in CI and document it | +| D7 | F7 release-time ordering | (a) move the check after tag creation; (b) compare against the *version being released* rather than the tag; (c) accept and document | +| D8 | F4 exclusion round-trip | (a) split `file` back into path + lineno in the finding record; (b) have `[e]` strip the `:lineno` suffix before writing | +| D9 | F8 box-close discipline | (a) close the box at the first line with no box character; (b) cap box mode at N lines; (c) require a matching `└` within the same fenced block | + +### Non-negotiable, not up for decision + +Anything shipped must not **report a result it did not establish** (ADR-007). D3 option (c) and +D5 are constrained by that: an offered fix must be applicable and correct, and a severity must be +chosen from verified consumer behavior rather than an assumption about exit codes. + +## Scope + +**In:** `scripts/docs-staleness-check.sh` Phase 7 checks and their fix payloads; the pass-2 +`[e]` branch; `docs-quality.yml` checkout config if D6(a); ADR-007's severity section; the REFCARD +shape table; fixtures + harness rows for every fix. + +**In (added by the grill):** Phase 9's `[e]` findings, which carry the same `path:lineno` shape — +the ledger's D4 fixes this at the record level rather than per-phase, so both are covered by one +change. The git-tag authority and `docs-quality.yml`'s checkout config drop out of scope entirely: +D1 replaces the authority with cross-file consistency, so there is no tag to fetch. + +**Out:** the version-highlight proxy (dropped in the parent SPEC, still dropped); porting to +sibling repos; any new script or dependency (D1/D4 of the parent SPEC still hold); Phase 9's +*severity*, which D2 leaves as-is. + +## Acceptance criteria + +- [ ] Every finding F1–F9 has a fixture or test that **fails before the fix and passes after**, + verified by planted mutation — not merely a passing test after the change. +- [ ] A hyphenated-compound corpus (`command-line`, `agent-facing`, `skill-authoring`, + `agents-only`) produces zero findings, and the corresponding real counts still do. +- [ ] `[e]xclude` round-trips: writing the entry actually suppresses the finding on the next run, + asserted end-to-end rather than by inspecting the written line. +- [ ] The severity decision (D5) is recorded in ADR-007 with the **verified** consumer behavior, + and the incorrect claim is removed, not softened. +- [ ] Check 1 demonstrably fires in the environment D6/D7 select — proven by running it there, + not by reasoning that it should. +- [ ] `docs-staleness-check.sh` stays under the `test_pre_release_check_runs` 30s budget. +- [ ] Live repo stays GREEN. + +## Test plan + +| Tier | Coverage | +|------|----------| +| unit | `apply_line_fix` substitution safety for hyphenated spans (D3); floor arithmetic at every count magnitude incl. 1 and 2 (D2) | +| fixture | New `falsepos/` entries for hyphenated compounds and unclosed boxes; new `defect/` entries for each still-must-catch case | +| e2e | `[e]xclude` round-trip through a real `exclusions.txt`; check 1 firing in the D6-selected environment | +| dogfood | live repo GREEN; runtime under budget | +| negative | planted mutation per fix — the point is that each new test can fail | + +## Risks + +- **D4 and D6/D7 may interact.** If the authority moves to `.STATUS` (D6b), the release-ordering + problem (D7) changes shape, because `.STATUS` is written in the same commit as the doc dates. +- **Tightening F1 could re-mask the E1 bug** the parent SPEC exists to catch + (`8 agent definitions` — noun followed by a space, so it should survive; must be pinned). +- **D5 → `error` makes this a release blocker.** Every remaining false positive becomes a blocked + release rather than a warning, which raises the bar on D1/D2/D3 being right. + +## Next step + +Grill this spec before implementing. The decisions most worth attacking: **D5** (severity, given +the corrected facts), **D6/D7** (whether check 1 can fire anywhere that matters), and **D4** +(whether proximity is salvageable at all). + +> Interrogated by grill — see [GRILL-prose-check-hardening-2026-08-15.md](GRILL-prose-check-hardening-2026-08-15.md) diff --git a/mkdocs.yml b/mkdocs.yml index 0255c9223..420460517 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -177,6 +177,7 @@ nav: - "ADR-004: Release Surface Registry": adr/ADR-004-release-surface-registry.md - "ADR-005: Shim Canonical-Path Resolution": adr/ADR-005-shim-canonical-path-resolution.md - "ADR-006: Done Renamed to Finish": adr/ADR-006-done-renamed-to-finish.md + - "ADR-007: Pattern-Scoped Prose Staleness Gating": adr/ADR-007-pattern-scoped-prose-staleness-gating.md - Brainstorm Power User Guide: tutorials/TUTORIAL-brainstorm-power-user.md - CRAFT-001 Emoji Spacing Tutorial: tutorials/TUTORIAL-craft-001-emoji-spacing.md - Your First 10 Minutes: tutorials/TUTORIAL-first-10-minutes.md diff --git a/scripts/docs-staleness-check.sh b/scripts/docs-staleness-check.sh index a6ac9b152..dac771fc4 100755 --- a/scripts/docs-staleness-check.sh +++ b/scripts/docs-staleness-check.sh @@ -147,13 +147,207 @@ EXPECTED_SKILLS="" EXPECTED_AGENTS="" CURRENT_VERSION="" +# The CRAFT_EXPECTED_* overrides exist so the prose-staleness fixtures can declare +# their expected counts directly instead of materializing 48 command files and 41 +# skill dirs per fixture case. Unset in every production path, so the derived +# values are what actually runs. load_counts() { - EXPECTED_CMDS=$(find commands -name "*.md" ! -name "index.md" ! -name "README.md" 2>/dev/null | wc -l | tr -d ' ') - EXPECTED_SKILLS=$(find skills -name "SKILL.md" 2>/dev/null | wc -l | tr -d ' ') - EXPECTED_AGENTS=$(find agents -name "*.md" 2>/dev/null | wc -l | tr -d ' ') + EXPECTED_CMDS="${CRAFT_EXPECTED_CMDS:-$(find commands -name "*.md" ! -name "index.md" ! -name "README.md" 2>/dev/null | wc -l | tr -d ' ')}" + EXPECTED_SKILLS="${CRAFT_EXPECTED_SKILLS:-$(find skills -name "SKILL.md" 2>/dev/null | wc -l | tr -d ' ')}" + EXPECTED_AGENTS="${CRAFT_EXPECTED_AGENTS:-$(find agents -name "*.md" 2>/dev/null | wc -l | tr -d ' ')}" CURRENT_VERSION=$(python3 -c "import json; print(json.load(open('.claude-plugin/plugin.json'))['version'])") } +# --------------------------------------------------------------------------- +# Prose staleness helpers (SPEC-doc-staleness-prose-gaps-2026-08-07) +# --------------------------------------------------------------------------- +# emit_shaped_lines prints "file:lineno:shape:content" for lines sitting in one +# of four structured shapes. Free prose is deliberately NOT emitted: an +# adversarial review of a blanket "N agents" search found 90+ false positives +# across docs/ — orchestration mode-limit prose ("2 agents max"), a tutorial +# using an intentionally fictional plugin, and a troubleshooting page that +# prints a wrong count on purpose to teach the bug. Scoping to line shapes is +# what makes checking the singular noun form safe here. +# +# Takes the whole file list at once and makes ONE awk pass over it. The first +# build called awk per file: ~640 markdown files x 2 scans is ~1300 process +# spawns, which took this script from 8s to 36s and blew the 30s timeout in +# test_pre_release_check_runs. FILENAME/FNR give the same per-file output from a +# single invocation. +emit_shaped_lines() { + [[ $# -eq 0 ]] && return 0 + awk ' + # Only lines that actually carry a count are worth emitting. Without this + # filter every line of every box block is emitted (~3500 across docs/), + # and the bash loop downstream spends ~6 grep subprocesses on each — the + # other half of the 4x slowdown, alongside per-file awk spawns. + function hascount(s) { + return s ~ /[0-9]+ (specialized )?(commands?|skills?|agents?)([^a-z]|$)/ + } + + FNR == 1 { inbox = 0 } + + # "version-box": a quick-reference/version box drawn with box characters. + /┌/ { inbox = 1 } + inbox { + if (hascount($0)) print FILENAME ":" FNR ":version-box:" $0 + if ($0 ~ /└/) inbox = 0 + next + } + + # "tldr": a line that OPENS with TL;DR (after optional blockquote or + # emphasis markers), not merely one that mentions it. A doc describing a + # TL;DR bug is not itself making a TL;DR claim: the context table in + # ADR-007 quotes the "8 specialized agents" line the ADR exists to + # explain, and a contains-match flagged it the moment it was written. + # (No apostrophes in this awk program -- it is single-quoted in bash.) + /^[[:space:]]*[>*_[:space:]]*TL;DR/ { + if (hascount($0)) print FILENAME ":" FNR ":tldr:" $0 + next + } + + # "count-summary": the bolded badge line, e.g. **48 commands** | **41 skills**. + /\*\*[0-9]+ (commands|skills|agents)\*\*/ { print FILENAME ":" FNR ":count-summary:" $0; next } + + # "structure-table": a table row whose first cell names a counted + # directory. Added 2026-08-15 after CLAUDE.md claimed "8 agent + # definitions" (singular) for five minors while the plural-only scan + # below read GREEN over it. + /^\|[[:space:]]*`(commands|skills|agents)\/`[[:space:]]*\|/ { + if (hascount($0)) print FILENAME ":" FNR ":structure-table:" $0 + next + } + ' "$@" 2>/dev/null || true +} + +# emit_release_date_claims prints "file:lineno:YYYY-MM-DD" for release-date +# claims within 4 lines of a mention of the current version token — e.g. +# NEWS.md's "## v4.5.0 ..." heading followed by "**Released:** 2026-08-08". +# Windowed so historical entries further down the same file are not compared +# against the current version's date. +# Single awk pass over the whole file list, for the same reason as above. +emit_release_date_claims() { + [[ $# -eq 0 ]] && return 0 + awk -v ver="v${CURRENT_VERSION}" ' + FNR == 1 { win = 0 } + # 5, not 4: the win-- below runs on the version line itself, so win=4 + # scanned that line plus only 3 more -- one short of the 4 following + # lines this check documents, silently missing a layout as ordinary as + # heading / blank / Type / blank / Released. + index($0, ver) { win = 5 } + win > 0 { + if (match($0, /[Rr]eleased[^0-9]{0,12}[0-9]{4}-[0-9]{2}-[0-9]{2}/)) { + s = substr($0, RSTART, RLENGTH) + if (match(s, /[0-9]{4}-[0-9]{2}-[0-9]{2}/)) + print FILENAME ":" FNR ":" substr(s, RSTART, RLENGTH) + } + win-- + } + ' "$@" 2>/dev/null || true +} + +# Authority for the release-date check is the current version's git tag date, +# NOT .STATUS's release_date:. The two legitimately disagree by a day whenever a +# release publishes across the UTC boundary (v4.5.0: tag 2026-08-07, GitHub +# release 2026-08-08T03:44Z), so a claim within one day of the tag is not +# staleness. CRAFT_RELEASE_DATE overrides for hermetic fixtures. +resolve_release_date() { + if [[ -n "${CRAFT_RELEASE_DATE:-}" ]]; then + echo "$CRAFT_RELEASE_DATE" + return + fi + git for-each-ref --format='%(creatordate:short)' "refs/tags/v${CURRENT_VERSION}" 2>/dev/null | head -1 +} + +# Applies one `s/PATTERN/REPLACEMENT/flags` substitution to one line of one file. +# Echoes "true" only when the file actually changed, "false" otherwise — +# including when fix_detail is not a substitution at all (Phase 8's doc-coverage +# findings carry a `doc-coverage:surface:cmd` marker instead). +# +# Uses python3 rather than `sed -i`: the patterns built in count_consistency use +# `\b` word boundaries, which GNU sed honors but BSD sed (macOS default) treats +# as a literal `b`. sed also exits 0 when nothing matched, so the script once +# reported "Fixed: N items" while no file was modified — a silent no-op on every +# macOS run. Shared by pass 1 and pass 2 so the two cannot drift apart again. +apply_line_fix() { + python3 - "$1" "$2" "$3" <<'PYEOF' 2>/dev/null || echo "false" +import re, sys + +file_path, lineno_str, sed_cmd = sys.argv[1], sys.argv[2], sys.argv[3] +# sed_cmd looks like: s/PATTERN/REPLACEMENT/g +# The delimiter is `/` — we never construct anything else. +if not sed_cmd.startswith("s/"): + print("false") + sys.exit(0) +parts = sed_cmd[2:].rsplit("/", 2) +if len(parts) != 3: + print("false") + sys.exit(0) +pattern, replacement, flags = parts +try: + with open(file_path, encoding="utf-8") as f: + lines = f.readlines() +except OSError: + print("false") + sys.exit(0) + +idx = int(lineno_str) - 1 +if idx < 0 or idx >= len(lines): + print("false") + sys.exit(0) +old = lines[idx] +try: + new = re.sub(pattern, replacement, old) +except re.error: + print("false") + sys.exit(0) +if new == old: + print("false") + sys.exit(0) +lines[idx] = new +with open(file_path, "w", encoding="utf-8") as f: + f.writelines(lines) +print("true") +PYEOF +} + +# The acceptable window is three literal dates (tag-1, tag, tag+1), computed +# ONCE with a single python3 call rather than a python3 spawn per claim. Callers +# then do a plain string comparison. python3 is already a hard dependency here +# (load_counts reads plugin.json with it) — no new dependency. +ACCEPTED_RELEASE_DATES="" +compute_release_date_window() { + local tag_date="$1" + ACCEPTED_RELEASE_DATES=$(python3 -c " +import sys, datetime +try: + d = datetime.date.fromisoformat(sys.argv[1]) +except ValueError: + sys.exit(0) +print(' '.join(str(d + datetime.timedelta(days=n)) for n in (-1, 0, 1))) +" "$tag_date" 2>/dev/null) +} +release_date_accepted() { + case " ${ACCEPTED_RELEASE_DATES} " in + *" $1 "*) return 0 ;; + esac + return 1 +} + +# Dedup ledger shared by the broad count scan and the line-shape scan, so a +# stale count sitting inside a structured shape is reported once, not twice. +# A plain string, not an associative array — this script supports bash 3.2. +REPORTED_COUNT_KEYS="" +count_key_reported() { + case "$REPORTED_COUNT_KEYS" in + *"|$1|"*) return 0 ;; + esac + return 1 +} +mark_count_key() { + REPORTED_COUNT_KEYS="${REPORTED_COUNT_KEYS}|$1|" +} + # --------------------------------------------------------------------------- # Result tracking # --------------------------------------------------------------------------- @@ -334,6 +528,9 @@ phase7_count_consistency() { continue fi + # Already reported by the line-shape scan below? Report once. + count_key_reported "${file}:${lineno}:${ctype}" && continue + # Determine if auto-fixable (simple count swap) local fixable="true" local fix_detail="${file}:${lineno}:s/\b${found_count} ${ctype}\b/${expected} ${ctype}/g" @@ -341,10 +538,119 @@ phase7_count_consistency() { add_finding 7 "warning" "${file}:${lineno}" \ "'${found_count} ${ctype}' (expected ${expected})" \ "$fixable" "$fix_detail" + mark_count_key "${file}:${lineno}:${ctype}" issues=$((issues + 1)) done < <(grep -rnE "\b[0-9]+ ${ctype}\b" docs/ CLAUDE.md README.md --include="*.md" 2>/dev/null || true) done + # ----------------------------------------------------------------------- + # Check 2 — count prose inside structured line shapes, singular included. + # The scan above is plural-only and unscoped; this one is shape-scoped and + # therefore safe to run against the singular form too. Ordering matters: + # the broad scan marks its keys first, so a line both scans can see is + # reported by whichever reaches it first and skipped by the other. + # ----------------------------------------------------------------------- + # Build the scannable file list once, then hand it to each scan in a single + # awk invocation (see emit_shaped_lines' header note on why per-file awk + # calls were a 4x slowdown). + local prose_file shaped sfile srest slineno shape scontent singular found j + local -a prose_files=() + while IFS= read -r prose_file; do + [[ -z "$prose_file" ]] && continue + [[ -f "$prose_file" ]] || continue + is_file_excluded "$prose_file" && continue + prose_files+=("$prose_file") + done < <(printf '%s\n' CLAUDE.md README.md; find docs -name '*.md' 2>/dev/null || true) + + while IFS= read -r shaped; do + [[ -z "$shaped" ]] && continue + sfile="${shaped%%:*}"; srest="${shaped#*:}" + slineno="${srest%%:*}"; srest="${srest#*:}" + shape="${srest%%:*}"; scontent="${srest#*:}" + + for j in "${!count_types[@]}"; do + local ctype2="${count_types[$j]}" + local expected2="${expected_values[$j]}" + local min2="${min_thresholds[$j]}" + singular="${ctype2%s}" + + # A structure-table row names its own type in the first cell, so + # only compare against that type — a `commands/` row must not be + # measured against the agent count. + if [[ "$shape" == "structure-table" ]]; then + echo "$scontent" | grep -qE "^\|[[:space:]]*\`${ctype2}/\`" || continue + fi + + found=$(echo "$scontent" \ + | grep -oE "[0-9]+ (specialized )?(${ctype2}|${singular})([^a-z]|$)" \ + | grep -oE '^[0-9]+' | head -1) + [[ -z "$found" ]] && continue + [[ "$found" == "$expected2" ]] && continue + + # Same 40%-of-expected floor the broad scan applies. Structured + # shapes still carry non-total counts: category subtotals inside + # a reference box ("SMART (4 commands)"), a bolded subset count + # ("`--refine` is declared on **9 commands**"), and narrative + # counts about other plugins ("kept shipping **0 skills**"). + # Without this floor all three read as stale totals. + [[ "$found" -lt "$min2" ]] && continue + + count_key_reported "${sfile}:${slineno}:${ctype2}" && continue + is_pattern_excluded "$sfile" "${found} ${ctype2}" && continue + is_pattern_excluded "$sfile" "${found} ${singular}" && continue + + # Not auto-fixable: the surrounding prose ("8 agent definitions") + # is hand-authored, so a blind count swap can produce grammatical + # nonsense. Routed to the interactive pass instead. + # Report the noun as it actually appears on the line, not a + # normalized form — the reader has to find this string. + local noun + noun=$(echo "$scontent" \ + | grep -oE "${found} (specialized )?(${ctype2}|${singular})" | head -1) + noun="${noun:-${found} ${ctype2}}" + + # Routed to pass 2 (uncertain), not pass 1: the surrounding + # prose is hand-authored, so a human should see the line before + # the number changes under it. The fix_detail is still a real + # substitution so that confirming it actually edits the file — + # it swaps only the digits, leaving "agent definitions" intact. + add_finding 7 "warning" "${sfile}:${slineno}" \ + "prose[${shape}]: '${noun}' (expected ${expected2})" \ + "uncertain" "${sfile}:${slineno}:s/${noun}/${expected2}${noun#"$found"}/" + mark_count_key "${sfile}:${slineno}:${ctype2}" + issues=$((issues + 1)) + done + done < <(emit_shaped_lines "${prose_files[@]+"${prose_files[@]}"}") + + # ----------------------------------------------------------------------- + # Check 1 — release-date claims tied to the current version. + # Vacuous (skipped, not failed) when the current version has no tag yet, + # which is the normal state on a feature branch before release. + # ----------------------------------------------------------------------- + local tag_date claim cfile crest clineno cdate + tag_date="$(resolve_release_date)" + compute_release_date_window "$tag_date" + # Skip when the authority is missing OR unparseable. A broken authority must + # make this check vacuous, never universal: with an empty accept-window every + # release-date claim in the repo fails at once, turning one bad input into a + # repo-wide false-positive storm. Same posture as the no-tag case (normal on + # a feature branch before release). + if [[ -n "$ACCEPTED_RELEASE_DATES" ]]; then + while IFS= read -r claim; do + [[ -z "$claim" ]] && continue + cfile="${claim%%:*}"; crest="${claim#*:}" + clineno="${crest%%:*}"; cdate="${crest#*:}" + + release_date_accepted "$cdate" && continue + is_pattern_excluded "$cfile" "$cdate" && continue + + add_finding 7 "warning" "${cfile}:${clineno}" \ + "release date '${cdate}' for v${CURRENT_VERSION} (tag: ${tag_date})" \ + "uncertain" "${cfile}:${clineno}:s/${cdate}/${tag_date}/" + issues=$((issues + 1)) + done < <(emit_release_date_claims "${prose_files[@]+"${prose_files[@]}"}") + fi + print_phase_status "$issues" } @@ -601,54 +907,8 @@ pass1_auto_fix() { local sed_cmd="${rest#*:}" if [[ -f "$file" ]]; then - # Apply the fix via python3 instead of `sed -i`. - # Why: the sed patterns built in count_consistency use `\b` word - # boundaries, which GNU sed honors but BSD sed (macOS default) - # treats as literal `b`. The sed call previously returned exit 0 - # even when nothing matched, so the script reported "Fixed: N - # items" while no file was actually modified — silent no-op on - # every macOS run. Python's re.sub gets the contract right on - # every platform and lets us also detect "nothing actually - # changed" via a proper before/after comparison. local fixed_one=false - fixed_one="$(python3 - "$file" "$lineno" "$sed_cmd" <<'PYEOF' 2>/dev/null || echo "false" -import re, sys - -file_path, lineno_str, sed_cmd = sys.argv[1], sys.argv[2], sys.argv[3] -# sed_cmd looks like: s/PATTERN/REPLACEMENT/g -# Parse it. The delimiter is `/` (we never use anything else when constructing). -if not sed_cmd.startswith("s/"): - print("false") - sys.exit(0) -parts = sed_cmd[2:].rsplit("/", 2) -if len(parts) != 3: - print("false") - sys.exit(0) -pattern, replacement, flags = parts -# Translate `\b` (GNU sed word boundary) into Python regex's equivalent. -# Python's re already supports \b, so the pattern is mostly portable as-is. -try: - with open(file_path, encoding="utf-8") as f: - lines = f.readlines() -except OSError: - print("false") - sys.exit(0) - -idx = int(lineno_str) - 1 -if idx < 0 or idx >= len(lines): - print("false") - sys.exit(0) -old = lines[idx] -new = re.sub(pattern, replacement, old) -if new == old: - print("false") - sys.exit(0) -lines[idx] = new -with open(file_path, "w", encoding="utf-8") as f: - f.writelines(lines) -print("true") -PYEOF -)" + fixed_one="$(apply_line_fix "$file" "$lineno" "$sed_cmd")" if [[ "$fixed_one" == "true" ]]; then TOTAL_FIXED=$((TOTAL_FIXED + 1)) if [[ "$JSON_MODE" != "true" ]]; then @@ -709,8 +969,20 @@ pass2_interactive_review() { case "$response" in f) - if [[ -n "$fix_detail" ]]; then - echo -e " -> ${GREEN}Fixed${NC}" + # Actually apply it. This branch used to print "Fixed" and bump + # TOTAL_FIXED without touching the file — the same + # reports-success-changes-nothing bug pass 1 was fixed for + # earlier, left behind in pass 2. Both now share apply_line_fix. + local target_file="${fix_detail%%:*}" + local fix_rest="${fix_detail#*:}" + local target_line="${fix_rest%%:*}" + local fix_cmd="${fix_rest#*:}" + local applied="false" + if [[ -n "$fix_detail" && -f "$target_file" ]]; then + applied="$(apply_line_fix "$target_file" "$target_line" "$fix_cmd")" + fi + if [[ "$applied" == "true" ]]; then + echo -e " -> ${GREEN}Fixed${NC} ${target_file}:${target_line}" TOTAL_FIXED=$((TOTAL_FIXED + 1)) else echo " -> Cannot auto-fix (manual edit needed)" diff --git a/tests/fixtures/prose-staleness/clean/release-date-utc-boundary.md b/tests/fixtures/prose-staleness/clean/release-date-utc-boundary.md new file mode 100644 index 000000000..aa6deb7ea --- /dev/null +++ b/tests/fixtures/prose-staleness/clean/release-date-utc-boundary.md @@ -0,0 +1,10 @@ + + + +# News + +## v4.5.0 — repo-triage skill + ecosystem-tool tutorials + +**Released:** 2026-08-08 · **Type:** Minor + +Without the one-day window this check would flag a correct repo on its first run. diff --git a/tests/fixtures/prose-staleness/clean/structure-table-correct.md b/tests/fixtures/prose-staleness/clean/structure-table-correct.md new file mode 100644 index 000000000..4aacb5b09 --- /dev/null +++ b/tests/fixtures/prose-staleness/clean/structure-table-correct.md @@ -0,0 +1,10 @@ + + +# Project Structure + +| Directory | Purpose | +|-----------|---------| +| `commands/` | 48 commands (auto-discovered, one `.md` per command) | +| `skills/` | 41 skills (`SKILL.md` pattern) | +| `agents/` | 2 agent definitions | +| `tests/` | Unit, e2e, dogfood, and integration test suites | diff --git a/tests/fixtures/prose-staleness/clean/tldr-correct.md b/tests/fixtures/prose-staleness/clean/tldr-correct.md new file mode 100644 index 000000000..61546e794 --- /dev/null +++ b/tests/fixtures/prose-staleness/clean/tldr-correct.md @@ -0,0 +1,7 @@ + + +# Skills and Agents + +**TL;DR** — craft ships 41 skills and 2 specialized agents. + +The rest of this page enumerates them. diff --git a/tests/fixtures/prose-staleness/clean/version-box-correct.md b/tests/fixtures/prose-staleness/clean/version-box-correct.md new file mode 100644 index 000000000..63d31316f --- /dev/null +++ b/tests/fixtures/prose-staleness/clean/version-box-correct.md @@ -0,0 +1,13 @@ + + +# Quick Reference + +```text +┌─────────────────────────────────────────────┐ +│ craft v4.5.0 │ +│ Released: 2026-08-07 │ +│ 48 commands · 41 skills · 2 agents │ +└─────────────────────────────────────────────┘ +``` + +Nothing on this page contradicts the plugin manifest. diff --git a/tests/fixtures/prose-staleness/defect/release-date-far-edge.md b/tests/fixtures/prose-staleness/defect/release-date-far-edge.md new file mode 100644 index 000000000..648dfadba --- /dev/null +++ b/tests/fixtures/prose-staleness/defect/release-date-far-edge.md @@ -0,0 +1,12 @@ + + + + + +# News + +## v4.5.0 — repo-triage skill + ecosystem-tool tutorials + +**Type:** Minor + +**Released:** 2026-07-19 diff --git a/tests/fixtures/prose-staleness/defect/structure-table-singular.md b/tests/fixtures/prose-staleness/defect/structure-table-singular.md new file mode 100644 index 000000000..c38bce96d --- /dev/null +++ b/tests/fixtures/prose-staleness/defect/structure-table-singular.md @@ -0,0 +1,9 @@ + + + +# Project Structure + +| Directory | Purpose | +|-----------|---------| +| `commands/` | 48 commands (auto-discovered, one `.md` per command) | +| `agents/` | 8 agent definitions | diff --git a/tests/fixtures/prose-staleness/defect/tldr-eight-agents.md b/tests/fixtures/prose-staleness/defect/tldr-eight-agents.md new file mode 100644 index 000000000..c7449647e --- /dev/null +++ b/tests/fixtures/prose-staleness/defect/tldr-eight-agents.md @@ -0,0 +1,7 @@ + + +# Skills and Agents + +**TL;DR** — craft ships 41 skills and 8 specialized agents. + +Two lines below, the page's own table correctly lists 2. diff --git a/tests/fixtures/prose-staleness/defect/version-box-stale-date.md b/tests/fixtures/prose-staleness/defect/version-box-stale-date.md new file mode 100644 index 000000000..ca8c92bab --- /dev/null +++ b/tests/fixtures/prose-staleness/defect/version-box-stale-date.md @@ -0,0 +1,12 @@ + + + +# Quick Reference + +```text +┌─────────────────────────────────────────────┐ +│ craft v4.5.0 │ +│ Released: 2026-07-19 │ +│ 48 commands · 41 skills · 2 agents │ +└─────────────────────────────────────────────┘ +``` diff --git a/tests/fixtures/prose-staleness/falsepos/category-subtotal-box.md b/tests/fixtures/prose-staleness/falsepos/category-subtotal-box.md new file mode 100644 index 000000000..2f24dacce --- /dev/null +++ b/tests/fixtures/prose-staleness/falsepos/category-subtotal-box.md @@ -0,0 +1,13 @@ + + + + +# Playground + +```text +┌─────────────────────────────────────────────────────────────┐ +│ 🎯 SMART (4 commands) │ +│ 📚 DOCUMENTATION (13 commands) │ +│ 🔧 Code (12 commands) ............................ /craft:code:* +└─────────────────────────────────────────────────────────────┘ +``` diff --git a/tests/fixtures/prose-staleness/falsepos/mode-limit-prose.md b/tests/fixtures/prose-staleness/falsepos/mode-limit-prose.md new file mode 100644 index 000000000..81a41af2f --- /dev/null +++ b/tests/fixtures/prose-staleness/falsepos/mode-limit-prose.md @@ -0,0 +1,10 @@ + + + + + + +# Orchestration flag usage + +Default mode dispatches 2 agents max. Raising the cap to 4 agents is supported but rarely +useful; beyond that the coordination overhead dominates. diff --git a/tests/fixtures/prose-staleness/falsepos/subset-bold-count.md b/tests/fixtures/prose-staleness/falsepos/subset-bold-count.md new file mode 100644 index 000000000..83e0a8b5b --- /dev/null +++ b/tests/fixtures/prose-staleness/falsepos/subset-bold-count.md @@ -0,0 +1,10 @@ + + + + + +# The --refine flag + +`--refine` is declared on **9 commands** in two modes: default-on and opt-in. + +A stale install of another plugin once shipped **0 skills** because its tarball predated them. diff --git a/tests/fixtures/prose-staleness/falsepos/tldr-mentioned-not-claimed.md b/tests/fixtures/prose-staleness/falsepos/tldr-mentioned-not-claimed.md new file mode 100644 index 000000000..33ac2930b --- /dev/null +++ b/tests/fixtures/prose-staleness/falsepos/tldr-mentioned-not-claimed.md @@ -0,0 +1,13 @@ + + + + +# ADR-00N: some decision + +## Context + +| Bug | Why it was missed | +|---|---| +| `docs/skills-agents.md` — TL;DR claiming "8 specialized agents" above its own correct "2" | the intervening word broke the pattern | + +Prose about the bug, quoting 8 specialized agents again for good measure. diff --git a/tests/test_docs_staleness_prose.py b/tests/test_docs_staleness_prose.py new file mode 100644 index 000000000..0da6b0f35 --- /dev/null +++ b/tests/test_docs_staleness_prose.py @@ -0,0 +1,264 @@ +"""Table-driven harness for the prose-staleness checks in docs-staleness-check.sh. + +Implements SPEC-doc-staleness-prose-gaps-2026-08-07's "Test Harness" section. + +Each row runs the real script against a throwaway repo containing exactly one +fixture document, and asserts on the structured ``count_consistency`` findings +from ``--json`` rather than parsing colored terminal output. + +Three fixture classes, and the harness is only meaningful with all three: + +* ``clean/`` — correct content must stay GREEN (no false positive) +* ``defect/`` — planted defect must go RED (positive control; a check shipped + without one of these is a rejected change) +* ``falsepos/``— content that LOOKS like a stale count but is not, drawn from + real pages an earlier build of these checks wrongly flagged + +Hermetic by construction: no network, no git-history walk, no TTY. The one git +read check 1 needs (the version's tag date) is injected via ``CRAFT_RELEASE_DATE``, +and the expected counts via ``CRAFT_EXPECTED_*``, so the fixtures do not have to +materialize 48 command files apiece. +""" + +from __future__ import annotations + +import json +import shutil +import subprocess +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parent.parent +SCRIPT = REPO_ROOT / "scripts" / "docs-staleness-check.sh" +FIXTURES = Path(__file__).resolve().parent / "fixtures" / "prose-staleness" + +# The authority values every fixture is written against. Kept here, not derived +# from the live repo, so a future count change cannot silently invalidate a +# planted defect (e.g. an "8 agents" defect stops being a defect the day craft +# actually ships 8 agents). +FIXTURE_VERSION = "4.5.0" +FIXTURE_TAG_DATE = "2026-08-07" +FIXTURE_COUNTS = {"CMDS": "48", "SKILLS": "41", "AGENTS": "2"} + +# (fixture, destination path inside the throwaway repo, expect_finding, proves) +# +# Destination matters: `mode-limit-prose` must land on its real path because the +# exclusion that saves it in production is path-keyed. +CASES = [ + pytest.param( + "clean/version-box-correct.md", "docs/refcard.md", False, + "correct date + counts in a version box", + id="clean-version-box", + ), + pytest.param( + "clean/tldr-correct.md", "docs/skills-agents.md", False, + "correct counts in a TL;DR line", + id="clean-tldr", + ), + pytest.param( + "clean/structure-table-correct.md", "docs/structure.md", False, + "E1 stays fixed: correct singular count in a structure-table row", + id="clean-structure-table", + ), + pytest.param( + "clean/release-date-utc-boundary.md", "docs/news.md", False, + "E2: a one-day gap is the UTC boundary, not staleness", + id="clean-utc-boundary", + ), + pytest.param( + "defect/version-box-stale-date.md", "docs/refcard.md", True, + "check 1 catches a release date well off the tag", + id="defect-stale-date", + ), + pytest.param( + "defect/release-date-far-edge.md", "docs/news.md", True, + "check 1 reaches the last line its window documents (off-by-one guard)", + id="defect-release-date-far-edge", + ), + pytest.param( + "defect/tldr-eight-agents.md", "docs/skills-agents.md", True, + "check 2 catches the original review bug", + id="defect-tldr-eight-agents", + ), + pytest.param( + "defect/structure-table-singular.md", "docs/structure.md", True, + "E1 would now be caught: singular noun form in a structure-table row", + id="defect-structure-table-singular", + ), + pytest.param( + "falsepos/category-subtotal-box.md", "docs/playground.md", False, + "category subtotals inside a box are not the grand total", + id="falsepos-category-subtotal", + ), + pytest.param( + "falsepos/subset-bold-count.md", "docs/help/refine-flag.md", False, + "a bolded count can describe a subset or another plugin", + id="falsepos-subset-bold-count", + ), + pytest.param( + "falsepos/mode-limit-prose.md", "docs/guide/orch-flag-usage.md", False, + "mode-limit prose is free prose and stays exclusion-covered", + id="falsepos-mode-limit-prose", + ), + pytest.param( + "falsepos/tldr-mentioned-not-claimed.md", "docs/adr/ADR-00N-example.md", False, + "a doc describing a TL;DR bug is not itself making a TL;DR claim", + id="falsepos-tldr-mentioned", + ), +] + + +def build_repo(tmp_path: Path, fixture: str, dest: str) -> Path: + """Materialize a throwaway repo holding exactly one fixture document.""" + repo = tmp_path / "repo" + (repo / ".claude-plugin").mkdir(parents=True) + (repo / ".claude-plugin" / "plugin.json").write_text( + json.dumps({"name": "craft", "version": FIXTURE_VERSION}) + "\n" + ) + target = repo / dest + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(FIXTURES / fixture, target) + return repo + + +def run_check(repo: Path) -> dict: + """Run the real script against `repo` and return its parsed JSON report.""" + env = { + "PATH": "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin", + "HOME": str(repo), + "CRAFT_PLUGIN_DIR": str(repo), + "CRAFT_RELEASE_DATE": FIXTURE_TAG_DATE, + "CRAFT_EXPECTED_CMDS": FIXTURE_COUNTS["CMDS"], + "CRAFT_EXPECTED_SKILLS": FIXTURE_COUNTS["SKILLS"], + "CRAFT_EXPECTED_AGENTS": FIXTURE_COUNTS["AGENTS"], + } + proc = subprocess.run( + ["bash", str(SCRIPT), "--json"], + env=env, capture_output=True, text=True, timeout=120, + ) + assert proc.stdout.strip(), f"no JSON emitted; stderr:\n{proc.stderr}" + return json.loads(proc.stdout) + + +@pytest.mark.parametrize("fixture,dest,expect_finding,proves", CASES) +def test_prose_staleness_fixture(tmp_path, fixture, dest, expect_finding, proves): + report = run_check(build_repo(tmp_path, fixture, dest)) + findings = report["phases"]["count_consistency"]["findings"] + + rendered = "\n".join(f" {f['file']}: {f['message']}" for f in findings) or " (none)" + + if expect_finding: + assert findings, ( + f"planted defect went undetected — {proves}\n" + f"fixture: {fixture} -> {dest}\nfindings:\n{rendered}" + ) + else: + assert not findings, ( + f"false positive on content that is correct — {proves}\n" + f"fixture: {fixture} -> {dest}\nfindings:\n{rendered}" + ) + + +def test_unparseable_authority_date_is_vacuous_not_universal(tmp_path): + """A broken release-date authority must skip the check, not flag everything. + + Found in review of PR #334. `compute_release_date_window` exits silently on + an unparseable date, leaving an empty accept-window — and an empty window + matches nothing, so every release-date claim in the repo failed at once. One + bad input became a repo-wide false-positive storm. The guard is that the + check is vacuous unless the authority actually parsed. + + Uses the `clean/` fixture on purpose: it is correct against a real tag date, + so any finding here is caused by the broken authority alone. + """ + repo = build_repo(tmp_path, "clean/release-date-utc-boundary.md", "docs/news.md") + env = { + "PATH": "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin", + "HOME": str(repo), + "CRAFT_PLUGIN_DIR": str(repo), + "CRAFT_RELEASE_DATE": "not-a-date", + "CRAFT_EXPECTED_CMDS": FIXTURE_COUNTS["CMDS"], + "CRAFT_EXPECTED_SKILLS": FIXTURE_COUNTS["SKILLS"], + "CRAFT_EXPECTED_AGENTS": FIXTURE_COUNTS["AGENTS"], + } + proc = subprocess.run( + ["bash", str(SCRIPT), "--json"], + env=env, capture_output=True, text=True, timeout=120, + ) + findings = json.loads(proc.stdout)["phases"]["count_consistency"]["findings"] + rendered = "\n".join(f" {f['file']}: {f['message']}" for f in findings) + assert not findings, ( + f"unparseable authority date produced findings instead of skipping:\n{rendered}" + ) + + +def _call_apply_line_fix(path: Path, lineno: int, fix_detail: str) -> str: + """Source apply_line_fix out of the real script and call it directly.""" + script = ( + f"eval \"$(sed -n '/^apply_line_fix()/,/^}}/p' {SCRIPT})\"\n" + f"apply_line_fix {path} {lineno} {json.dumps(fix_detail)}\n" + ) + proc = subprocess.run(["bash", "-c", script], capture_output=True, text=True, timeout=60) + return proc.stdout.strip() + + +def test_apply_line_fix_edits_the_file_and_reports_truthfully(tmp_path): + """The shared applier must change the file when it says it did. + + pass 2's [f]ix branch printed "Fixed" and incremented TOTAL_FIXED without + touching anything — the same reports-success-changes-nothing bug pass 1 had + already been fixed for. Both now route through apply_line_fix, so this is + the one place that contract is pinned. + """ + doc = tmp_path / "structure.md" + doc.write_text("intro\n| `agents/` | 8 agent definitions |\noutro\n") + + assert _call_apply_line_fix(doc, 2, "s/8 agent/2 agent/") == "true" + assert doc.read_text().splitlines()[1] == "| `agents/` | 2 agent definitions |" + + +def test_apply_line_fix_returns_false_without_changing_anything(tmp_path): + """False must mean untouched, for every way a fix can fail to apply.""" + doc = tmp_path / "structure.md" + original = "intro\n| `agents/` | 2 agent definitions |\noutro\n" + doc.write_text(original) + + # Phase 8 emits this marker rather than a substitution — must not be run. + assert _call_apply_line_fix(doc, 2, "doc-coverage:refcard:craft:do") == "false" + # Pattern that matches nothing on that line. + assert _call_apply_line_fix(doc, 2, "s/9 agent/2 agent/") == "false" + # Line number past the end of the file. + assert _call_apply_line_fix(doc, 99, "s/2 agent/3 agent/") == "false" + + assert doc.read_text() == original, "a false result still modified the file" + + +def test_every_check_has_a_planted_defect(): + """A check without a defect fixture is a rejected change (SPEC harness rule). + + Guards the harness itself: it is the one assertion that fails if someone + adds a check and only ever tests the happy path. + """ + defects = {p.name for p in (FIXTURES / "defect").glob("*.md")} + assert "version-box-stale-date.md" in defects, "check 1 lost its positive control" + assert {"tldr-eight-agents.md", "structure-table-singular.md"} <= defects, ( + "check 2 lost a positive control" + ) + + +def test_live_repo_stays_green_on_count_consistency(): + """The checks must not fire on craft's own already-corrected docs. + + SPEC acceptance criterion: running against current HEAD stays GREEN. This is + the assertion that would have caught the first build of check 2, which + flagged five legitimate subset counts before the 40%-of-expected floor. + """ + proc = subprocess.run( + ["bash", str(SCRIPT), "--json"], + cwd=REPO_ROOT, capture_output=True, text=True, timeout=180, + ) + report = json.loads(proc.stdout) + findings = report["phases"]["count_consistency"]["findings"] + rendered = "\n".join(f" {f['file']}: {f['message']}" for f in findings) + assert not findings, f"count_consistency regressed on the live repo:\n{rendered}" From 20976516606181e986402f566ff98cedb67a5cc5 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Sat, 15 Aug 2026 16:27:15 -0700 Subject: [PATCH 19/24] =?UTF-8?q?docs:=20post-merge=20cleanup=20for=20#334?= =?UTF-8?q?=20=E2=80=94=20drop=20ORCHESTRATE,=20refresh=20worktree=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ORCHESTRATE-*.md files are feature-branch working artifacts, not dev content (craft CLAUDE.md, grill decision D10). Nothing enforces this — the file is not gitignored, not in exclusions.txt, and no test guards it. .STATUS row flipped to MERGED and rewritten to say what actually shipped, including what did not: a high-effort review found 9 further defects, 2 of them HIGH, and the hardening is spec'd and grilled but not implemented. Recorded plainly rather than left implicit, since check 2's --fix can corrupt prose until that work lands. Co-Authored-By: Claude Opus 5 (1M context) --- .STATUS | 2 +- ORCHESTRATE-prose-check-hardening.md | 181 --------------------------- 2 files changed, 1 insertion(+), 182 deletions(-) delete mode 100644 ORCHESTRATE-prose-check-hardening.md diff --git a/.STATUS b/.STATUS index 36579334c..82ea1ef76 100644 --- a/.STATUS +++ b/.STATUS @@ -709,7 +709,7 @@ next occurrence, not a date. | Branch | Path | Purpose | |--------|------|---------| -| `feature/doc-staleness-prose-gaps` | `~/.git-worktrees/craft/feature-doc-staleness-prose-gaps` | LIVE, created 2026-08-15. Implements `SPEC-doc-staleness-prose-gaps-2026-08-07.md` — 2 prose checks in `docs-staleness-check.sh` Phase 7 (release-date claims vs git tag ±1 day; count prose scoped to 4 structured line shapes, singular noun form included), ADR-007, and a 11-fixture table-driven harness (`tests/fixtures/prose-staleness/` + `tests/test_docs_staleness_prose.py`). | +| _(prior, MERGED)_ | — | `feature/doc-staleness-prose-gaps` → dev via [#334](https://github.com/Data-Wise/craft/pull/334) (squash `b5de1ae39`), 2026-08-15. Two prose checks in `docs-staleness-check.sh` Phase 7 (release-date claims; count prose scoped to 4 structured line shapes, singular noun form included), ADR-007, and a 12-fixture table-driven harness. Four bugs were caught pre-merge by four different mechanisms — a 4× perf regression by the suite's own 30s timeout, an unparseable-authority false-positive storm by self-review, a release-date off-by-one by `/code-review`, and a pass-2 `[f]ix` that reported success without touching the file by reading adjacent code. **KNOWN, NOT FIXED:** a high-effort review found 9 further defects (2 HIGH — hyphenated compounds like `command-line` read as counts, and accepting the offered fix rewrites that prose). Merged on explicit instruction, reversing grill decision D5. Hardening is fully spec'd and grilled — `SPEC-prose-check-hardening-2026-08-15.md` + `GRILL-prose-check-hardening-2026-08-15.md` (11 locked branches) — but NOT implemented. Check 2 is `warning`-only so nothing blocks, but `--fix` can corrupt a document until it lands. Worktree still live at `~/.git-worktrees/craft/feature-doc-staleness-prose-gaps`. | | _(prior, MERGED)_ | — | `feature/repo-triage` → shipped in v4.5.0 (PR [#328](https://github.com/Data-Wise/craft/pull/328) squash to dev, released via [#329](https://github.com/Data-Wise/craft/pull/329)). Worktree removed. Original dispatch note: DISPATCHED 2026-08-07. New `repo-triage` skill (batch issue/worktree/branch triage, thin orchestrator over `issue-check` + `dev/git` Op 4/5). 6-phase ORCHESTRATE, `orchestrate-dispatch` mode. Phase 0 (`dev/git` Op 5 `--dry-run` + output contract) blocks Phases 2-4. Planning: `docs/specs/BRAINSTORM-repo-triage-2026-08-07.md`, `docs/specs/GRILL-repo-triage-2026-08-07.md` (amended post-review), `docs/specs/REVIEW-repo-triage-2026-08-07.md`. | | _(prior, MERGED)_ | — | `feature/prerelease-version-check-hardening` → dev via [#311](https://github.com/Data-Wise/craft/pull/311) (squash), 2026-07-25. Audited the repo for the "grep first bare vX.Y.Z string" bug class that broke the Homebrew release gate twice (v4.2.0, v4.3.0, fixed for `docs/index.md` in #308) — found and fixed 4 more live sites: `.github/workflows/docs.yml`'s live-site poll, `verify-surfaces.sh`'s `resolve_docs_site` (explicitly reused docs.yml's mechanism), `version-sync-precommit.sh`'s `.STATUS` check (a blocking pre-commit hook), and the manual verification steps in `skills/release/SKILL.md`/`downstream-verification.md`; all anchored to the actual version signal (badge slug / `^version:` field) instead of the first bare match. Confirmed 3 other sites already safe, left unchanged. New regression tests for each fix, each verified against a planted-defect positive control. Closes the recurrence risk flagged after v4.3.0. Worktree removed, local + remote branch deleted. | | _(prior, MERGED)_ | — | `feature/issue-309-branch-guard-docs` → dev via [#310](https://github.com/Data-Wise/craft/pull/310) (squash), 2026-07-25. The documented "Claude writes `.claude/allow-once`" one-shot bypass flow is circular when the approved action *is* creating/editing that marker itself (every tool path intercepts it with its own `[CONFIRM]`); added the `CRAFT_GUARD_ALLOW_DEV_EDIT` env-var hint (#281) to the 3 marker-specific confirms only, and reconciled `REFCARD-BRANCH-GUARD.md`/`branch-guard-smart-mode.md` with a caveat pointing at `skills/dev/git/SKILL.md` Operation 10. Code review caught the negative regression test was checking the wrong verbosity encounter (2nd-call brief mode never renders suggestions regardless of content, so it passed vacuously); fixed to assert against the 1st-encounter full-verbosity capture, verified via planted-defect positive control. Closes #309. Worktree removed, local + remote branch deleted. | diff --git a/ORCHESTRATE-prose-check-hardening.md b/ORCHESTRATE-prose-check-hardening.md deleted file mode 100644 index 8fc7758eb..000000000 --- a/ORCHESTRATE-prose-check-hardening.md +++ /dev/null @@ -1,181 +0,0 @@ -# ORCHESTRATE: Prose-Check Hardening - -**Spec:** [`docs/specs/SPEC-prose-check-hardening-2026-08-15.md`](docs/specs/SPEC-prose-check-hardening-2026-08-15.md) -**Grill:** [`docs/specs/GRILL-prose-check-hardening-2026-08-15.md`](docs/specs/GRILL-prose-check-hardening-2026-08-15.md) — 11 branches locked (D1–D6 spec, D7–D11 plan) -**Worktree:** `~/.git-worktrees/craft/feature-doc-staleness-prose-gaps` (exists) -**Branch:** `feature/doc-staleness-prose-gaps` · **PR:** #334 (open, CI green, **do not merge until P6**) - -## Objective - -Close the 9 defects a high-effort review found in PR #334, per the grill's locked decisions, on the -same branch — so #334 never ships a check that emits false positives or corrupts prose. - -## Phase Overview - -**Reordered per D7:** the record change goes first. As originally written, Phases 1–3 authored -`add_finding` calls and tests against the old signature and Phase 4 then rewrote all 12 call sites — -every prose finding written twice, its tests touched twice, and the mechanical sweep landing where -a missed site is likeliest. - -| # | Phase | Findings | Locked by | Est | -|---|-------|----------|-----------|-----| -| 1 | Finding record: line number as its own field | F4 | D4, D7 | 45 min | -| 2 | Matcher, fix payload, floor | F1, F2, F6 | D3 | 25 min | -| 3 | Check 1 → cross-file consistency | F3, F7 | D1, D6, D8 | 40 min | -| 4 | Severity split + ADR correction | F5 | D2, D11 | 20 min | -| 5 | Low findings, date normalization, doc sync | F8, F9 | D9 | 25 min | -| 6 | Verify, promote, update PR #334 | — | D5, D10, D11 | 30 min | - -**Estimate honesty (D11):** ~3 hours, not the 2 first written. Phase 1 alone is 12 call sites plus -JSON rendering plus both fix passes plus an end-to-end exclusion test; 25 minutes was wishful. - -Sequential. Phase 1 must complete before any other phase touches `add_finding`. - -## Phase 1 — Finding record: line number as its own field (D4, D7) - -Goes first so every later phase is written once, against the final signature, and so the -largest-blast-radius change lands against an unmodified baseline where the full suite is a clean -control. - -- [ ] `add_finding` takes line number as its own argument instead of glued into `file` -- [ ] Update all 12 call sites: Phase 6 (×2), Phase 7 (×3), Phase 8 (×3), Phase 9 (×4) -- [ ] JSON output preserves today's `file` rendering (`path:lineno`) so consumers do not break — - the split is internal -- [ ] `[e]` writes `path:pattern`, matching what `is_pattern_excluded` parses -- [ ] **E2E, not inspection:** write the exclusion, re-run, assert the finding is gone -- [ ] Mutation control: revert the record change → the round-trip test fails -- [ ] **Full suite after this phase**, not just the prose tests — this is the widest change here - -## Phase 2 — Matcher, fix payload, floor (D3) - -- [ ] Trailer: `([^a-z]|$)` → whitespace / closing punctuation / end-of-line, in the check-2 - matcher **and** awk's `hascount()` pre-filter (they must agree, or the pre-filter drops lines - the matcher would have caught) -- [ ] Floor: `expected * 40 / 100` → `max(2, expected * 40 / 100)` -- [ ] Fix payload: build the substitution from the full matched span, not the bare `N noun` prefix -- [ ] Fixture `falsepos/hyphenated-compounds.md`: `command-line`, `agent-facing`, - `skill-authoring`, `agents-only` — all GREEN -- [ ] Fixture `defect/small-count-agents.md`: a wrong agent count the zero floor used to pass only - by accident, now caught deliberately -- [ ] **Pin E1 survives:** `clean/structure-table-correct.md` and - `defect/structure-table-singular.md` pass unchanged — `8 agent definitions` has a space after - the noun and `8 ≥ max(2, 0)` -- [ ] Mutation control per change: revert the trailer → hyphen fixture fails; revert the floor → - small-count fixture fails; revert the span anchor → a fix-application test corrupts prose - -## Phase 3 — Check 1 → cross-file consistency (D1, D6, D8) - -- [ ] Window opens only on a **markdown heading** or a version-box line containing the version - token — not on any line mentioning it (D6, closes F3) -- [ ] Replace `resolve_release_date` / `compute_release_date_window` / `release_date_accepted` - with claim collection + pairwise agreement (one-day tolerance retained) -- [ ] Delete `CRAFT_RELEASE_DATE`; the harness no longer injects an authority -- [ ] Report shape names **both** disagreeing sites — a finding naming one file cannot be acted on -- [ ] Vacuous when fewer than 2 claims exist (ADR-007: report only what you can establish) -- [ ] **Rewrite, never delete, the two existing check-1 fixtures (D8).** The window still exists - under D6, so the off-by-one class is still live and `defect/release-date-far-edge.md` is its - only positive control — the exact bug `/code-review` caught. Rewrite it as: heading at line N, - disagreeing claim at the window's last line → still RED. Rewrite - `defect/version-box-stale-date.md` as an in-box disagreement, since boxes are where the - original REFCARD bug lived. -- [ ] Fixture `falsepos/version-mentioned-in-prose.md`: "Upgrading to v4.5.0 is a drop-in change" - followed by an unrelated `Released:` date → GREEN -- [ ] Fixture `defect/release-date-disagreement.md` pair; `clean/release-date-utc-boundary.md` - rewritten to a two-claim form one day apart → GREEN - -**Real claim sites, confirmed 2026-08-15** — the check must reach both: - -| Site | Shape | Reached via | -|---|---|---| -| `docs/NEWS.md:9` | `**Released:** 2026-08-08` | `## v4.5.0 …` heading two lines above | -| `docs/REFCARD.md:7` | `│ Version: 4.5.0 (released 2026-08-07)` | version-box line, claim on the same line | - -## Phase 4 — Severity split + ADR correction (D2, D11) - -- [ ] Check 1 ships as **`warning`** in this phase. Promotion to `error` happens in Phase 6, gated - on evidence (D11) — the check is being redesigned and promoted in one PR, and the only - evidence it is sound would otherwise be tests written alongside it. -- [ ] `print_phase_status` / phase-label handling copes with mixed severities in one phase -- [ ] ADR-007 Severity section: **remove** the false claim (that both severities fail the gate - identically), replace with verified behavior — `pre-release-check.sh:280` swallows the exit - code, `docs-quality.yml` sets `continue-on-error`, `skills/release/SKILL.md:143` blocks on - the RED label. Record the D11 gate as the reason RED is earned, not assumed. -- [ ] Test: a check-1 finding makes overall status RED once promoted; a check-2-only finding stays - YELLOW - -## Phase 5 — Low findings, date normalization, doc sync (D9) - -- [ ] F8: close `version-box` mode at the first line carrying no box character -- [ ] F8 fixture: unclosed `┌` followed by a `commands/` structure-table row → the row is still - measured against commands, not agents -- [ ] F9: REFCARD `tldr` row → "a line that **opens** with `TL;DR`" -- [ ] **Normalize the two claim sites (D9).** `NEWS.md` and `REFCARD.md` currently sit exactly one - day apart (2026-08-08 vs 2026-08-07), so the tolerance is the only thing keeping the repo - GREEN. Make them agree on the tag-local date and record the convention where the release - skill writes these, so the tolerance is headroom rather than load-bearing. -- [ ] Sync REFCARD, both CHANGELOGs, and the parent - `SPEC-doc-staleness-prose-gaps-2026-08-07.md` — its check-1 description is now wrong -- [ ] Mark the hardening SPEC's acceptance criteria - -## Phase 6 — Verify, promote, update PR #334 (D5, D10, D11) - -- [ ] Full suite in-tree; compare against the dev baseline (20 failed / 2462 passed) -- [ ] `docs-staleness-check.sh` GREEN; runtime under the 30s `test_pre_release_check_runs` budget -- [ ] `validate-counts.sh`, markdownlint, `mkdocs build --strict` -- [ ] **D11 promotion gate:** check 1 ran clean across every tracked `.md` and both known claim - sites, transcript quoted → promote to `error`. If not clean, it **stays `warning`** and the - promotion becomes a follow-up. Do not promote on a passing unit suite alone. -- [ ] **D10:** refresh the `.STATUS` worktree row — it still describes this branch as only the - parent SPEC and its harness, with no mention of the hardening work -- [ ] Update the PR #334 body with the hardening summary and quoted E2E transcripts -- [ ] Watch CI to green — **then stop.** Merging is a separate, explicit instruction. -- [ ] **At merge (D10):** delete `ORCHESTRATE-prose-check-hardening.md`. It is a feature-branch - working artifact, not `dev` content (craft CLAUDE.md). Nothing enforces this — not - `.gitignore`, not `exclusions.txt`, not a test — so it only happens if someone does it. - -## Friction Prevention - -- **The awk pre-filter and the bash matcher must stay in sync** (Phase 2). Two regexes expressing - one rule; if the pre-filter is tighter, lines silently never reach the matcher. This already - caused a 4× perf regression once when they were tuned independently. -- **Runtime budget is real, not theoretical.** `test_pre_release_check_runs` times out at 30s and - is the only thing that caught the last perf regression. Re-time after Phase 3. -- **Do not widen the broad plural-only scan.** Every hardening here is inside the line shapes; the - unscoped scan stays plural-only or the 90+ false positives return. -- **Phase 1 has the largest blast radius** — a record consumed by four phases, JSON output, and - both fix passes. Full suite after it, not just the prose tests. -- **Never delete a positive control to make a redesign easier** (D8). If a fixture's model is - obsolete, rewrite it so the boundary it pinned stays pinned. - -## Acceptance Criteria - -- [ ] Every finding F1–F9 has a test that **fails before its fix and passes after**, each verified - by planted mutation -- [ ] Hyphenated-compound corpus produces zero findings; real counts still caught -- [ ] `[e]xclude` round-trips end-to-end -- [ ] ADR-007's incorrect severity claim is removed, not softened -- [ ] Check 1 reaches both real claim sites (`docs/NEWS.md`, `docs/REFCARD.md`) — transcript quoted -- [ ] The two claim sites agree exactly, so the one-day tolerance is headroom (D9) -- [ ] RED promotion is evidence-gated, not assumed (D11) -- [ ] Live repo GREEN; runtime under budget; dev-baseline parity - -## Commit Strategy - -One commit per phase, conventional prefix, each naming the finding IDs it closes. Phase 1 commits -alone given its blast radius. No squashing before review — the phase boundaries are the review -units. - -## Verification - -```bash -cd ~/.git-worktrees/craft/feature-doc-staleness-prose-gaps -python3 -m pytest tests/test_docs_staleness_prose.py -q # per phase -python3 -m pytest tests/ -q # after Phase 1 and at Phase 6 -./scripts/docs-staleness-check.sh # GREEN -time ./scripts/docs-staleness-check.sh # under 30s -``` - -## Session Instructions - -Implementation continues in this session per craft's ORCHESTRATE convention (no handoff to a new -session). Every phase ends with its mutation controls demonstrated, not merely a passing suite. From 4390da1104a72ec383c257f31eb3216989c10312 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Sat, 15 Aug 2026 16:31:36 -0700 Subject: [PATCH 20/24] docs(.status): swap in the prose-check-hardening worktree Recreated rather than reused. After #334's squash-merge the old worktree's branch no longer descended from dev, so `git diff origin/dev...HEAD` re-proposed 24 files / +1666 lines already merged, against 2 files of real difference -- the ahead/behind counts read "dev ahead 3, branch ahead 10" and meant nothing. Continuing there would have made the next PR unreviewable. The branch name was also wrong: doc-staleness-prose-gaps shipped, this is the hardening. Deletion gated on the PR head matching the local tip (nothing added after the merge) and the merge commit being an ancestor of dev, per the squash-merge footgun -- `git cherry` misreports squash-merged branches as unmerged. Nothing lost: SPEC and GRILL are on dev; the ORCHESTRATE was restored into the new worktree from b5de1ae39. Co-Authored-By: Claude Opus 5 (1M context) --- .STATUS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.STATUS b/.STATUS index 82ea1ef76..43ada328f 100644 --- a/.STATUS +++ b/.STATUS @@ -709,7 +709,8 @@ next occurrence, not a date. | Branch | Path | Purpose | |--------|------|---------| -| _(prior, MERGED)_ | — | `feature/doc-staleness-prose-gaps` → dev via [#334](https://github.com/Data-Wise/craft/pull/334) (squash `b5de1ae39`), 2026-08-15. Two prose checks in `docs-staleness-check.sh` Phase 7 (release-date claims; count prose scoped to 4 structured line shapes, singular noun form included), ADR-007, and a 12-fixture table-driven harness. Four bugs were caught pre-merge by four different mechanisms — a 4× perf regression by the suite's own 30s timeout, an unparseable-authority false-positive storm by self-review, a release-date off-by-one by `/code-review`, and a pass-2 `[f]ix` that reported success without touching the file by reading adjacent code. **KNOWN, NOT FIXED:** a high-effort review found 9 further defects (2 HIGH — hyphenated compounds like `command-line` read as counts, and accepting the offered fix rewrites that prose). Merged on explicit instruction, reversing grill decision D5. Hardening is fully spec'd and grilled — `SPEC-prose-check-hardening-2026-08-15.md` + `GRILL-prose-check-hardening-2026-08-15.md` (11 locked branches) — but NOT implemented. Check 2 is `warning`-only so nothing blocks, but `--fix` can corrupt a document until it lands. Worktree still live at `~/.git-worktrees/craft/feature-doc-staleness-prose-gaps`. | +| `feature/prose-check-hardening` | `~/.git-worktrees/craft/feature-prose-check-hardening` | LIVE, created 2026-08-15 from `dev` @ `209765166`. Implements `SPEC-prose-check-hardening-2026-08-15.md` per `GRILL-prose-check-hardening-2026-08-15.md` (11 locked branches) — closes the 9 defects a high-effort review found in #334, incl. 2 HIGH (hyphenated compounds read as counts; the offered fix rewrites that prose). 6 phases, ~3h, record-change first per D7. Plan restored from `b5de1ae39` into the worktree root as `ORCHESTRATE-prose-check-hardening.md` (untracked until Phase 1 commits). Replaces the stale `feature/doc-staleness-prose-gaps` worktree, which was recreated rather than reused: after #334's squash-merge its history no longer descended from `dev`, so a three-dot diff re-proposed 24 files / +1666 lines already merged, versus 2 files of real difference. | +| _(prior, MERGED)_ | — | `feature/doc-staleness-prose-gaps` → dev via [#334](https://github.com/Data-Wise/craft/pull/334) (squash `b5de1ae39`), 2026-08-15. Two prose checks in `docs-staleness-check.sh` Phase 7 (release-date claims; count prose scoped to 4 structured line shapes, singular noun form included), ADR-007, and a 12-fixture table-driven harness. Four bugs were caught pre-merge by four different mechanisms — a 4× perf regression by the suite's own 30s timeout, an unparseable-authority false-positive storm by self-review, a release-date off-by-one by `/code-review`, and a pass-2 `[f]ix` that reported success without touching the file by reading adjacent code. **KNOWN, NOT FIXED:** a high-effort review found 9 further defects (2 HIGH — hyphenated compounds like `command-line` read as counts, and accepting the offered fix rewrites that prose). Merged on explicit instruction, reversing grill decision D5. Hardening is fully spec'd and grilled — `SPEC-prose-check-hardening-2026-08-15.md` + `GRILL-prose-check-hardening-2026-08-15.md` (11 locked branches) — but NOT implemented. Check 2 is `warning`-only so nothing blocks, but `--fix` can corrupt a document until it lands. Worktree removed and local branch deleted 2026-08-15; the work continues on `feature/prose-check-hardening` above. | | _(prior, MERGED)_ | — | `feature/repo-triage` → shipped in v4.5.0 (PR [#328](https://github.com/Data-Wise/craft/pull/328) squash to dev, released via [#329](https://github.com/Data-Wise/craft/pull/329)). Worktree removed. Original dispatch note: DISPATCHED 2026-08-07. New `repo-triage` skill (batch issue/worktree/branch triage, thin orchestrator over `issue-check` + `dev/git` Op 4/5). 6-phase ORCHESTRATE, `orchestrate-dispatch` mode. Phase 0 (`dev/git` Op 5 `--dry-run` + output contract) blocks Phases 2-4. Planning: `docs/specs/BRAINSTORM-repo-triage-2026-08-07.md`, `docs/specs/GRILL-repo-triage-2026-08-07.md` (amended post-review), `docs/specs/REVIEW-repo-triage-2026-08-07.md`. | | _(prior, MERGED)_ | — | `feature/prerelease-version-check-hardening` → dev via [#311](https://github.com/Data-Wise/craft/pull/311) (squash), 2026-07-25. Audited the repo for the "grep first bare vX.Y.Z string" bug class that broke the Homebrew release gate twice (v4.2.0, v4.3.0, fixed for `docs/index.md` in #308) — found and fixed 4 more live sites: `.github/workflows/docs.yml`'s live-site poll, `verify-surfaces.sh`'s `resolve_docs_site` (explicitly reused docs.yml's mechanism), `version-sync-precommit.sh`'s `.STATUS` check (a blocking pre-commit hook), and the manual verification steps in `skills/release/SKILL.md`/`downstream-verification.md`; all anchored to the actual version signal (badge slug / `^version:` field) instead of the first bare match. Confirmed 3 other sites already safe, left unchanged. New regression tests for each fix, each verified against a planted-defect positive control. Closes the recurrence risk flagged after v4.3.0. Worktree removed, local + remote branch deleted. | | _(prior, MERGED)_ | — | `feature/issue-309-branch-guard-docs` → dev via [#310](https://github.com/Data-Wise/craft/pull/310) (squash), 2026-07-25. The documented "Claude writes `.claude/allow-once`" one-shot bypass flow is circular when the approved action *is* creating/editing that marker itself (every tool path intercepts it with its own `[CONFIRM]`); added the `CRAFT_GUARD_ALLOW_DEV_EDIT` env-var hint (#281) to the 3 marker-specific confirms only, and reconciled `REFCARD-BRANCH-GUARD.md`/`branch-guard-smart-mode.md` with a caveat pointing at `skills/dev/git/SKILL.md` Operation 10. Code review caught the negative regression test was checking the wrong verbosity encounter (2nd-call brief mode never renders suggestions regardless of content, so it passed vacuously); fixed to assert against the 1st-encounter full-verbosity capture, verified via planted-defect positive control. Closes #309. Worktree removed, local + remote branch deleted. | From a95170b639614c3b3afc63a23409cfbf522a0b30 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Sat, 15 Aug 2026 18:20:38 -0700 Subject: [PATCH 21/24] docs: fix stale REFCARD test count + prune 6 dead exclusions Full docs-staleness-check.sh (all 4 gating phases) and post-release-sweep.sh already read GREEN/CLEAN. This is the advisory-only pass on top --audit-exclusions plus real content review, per the project's doc-currency check. REFCARD.md:9 claimed "2632+ passing" -- unguarded (no script owns this box line; claude_md_sync.py only tracks CLAUDE.md's bold **Tests:** form), and already wrong against the measured dev baseline (2462 passed / 20 failed). Same class as the README fix earlier this session -- swapped the hard number for the command that produces it, so it can't drift again. Also removed a duplicate "All 1432 tests passing" claim further down the same file, equally unguarded. Re-padded the box borders to the file's existing 63-char width. Pruned 6 exclusions.txt entries, all dead for one of two reasons: - docs/commands/orchestrate.md:4 agents -- file deleted (orch rename, v2.60.0) - The other 5 sat under docs/cookbook/ or docs/examples/, both already whole-directory excluded (lines 27-28) -- the per-pattern entries were redundant from the start, and their target numbers have since drifted further (94 -> 115), so they matched nothing either way. Verified: docs-staleness-check.sh GREEN (0 issues), --audit-exclusions "All exclusions valid" (was 6 stale), validate-counts.sh 4/4, markdownlint 0 errors, mkdocs build --strict clean. Co-Authored-By: Claude Opus 5 (1M context) --- docs/REFCARD.md | 5 ++--- scripts/config/exclusions.txt | 6 ------ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/REFCARD.md b/docs/REFCARD.md index b74d749f9..347622580 100644 --- a/docs/REFCARD.md +++ b/docs/REFCARD.md @@ -6,9 +6,9 @@ ├─────────────────────────────────────────────────────────────┤ │ Version: 4.5.0 (released 2026-08-07) │ │ Commands: 48 | Agents: 2 | Skills: 41 │ -│ Tests: 2632+ passing │ +│ Tests: python3 -m pytest tests/ │ │ Docs: https://data-wise.github.io/craft/ │ -│ v4.5.0: repo-triage skill + ecosystem-tool tutorials │ +│ v4.5.0: repo-triage skill + ecosystem-tool tutorials │ └─────────────────────────────────────────────────────────────┘ ``` @@ -573,7 +573,6 @@ python3 scripts/mermaid-autofix.py docs/ --fix # Auto-fix safe patte - Auto-fix: `/folio:docs:lint --fix` (apply safe fixes) - Path targeting: `/folio:docs:lint docs/guide/` (check specific directories) - Pre-commit integration: Auto-fix on staged markdown -- All 1432 tests passing (100%) - [Release Notes](RELEASE-v2.8.0.md) | [Docs Command Reference](commands/docs.md) **Interactive Documentation Update** diff --git a/scripts/config/exclusions.txt b/scripts/config/exclusions.txt index b75932712..33bc32d94 100644 --- a/scripts/config/exclusions.txt +++ b/scripts/config/exclusions.txt @@ -42,7 +42,6 @@ docs/architecture/HUB-V2-ARCHITECTURE.md:97 commands docs/tutorials/TUTORIAL-post-merge-pipeline.md:97 commands docs/demos/hub-v2.tape:97 commands docs/api/DISCOVERY-API.md:97 commands -docs/API-REFERENCE-COMMANDS.md:97 commands docs/reference/ERROR-SCENARIOS.md:97 commands docs/dev/CONTRIBUTING-HUB-V2.md:97 commands docs/REFCARD.md:99 commands @@ -73,7 +72,6 @@ docs/guide/orch-flag-usage.md:4 agents docs/orchestrator.md:4 agents docs/orchestrator.md:6 agents docs/commands.md:4 agents -docs/commands/orchestrate.md:4 agents docs/commands/hub.md:4 agents docs/commands/smart.md:4 agents docs/workflows/index.md:4 agents @@ -89,9 +87,6 @@ docs/tutorials/TUTORIAL-claude-code-2.1-enhancements.md:4 agents # Tutorial/example counts (historical snapshots, not current totals) # --------------------------------------------------------------------------- # "Stale CLAUDE.md" troubleshooting example (intentionally stale) -docs/cookbook/troubleshooting/claude-md-out-of-sync.md:94 commands -docs/cookbook/troubleshooting/claude-md-out-of-sync.md:18 skills -docs/cookbook/troubleshooting/claude-md-out-of-sync.md:6 agents # Tutorial showing a hypothetical plugin (different plugin, not craft) docs/tutorials/claude-md-workflows.md:51 commands @@ -107,7 +102,6 @@ docs/tutorials/interactive-docs-update-tutorial.md:99 commands # Docs-update interactive example (shows option preview 99→101) docs/examples/docs-update-interactive-example.md:101 commands -docs/examples/docs-update-interactive-example.md:99 commands # REFCARD-DOCS-UPDATE (describes a check type, not actual count) docs/reference/REFCARD-DOCS-UPDATE.md:60 commands From 7d79228535ebfd97752924ceafadd0c30164aab4 Mon Sep 17 00:00:00 2001 From: Davood Tofighi Date: Sat, 15 Aug 2026 22:48:23 -0600 Subject: [PATCH 22/24] fix(docs-staleness): harden PR #334's prose checks (F1-F9, D1-D11) (#335) * fix(docs-staleness): carry finding location as its own field (D4/F4) add_finding glued file+lineno into one string ("path:lineno"). Pass 2's [e]xclude writes an entry against $file expecting a bare path, so every Phase 7/9 exclusion request wrote a pattern that could never match is_pattern_excluded's path:pattern parse -- it printed "Excluded" and the finding came back on the next run. Predates PR #334; not a regression it introduced (git show dev: confirms the same glued shape at lines 341/497/548 before this PR). Splits the record into file + locator, threading the change through all 12 add_finding call sites, both consumers (pass1_auto_fix, pass2_interactive_review), and findings_to_json -- JSON output shape is unchanged (still emits "file":"path:locator" via a shared finding_location() helper). New test drives the real interactive [e] keystroke through a pty (pexpect), not a Python reimplementation of pass 2's write logic -- verifies the exclusion actually suppresses the finding on a second run. Phase 1 of 6 in the prose-check hardening (SPEC/GRILL-prose-check- hardening-2026-08-15.md, decision D4). Adds the ORCHESTRATE plan file, restored from the pre-merge worktree per handoff. Co-Authored-By: Claude Opus 5 (1M context) * fix(docs-staleness): matcher boundary, span-anchored fix, real floor (D3/F1/F2/F6) Check 2's noun trailer, `([^a-z]|$)`, treats `-` as a valid boundary character, so hyphenated compounds read as counts: "30 command-line entry points" matched as "30 command" (expected 48), "3 agent-facing surfaces" as "3 agent" (expected 2). Replaced with PROSE_COUNT_TRAILER (whitespace, closing punctuation, or end-of-line), shared by the awk pre-filter and the bash matcher so the two can't drift apart again -- independent tuning of that pair already caused a 4x perf regression once. Accepting the offered [f]ix on a genuine match corrupted the surrounding prose: the substitution was built from a second, un-anchored "N noun" regex with no trailer at all, so it could match a different (shorter) span than detection did. Detection and the fix anchor now derive from one captured span. The 40%-of-expected floor is 0 for agents (`2 * 40 / 100 == 0`), so the guard ADR-007 and two code comments cite as what makes shaped-line counts safe did not exist for the smallest count type -- any "N agent(s)" mention, however small, read as a stale-total candidate. Floored at max(2, 40%). New fixtures are positive controls, not just happy-path coverage: reverted the source change and confirmed both new falsepos cases fail (docs/ architecture.md flags '30 command'/'3 agent'; docs/migration.md flags '1 agent') before restoring the fix. A new pty-driven [f]ix test asserts the surrounding prose ("agent definitions") survives the substitution. E1 pin (defect/structure-table-singular.md, clean/structure-table- correct.md) and the release-date off-by-one guard (defect/release-date-far-edge.md) both still pass unchanged. 21/21 tests, ~10s raw script run (well under the 30s pre-release-check timeout). Phase 2 of 6 in the prose-check hardening (SPEC/GRILL-prose-check- hardening-2026-08-15.md, decision D3). Co-Authored-By: Claude Opus 5 (1M context) * fix(docs-staleness): check 1 -> cross-file consistency, heading-scoped window (D1/D6/F3/F7) Check 1 compared every release-date claim against the current version's git tag, but the tag doesn't exist at either point this check actually runs: the release pipeline writes NEWS/REFCARD dates in Step 3b, before Step 8 creates the tag, and docs-quality.yml's checkout has no fetch-tags. As built, the check only ever fired on a developer machine that had already pulled a tag that isn't the one being released -- not a gate anywhere it runs (F7). Dropped the external authority. Every release-date claim for the current version is now compared against every other one via a new majority_date() helper: most claims wins, ties break to the LATER date (a stale-date bug is a forgotten update, so the wrong claim is normally older than the correct one, never newer -- with the common case of exactly two claims, NEWS.md and a REFCARD-style box, every tie is 1-1, so this is what decides which one is "authority"). The one-day tolerance survives as the agreement window between claims, still absorbing the real UTC-boundary case (v4.5.0: NEWS.md 2026-08-08, REFCARD 2026-08-07). Vacuous with 0-1 claims, same posture as the old missing-tag case -- accepted cost, cannot catch a date uniformly wrong in every file. Separately, the claim-collection window opened on ANY line containing the version token, including running prose ("Upgrading to v4.5.0 is a drop-in change") -- a nearby unrelated date then got collected as though it were this version's release date (F3). Now requires the version token sit in a markdown heading or a version-box content line (D6). Test harness changed shape to match: check 1 needs a peer claim to agree or disagree with, so its fixtures now pair via a new build_repo_multi() helper instead of the single-fixture harness every other check uses. Reverted the source change and confirmed the 4 new/changed release-date tests fail against the pre-fix code (2 real assertion failures on the redesigned checks, 2 "command not found" for the not-yet-existing majority_date) before restoring the fix. 25/25 tests, ~12s raw script run. Phase 3 of 6 in the prose-check hardening (SPEC/GRILL-prose-check- hardening-2026-08-15.md, decisions D1 and D6). Co-Authored-By: Claude Opus 5 (1M context) * fix(docs-staleness): severity split -- check 1 error, check 2 warning (D2/F5) Check 1 (release-date consistency) now emits `error`; check 2 (count prose) stays `warning`. Not cosmetic: pre-release-check.sh:280 runs this whole script as `... || true`, discarding the exit code, and docs-quality.yml sets continue-on-error -- no consumer reads the process exit status. skills/release/SKILL.md:143 blocks on the RED label alone. The severity string IS the block decision, not a secondary label on top of one. Check 1 earns error: after the D1 cross-file redesign it's near-binary, two claims either agree or they don't. Check 2 stays warning: it matches free-form prose patterns and produced three false-positive defects in the PR that introduced it (a hyphenated-compound false match, a zero-floor for the smallest count type, five sub-threshold counts caught pre-merge) -- it hasn't earned a blocker yet. Rewrote ADR-007's Severity section, which had asserted "both severities already block the gate, so the label doesn't matter" -- checked during this review and found false per the citations above. Removed the claim, not softened it, per this branch's own rule (docs/adr's "reports success, changes nothing" posture applies to a doc's claims as much as a script's [f]ix). Also updated the ADR's authority description, still describing release dates as checked against a git tag from before the D1 rewrite. New test reverts the severity change and confirms it fails (check 1's finding comes back "warning" instead of "error") before restoring the fix. 26/26 tests. Phase 4 of 6 in the prose-check hardening (SPEC/GRILL-prose-check- hardening-2026-08-15.md, decision D2). Co-Authored-By: Claude Opus 5 (1M context) * fix(docs-staleness): un-promote check 1 to warning, gate error on evidence (D11) The previous commit shipped check 1 at severity error directly, skipping D11's evidence gate: "Check 1 ships as warning in this phase. Promotion to error happens in Phase 6, gated on evidence -- the check is being redesigned and promoted in one PR, and the only evidence it is sound would otherwise be tests written alongside it by the same author in the same sitting." (ORCHESTRATE-prose-check-hardening.md Phase 4.) Reverted check 1 to warning. Promotion to error belongs in Phase 6, after a clean run across every tracked doc and both real claim sites (docs/NEWS.md, docs/REFCARD.md) with the transcript quoted -- not on a passing unit suite alone. Rewrote ADR-007's Severity section to describe the gate rather than an unconditional split: check 1 is *designed* to block (D2) but ships warning until it earns error. Replaced the severity test, which had pinned the premature promotion, with two: one confirming both checks still ship warning pending the gate, and a second (test_phase_status_goes_red_on_any_error_finding) that pins the underlying mixed-severity mechanism directly against phase_status_label -- an error finding among warnings goes RED, an all-warning phase stays YELLOW -- independent of which check is promoted when, since neither check emits error yet. 27/27 tests. Still Phase 4 of 6 (SPEC/GRILL-prose-check-hardening-2026-08-15.md, decisions D2 and D11) -- this corrects the previous commit rather than starting Phase 5. Co-Authored-By: Claude Opus 5 (1M context) * fix(docs-staleness): box-close discipline, doc/regex sync, date normalization (D9/F8/F9) F8: an unclosed version-box (opening box character with no matching closing one) leaked `inbox` mode into the rest of the file, so a later structure-table row lost its own-type restriction. Closed at the FIRST line carrying no box-drawing character at all, not only on an explicit closing character. Discovered while planting F8's fixture: the broad, unscoped plural-only count scan shares check 2's F1 hyphen-boundary bug -- "7 agents-only" matched as "7 agents" (expected 2), and this scan's findings are auto-fixable, so it's worse than F2 (no human in the loop under `--fix --non-interactive`). Not a separately enumerated finding; surfaced by the hardening SPEC's own hyphenated-compound acceptance criterion (command-line, agent-facing, skill-authoring, agents-only). Fixed with the same PROSE_COUNT_TRAILER and span-anchored substitution as check 2. F9: REFCARD-DOCS-STALENESS.md described `tldr` as "a line containing TL;DR"; the regex requires the line to open with it (deliberate, so a doc describing this bug isn't itself flagged for the example it quotes). Fixed the doc. D9: normalized the two real release-date claim sites. docs/NEWS.md's v4.5.0 entry moved from 2026-08-08 (GitHub release UTC) to 2026-08-07 (tag-local); docs/REFCARD.md's box line gained the `v4.5.0` prefix it was missing -- without it, the line sat 4 lines before the box's other "v4.5.0" mention, so D6's forward-only window never reached it, and REFCARD contributed zero claims regardless of date value. Both CHANGELOGs' v4.5.0 headings synced to match. Recorded the convention in skills/release/references/pipeline-steps.md so it doesn't drift again. Verified live: injected a temporary mismatch into REFCARD.md's date and confirmed check 1 caught it ("disagrees with other claims (majority: 2026-08-07)") before reverting the injection. Added a "Superseded" pointer to the parent SPEC-doc-staleness-prose-gaps -2026-08-07.md rather than rewriting its historical acceptance criteria, and marked the hardening SPEC's own acceptance criteria against verified evidence gathered across all five phases so far. New/changed fixtures verified as positive controls by reverting each fix and re-testing: falsepos/unclosed-box-leaks-into-table.md (uses the singular noun form so it isolates the shape-classification bug from the unrelated broad scan) and the broadened hyphenated-compound-tldr.md (now covers skill-authoring, agents-only). 28/28 tests, ~11s raw script run. Phase 5 of 6 in the prose-check hardening (SPEC/GRILL-prose-check- hardening-2026-08-15.md, decision D9). Co-Authored-By: Claude Opus 5 (1M context) * fix(docs-staleness): Phase 6 verify + promote check 1 to error (D11) Full-suite run surfaced 2 regressions not in the original F1-F9 list, both introduced by earlier phases in this branch: - PROSE_COUNT_TRAILER was missing `*` from its boundary-char class, so the broad plural-only count scan never matched markdown-bold badges like README's `**777 commands**` -- the char after "commands" is `*`, not whitespace/punctuation/EOL. Added `*` to the trailer and both noun-trim sed patterns that must stay in sync with it. - bump-version.sh --verify's literal `Version: X.Y.Z` grep broke against Phase 5's REFCARD.md wording (`v4.5.0 (released ...)`, added to satisfy D6's window). Reworded to `Version: 4.5.0 (v4.5.0, released ...)`, satisfying both the grep and D6's same-line version-token requirement. Both verified via revert-and-retest positive control before restoring. Full suite: 2476 passed / 0 failed (dev baseline: 2462 passed / 20 failed). D11 promotion gate cleared: docs-staleness-check.sh --json returned 0 count_consistency findings across every tracked doc, reaching both real claim sites (docs/NEWS.md, docs/REFCARD.md). Live-fire transcript: injecting 2020-01-01 into docs/REFCARD.md:7 produced "release date '2020-01-01' for v4.5.0 disagrees with other claims (majority: 2026-08-07)", reverted after confirming. Check 1 promoted warning->error; check 2 stays warning. ADR-007 and REFCARD-DOCS-STALENESS.md updated to match (the latter also had stale pre-redesign prose describing the old git-tag authority and the now-deleted CRAFT_RELEASE_DATE override). .STATUS worktree row refreshed (D10); ORCHESTRATE Phase 6 + Acceptance Criteria checkboxes marked with evidence. Co-Authored-By: Claude Sonnet 5 * test(docs-staleness): drop dead CRAFT_RELEASE_DATE env var from fixtures Flagged by an independent architecture review of PR #335: the only consumer, resolve_release_date(), was deleted in this branch's Phase 3 redesign (check 1 moved from a single external authority to cross-file consistency), but 3 test call sites still set the env var. Harmless (unread by the script) but stale -- a future reader would assume it still did something. FIXTURE_TAG_DATE itself stays; it still documents the date fixtures are authored against (e.g. clean/release-date-companion.md's literal "2026-08-07"). Co-Authored-By: Claude Sonnet 5 --------- Co-authored-by: Claude Opus 5 (1M context) --- .STATUS | 2 +- CHANGELOG.md | 2 +- ORCHESTRATE-prose-check-hardening.md | 197 +++++++++ docs/CHANGELOG.md | 2 +- docs/NEWS.md | 2 +- docs/REFCARD.md | 2 +- ...7-pattern-scoped-prose-staleness-gating.md | 94 ++++- docs/reference/REFCARD-DOCS-STALENESS.md | 19 +- ...PEC-doc-staleness-prose-gaps-2026-08-07.md | 15 + .../SPEC-prose-check-hardening-2026-08-15.md | 35 +- scripts/docs-staleness-check.sh | 329 +++++++++++---- skills/release/references/pipeline-steps.md | 11 +- .../clean/release-date-companion.md | 11 + .../falsepos/hyphenated-compound-tldr.md | 11 + .../falsepos/release-date-prose-mention.md | 12 + .../falsepos/tldr-agent-subset-count.md | 10 + .../falsepos/unclosed-box-leaks-into-table.md | 18 + tests/test_docs_staleness_prose.py | 396 +++++++++++++++--- 18 files changed, 978 insertions(+), 190 deletions(-) create mode 100644 ORCHESTRATE-prose-check-hardening.md create mode 100644 tests/fixtures/prose-staleness/clean/release-date-companion.md create mode 100644 tests/fixtures/prose-staleness/falsepos/hyphenated-compound-tldr.md create mode 100644 tests/fixtures/prose-staleness/falsepos/release-date-prose-mention.md create mode 100644 tests/fixtures/prose-staleness/falsepos/tldr-agent-subset-count.md create mode 100644 tests/fixtures/prose-staleness/falsepos/unclosed-box-leaks-into-table.md diff --git a/.STATUS b/.STATUS index 43ada328f..e4afff268 100644 --- a/.STATUS +++ b/.STATUS @@ -709,7 +709,7 @@ next occurrence, not a date. | Branch | Path | Purpose | |--------|------|---------| -| `feature/prose-check-hardening` | `~/.git-worktrees/craft/feature-prose-check-hardening` | LIVE, created 2026-08-15 from `dev` @ `209765166`. Implements `SPEC-prose-check-hardening-2026-08-15.md` per `GRILL-prose-check-hardening-2026-08-15.md` (11 locked branches) — closes the 9 defects a high-effort review found in #334, incl. 2 HIGH (hyphenated compounds read as counts; the offered fix rewrites that prose). 6 phases, ~3h, record-change first per D7. Plan restored from `b5de1ae39` into the worktree root as `ORCHESTRATE-prose-check-hardening.md` (untracked until Phase 1 commits). Replaces the stale `feature/doc-staleness-prose-gaps` worktree, which was recreated rather than reused: after #334's squash-merge its history no longer descended from `dev`, so a three-dot diff re-proposed 24 files / +1666 lines already merged, versus 2 files of real difference. | +| `feature/prose-check-hardening` | `~/.git-worktrees/craft/feature-prose-check-hardening` | Hardens PR #334's prose checks per `SPEC-prose-check-hardening-2026-08-15.md` + `GRILL-prose-check-hardening-2026-08-15.md` (11 locked branches). All 6 phases implemented and verified: F1-F9 fixed (trailer/floor/fix-payload safety, cross-file release-date redesign dropping the git-tag authority, `[e]xclude` record split, box-close discipline, REFCARD prose accuracy). **D11 promotion gate cleared 2026-08-15**: check 1 promoted `warning`→`error` after a clean live-repo run (0 findings) across every tracked doc and both real claim sites (`docs/NEWS.md`, `docs/REFCARD.md`), plus a quoted transcript of the check firing on an injected mismatch. Check 2 stays `warning`. Full suite 2476 passed/0 failed (better than the 2462/20-failed dev baseline the branch started from — includes 2 tripwire-test fixes found during Phase 6 verification, not in the original F1-F9 list: `PROSE_COUNT_TRAILER` was missing `*`, so markdown-bold README badges like `**777 commands**` never matched the broad scan; `bump-version.sh --verify`'s literal `Version: X.Y.Z` grep broke against the Phase 5 REFCARD wording, fixed by a wording that satisfies both the grep and D6's window). `docs-staleness-check.sh` GREEN, 8.9s; `validate-counts.sh` 4/4; `mkdocs build --strict` clean; markdownlint 0 errors. PR [#335](https://github.com/Data-Wise/craft/pull/335) open. | | _(prior, MERGED)_ | — | `feature/doc-staleness-prose-gaps` → dev via [#334](https://github.com/Data-Wise/craft/pull/334) (squash `b5de1ae39`), 2026-08-15. Two prose checks in `docs-staleness-check.sh` Phase 7 (release-date claims; count prose scoped to 4 structured line shapes, singular noun form included), ADR-007, and a 12-fixture table-driven harness. Four bugs were caught pre-merge by four different mechanisms — a 4× perf regression by the suite's own 30s timeout, an unparseable-authority false-positive storm by self-review, a release-date off-by-one by `/code-review`, and a pass-2 `[f]ix` that reported success without touching the file by reading adjacent code. **KNOWN, NOT FIXED:** a high-effort review found 9 further defects (2 HIGH — hyphenated compounds like `command-line` read as counts, and accepting the offered fix rewrites that prose). Merged on explicit instruction, reversing grill decision D5. Hardening is fully spec'd and grilled — `SPEC-prose-check-hardening-2026-08-15.md` + `GRILL-prose-check-hardening-2026-08-15.md` (11 locked branches) — but NOT implemented. Check 2 is `warning`-only so nothing blocks, but `--fix` can corrupt a document until it lands. Worktree removed and local branch deleted 2026-08-15; the work continues on `feature/prose-check-hardening` above. | | _(prior, MERGED)_ | — | `feature/repo-triage` → shipped in v4.5.0 (PR [#328](https://github.com/Data-Wise/craft/pull/328) squash to dev, released via [#329](https://github.com/Data-Wise/craft/pull/329)). Worktree removed. Original dispatch note: DISPATCHED 2026-08-07. New `repo-triage` skill (batch issue/worktree/branch triage, thin orchestrator over `issue-check` + `dev/git` Op 4/5). 6-phase ORCHESTRATE, `orchestrate-dispatch` mode. Phase 0 (`dev/git` Op 5 `--dry-run` + output contract) blocks Phases 2-4. Planning: `docs/specs/BRAINSTORM-repo-triage-2026-08-07.md`, `docs/specs/GRILL-repo-triage-2026-08-07.md` (amended post-review), `docs/specs/REVIEW-repo-triage-2026-08-07.md`. | | _(prior, MERGED)_ | — | `feature/prerelease-version-check-hardening` → dev via [#311](https://github.com/Data-Wise/craft/pull/311) (squash), 2026-07-25. Audited the repo for the "grep first bare vX.Y.Z string" bug class that broke the Homebrew release gate twice (v4.2.0, v4.3.0, fixed for `docs/index.md` in #308) — found and fixed 4 more live sites: `.github/workflows/docs.yml`'s live-site poll, `verify-surfaces.sh`'s `resolve_docs_site` (explicitly reused docs.yml's mechanism), `version-sync-precommit.sh`'s `.STATUS` check (a blocking pre-commit hook), and the manual verification steps in `skills/release/SKILL.md`/`downstream-verification.md`; all anchored to the actual version signal (badge slug / `^version:` field) instead of the first bare match. Confirmed 3 other sites already safe, left unchanged. New regression tests for each fix, each verified against a planted-defect positive control. Closes the recurrence risk flagged after v4.3.0. Worktree removed, local + remote branch deleted. | diff --git a/CHANGELOG.md b/CHANGELOG.md index b583d7725..489ba4eb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,7 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- -## [4.5.0] - 2026-08-08 +## [4.5.0] - 2026-08-07 ### Added diff --git a/ORCHESTRATE-prose-check-hardening.md b/ORCHESTRATE-prose-check-hardening.md new file mode 100644 index 000000000..3e8116988 --- /dev/null +++ b/ORCHESTRATE-prose-check-hardening.md @@ -0,0 +1,197 @@ +# ORCHESTRATE: Prose-Check Hardening + +**Spec:** [`docs/specs/SPEC-prose-check-hardening-2026-08-15.md`](docs/specs/SPEC-prose-check-hardening-2026-08-15.md) +**Grill:** [`docs/specs/GRILL-prose-check-hardening-2026-08-15.md`](docs/specs/GRILL-prose-check-hardening-2026-08-15.md) — 11 branches locked (D1–D6 spec, D7–D11 plan) +**Worktree:** `~/.git-worktrees/craft/feature-doc-staleness-prose-gaps` (exists) +**Branch:** `feature/doc-staleness-prose-gaps` · **PR:** #334 (open, CI green, **do not merge until P6**) + +## Objective + +Close the 9 defects a high-effort review found in PR #334, per the grill's locked decisions, on the +same branch — so #334 never ships a check that emits false positives or corrupts prose. + +## Phase Overview + +**Reordered per D7:** the record change goes first. As originally written, Phases 1–3 authored +`add_finding` calls and tests against the old signature and Phase 4 then rewrote all 12 call sites — +every prose finding written twice, its tests touched twice, and the mechanical sweep landing where +a missed site is likeliest. + +| # | Phase | Findings | Locked by | Est | +|---|-------|----------|-----------|-----| +| 1 | Finding record: line number as its own field | F4 | D4, D7 | 45 min | +| 2 | Matcher, fix payload, floor | F1, F2, F6 | D3 | 25 min | +| 3 | Check 1 → cross-file consistency | F3, F7 | D1, D6, D8 | 40 min | +| 4 | Severity split + ADR correction | F5 | D2, D11 | 20 min | +| 5 | Low findings, date normalization, doc sync | F8, F9 | D9 | 25 min | +| 6 | Verify, promote, update PR #334 | — | D5, D10, D11 | 30 min | + +**Estimate honesty (D11):** ~3 hours, not the 2 first written. Phase 1 alone is 12 call sites plus +JSON rendering plus both fix passes plus an end-to-end exclusion test; 25 minutes was wishful. + +Sequential. Phase 1 must complete before any other phase touches `add_finding`. + +## Phase 1 — Finding record: line number as its own field (D4, D7) + +Goes first so every later phase is written once, against the final signature, and so the +largest-blast-radius change lands against an unmodified baseline where the full suite is a clean +control. + +- [ ] `add_finding` takes line number as its own argument instead of glued into `file` +- [ ] Update all 12 call sites: Phase 6 (×2), Phase 7 (×3), Phase 8 (×3), Phase 9 (×4) +- [ ] JSON output preserves today's `file` rendering (`path:lineno`) so consumers do not break — + the split is internal +- [ ] `[e]` writes `path:pattern`, matching what `is_pattern_excluded` parses +- [ ] **E2E, not inspection:** write the exclusion, re-run, assert the finding is gone +- [ ] Mutation control: revert the record change → the round-trip test fails +- [ ] **Full suite after this phase**, not just the prose tests — this is the widest change here + +## Phase 2 — Matcher, fix payload, floor (D3) + +- [ ] Trailer: `([^a-z]|$)` → whitespace / closing punctuation / end-of-line, in the check-2 + matcher **and** awk's `hascount()` pre-filter (they must agree, or the pre-filter drops lines + the matcher would have caught) +- [ ] Floor: `expected * 40 / 100` → `max(2, expected * 40 / 100)` +- [ ] Fix payload: build the substitution from the full matched span, not the bare `N noun` prefix +- [ ] Fixture `falsepos/hyphenated-compounds.md`: `command-line`, `agent-facing`, + `skill-authoring`, `agents-only` — all GREEN +- [ ] Fixture `defect/small-count-agents.md`: a wrong agent count the zero floor used to pass only + by accident, now caught deliberately +- [ ] **Pin E1 survives:** `clean/structure-table-correct.md` and + `defect/structure-table-singular.md` pass unchanged — `8 agent definitions` has a space after + the noun and `8 ≥ max(2, 0)` +- [ ] Mutation control per change: revert the trailer → hyphen fixture fails; revert the floor → + small-count fixture fails; revert the span anchor → a fix-application test corrupts prose + +## Phase 3 — Check 1 → cross-file consistency (D1, D6, D8) + +- [ ] Window opens only on a **markdown heading** or a version-box line containing the version + token — not on any line mentioning it (D6, closes F3) +- [ ] Replace `resolve_release_date` / `compute_release_date_window` / `release_date_accepted` + with claim collection + pairwise agreement (one-day tolerance retained) +- [ ] Delete `CRAFT_RELEASE_DATE`; the harness no longer injects an authority +- [ ] Report shape names **both** disagreeing sites — a finding naming one file cannot be acted on +- [ ] Vacuous when fewer than 2 claims exist (ADR-007: report only what you can establish) +- [ ] **Rewrite, never delete, the two existing check-1 fixtures (D8).** The window still exists + under D6, so the off-by-one class is still live and `defect/release-date-far-edge.md` is its + only positive control — the exact bug `/code-review` caught. Rewrite it as: heading at line N, + disagreeing claim at the window's last line → still RED. Rewrite + `defect/version-box-stale-date.md` as an in-box disagreement, since boxes are where the + original REFCARD bug lived. +- [ ] Fixture `falsepos/version-mentioned-in-prose.md`: "Upgrading to v4.5.0 is a drop-in change" + followed by an unrelated `Released:` date → GREEN +- [ ] Fixture `defect/release-date-disagreement.md` pair; `clean/release-date-utc-boundary.md` + rewritten to a two-claim form one day apart → GREEN + +**Real claim sites, confirmed 2026-08-15** — the check must reach both: + +| Site | Shape | Reached via | +|---|---|---| +| `docs/NEWS.md:9` | `**Released:** 2026-08-08` | `## v4.5.0 …` heading two lines above | +| `docs/REFCARD.md:7` | `│ Version: 4.5.0 (released 2026-08-07)` | version-box line, claim on the same line | + +## Phase 4 — Severity split + ADR correction (D2, D11) + +- [ ] Check 1 ships as **`warning`** in this phase. Promotion to `error` happens in Phase 6, gated + on evidence (D11) — the check is being redesigned and promoted in one PR, and the only + evidence it is sound would otherwise be tests written alongside it. +- [ ] `print_phase_status` / phase-label handling copes with mixed severities in one phase +- [ ] ADR-007 Severity section: **remove** the false claim (that both severities fail the gate + identically), replace with verified behavior — `pre-release-check.sh:280` swallows the exit + code, `docs-quality.yml` sets `continue-on-error`, `skills/release/SKILL.md:143` blocks on + the RED label. Record the D11 gate as the reason RED is earned, not assumed. +- [ ] Test: a check-1 finding makes overall status RED once promoted; a check-2-only finding stays + YELLOW + +## Phase 5 — Low findings, date normalization, doc sync (D9) + +- [x] F8: close `version-box` mode at the first line carrying no box character +- [x] F8 fixture: unclosed `┌` followed by a `commands/` structure-table row → the row is still + measured against commands, not agents (`falsepos/unclosed-box-leaks-into-table.md`; uses + the singular noun form so the fixture isolates the shape-classification bug from the + broad, unscoped plural scan, which would otherwise independently catch the same text) +- [x] F9: REFCARD `tldr` row → "a line that **opens** with `TL;DR`" + (`docs/reference/REFCARD-DOCS-STALENESS.md`) +- [x] **Normalize the two claim sites (D9).** `docs/NEWS.md`'s v4.5.0 entry moved from + 2026-08-08 to 2026-08-07 (tag-local); `docs/REFCARD.md`'s box line gained the `v4.5.0` + prefix it was missing, which is what makes it reachable under D6's heading-or-box-line + window in the first place (it sat 4 lines *before* the box's other `v4.5.0` mention, so + the forward-only window never reached it). Recorded the convention in + `skills/release/references/pipeline-steps.md`. Verified live by injecting a temporary + mismatch into REFCARD.md and confirming check 1 caught it before reverting. +- [x] Sync REFCARD, both CHANGELOGs, and the parent + `SPEC-doc-staleness-prose-gaps-2026-08-07.md` — its check-1 description is now wrong + (added a "Superseded" pointer section rather than rewriting the historical record) +- [x] Mark the hardening SPEC's acceptance criteria + +## Phase 6 — Verify, promote, update PR #334 (D5, D10, D11) + +- [x] Full suite in-tree; compare against the dev baseline (20 failed / 2462 passed) — **2476 + passed / 0 failed**, better than baseline. Also fixed 2 regressions surfaced by this run + (not in F1-F9): `PROSE_COUNT_TRAILER` missing `*` broke the broad scan against markdown-bold + README badges (`**777 commands**`); `bump-version.sh --verify`'s literal `Version: X.Y.Z` + grep broke against the Phase 5 REFCARD wording, reconciled with a wording that satisfies + both it and D6's window. +- [x] `docs-staleness-check.sh` GREEN; runtime under the 30s `test_pre_release_check_runs` budget + — 8.9s +- [x] `validate-counts.sh` (4/4 ✓), markdownlint (0 errors, 10 changed files), `mkdocs build + --strict` (clean) +- [x] **D11 promotion gate:** check 1 ran clean (0 findings) across every tracked `.md` and both + known claim sites, transcript quoted → **promoted to `error`**. Transcript: injecting + `2020-01-01` into `docs/REFCARD.md:7` produced `"release date '2020-01-01' for v4.5.0 + disagrees with other claims (majority: 2026-08-07)"`, reverted after confirming. Check 2 + stays `warning`. +- [x] **D10:** refreshed the `.STATUS` worktree row with the hardening summary +- [ ] Update the PR #334 body with the hardening summary and quoted E2E transcripts — N/A: #334 + already merged: this is a new PR from `feature/prose-check-hardening` → `dev` +- [ ] Watch CI to green — **then stop.** Merging is a separate, explicit instruction. +- [ ] **At merge (D10):** delete `ORCHESTRATE-prose-check-hardening.md`. It is a feature-branch + working artifact, not `dev` content (craft CLAUDE.md). Nothing enforces this — not + `.gitignore`, not `exclusions.txt`, not a test — so it only happens if someone does it. + +## Friction Prevention + +- **The awk pre-filter and the bash matcher must stay in sync** (Phase 2). Two regexes expressing + one rule; if the pre-filter is tighter, lines silently never reach the matcher. This already + caused a 4× perf regression once when they were tuned independently. +- **Runtime budget is real, not theoretical.** `test_pre_release_check_runs` times out at 30s and + is the only thing that caught the last perf regression. Re-time after Phase 3. +- **Do not widen the broad plural-only scan.** Every hardening here is inside the line shapes; the + unscoped scan stays plural-only or the 90+ false positives return. +- **Phase 1 has the largest blast radius** — a record consumed by four phases, JSON output, and + both fix passes. Full suite after it, not just the prose tests. +- **Never delete a positive control to make a redesign easier** (D8). If a fixture's model is + obsolete, rewrite it so the boundary it pinned stays pinned. + +## Acceptance Criteria + +- [x] Every finding F1–F9 has a test that **fails before its fix and passes after**, each verified + by planted mutation +- [x] Hyphenated-compound corpus produces zero findings; real counts still caught +- [x] `[e]xclude` round-trips end-to-end +- [x] ADR-007's incorrect severity claim is removed, not softened +- [x] Check 1 reaches both real claim sites (`docs/NEWS.md`, `docs/REFCARD.md`) — transcript quoted +- [x] The two claim sites agree exactly, so the one-day tolerance is headroom (D9) +- [x] RED promotion is evidence-gated, not assumed (D11) — promoted 2026-08-15, see Phase 6 +- [x] Live repo GREEN; runtime under budget (8.9s); dev-baseline parity (2476/0 vs. 2462/20) + +## Commit Strategy + +One commit per phase, conventional prefix, each naming the finding IDs it closes. Phase 1 commits +alone given its blast radius. No squashing before review — the phase boundaries are the review +units. + +## Verification + +```bash +cd ~/.git-worktrees/craft/feature-doc-staleness-prose-gaps +python3 -m pytest tests/test_docs_staleness_prose.py -q # per phase +python3 -m pytest tests/ -q # after Phase 1 and at Phase 6 +./scripts/docs-staleness-check.sh # GREEN +time ./scripts/docs-staleness-check.sh # under 30s +``` + +## Session Instructions + +Implementation continues in this session per craft's ORCHESTRATE convention (no handoff to a new +session). Every phase ends with its mutation controls demonstrated, not merely a passing suite. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 27b46a892..ec0328729 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -43,7 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- -## [4.5.0] - 2026-08-08 +## [4.5.0] - 2026-08-07 ### Added diff --git a/docs/NEWS.md b/docs/NEWS.md index b935d06a6..0bbb56154 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -6,7 +6,7 @@ Release announcements and notable changes for the Craft plugin. ## v4.5.0 — repo-triage skill + ecosystem-tool tutorials -**Released:** 2026-08-08 · **Type:** Minor +**Released:** 2026-08-07 · **Type:** Minor ### Highlights diff --git a/docs/REFCARD.md b/docs/REFCARD.md index 347622580..f8dba434e 100644 --- a/docs/REFCARD.md +++ b/docs/REFCARD.md @@ -4,7 +4,7 @@ ┌─────────────────────────────────────────────────────────────┐ │ CRAFT PLUGIN QUICK REFERENCE │ ├─────────────────────────────────────────────────────────────┤ -│ Version: 4.5.0 (released 2026-08-07) │ +│ Version: 4.5.0 (v4.5.0, released 2026-08-07) │ │ Commands: 48 | Agents: 2 | Skills: 41 │ │ Tests: python3 -m pytest tests/ │ │ Docs: https://data-wise.github.io/craft/ │ diff --git a/docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md b/docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md index 7733ff6db..1e5337731 100644 --- a/docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md +++ b/docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md @@ -45,40 +45,88 @@ names. The existing 40%-of-expected floor applies to shaped lines too — struct shapes still carry legitimate non-totals (category subtotals in a reference box, a bolded subset count, a narrative count about another plugin). -Release dates are checked against the **git tag's local date** for the current -version, with a one-day window. Not `.STATUS`'s `release_date:` — the two -legitimately disagree whenever a release publishes across the UTC boundary -(v4.5.0: tag 2026-08-07, GitHub release 2026-08-08T03:44Z), and a check that -flags a correct repo on its first run does not survive its first release. +Release dates for the current version are checked **against each other**, with a +one-day agreement window — not against the git tag's local date, as originally +built. The tag doesn't exist at either point this check actually runs (the +release pipeline writes NEWS/REFCARD dates *before* the tag is created; CI's +checkout has no `fetch-tags`), so a tag-based check only ever fired on a +developer machine that had already pulled the tag for a *previous* release — +never a real gate. Not `.STATUS`'s `release_date:` either — claims legitimately +disagree by a day whenever a release publishes across the UTC boundary (v4.5.0: +NEWS.md 2026-08-08, REFCARD.md 2026-08-07), and a check that flags a correct repo +on its first run does not survive its first release. See the prose-check +hardening's `GRILL-prose-check-hardening-2026-08-15.md` D1 for the full account. Constraints held from the BRAINSTORM: no new script (D1), craft only (D2), no semantic/NLP layer (D3), no external prose tool (D4). ## Severity -Findings are emitted at **`warning`**, matching every existing Phase 7 finding — -not `error`, which the SPEC's first draft specified. - -This is a smaller difference than it reads. `main()` exits 1 for warnings and -errors alike, so both already fail `/craft:check --for release` and -`pre-release-check.sh`; the divergence is the RED/YELLOW label, not whether the -gate blocks. Given that these are the first checks in this repo to judge *prose* -rather than structured tokens, and that the first build of check 2 produced five -false positives before the floor was added, shipping at `warning` follows -ADR-003's gentle-ramp precedent: earn `error` after the checks run clean across a -few real releases. +Split per check, not uniform across the phase — but check 1's `error` is **earned**, +not shipped by default. Both checks emitted `warning` initially. Check 1's +redesign (dropping the git-tag authority for cross-file comparison) and its +promotion to release-blocking could not land in the same change — the only +evidence it was sound would otherwise be tests written alongside it by the +same author in the same sitting, which is not independent evidence. + +**Promoted 2026-08-15.** The gate cleared: `docs-staleness-check.sh --json` +returned zero `count_consistency` findings across every tracked doc, reaching +both real claim sites (`docs/NEWS.md`, `docs/REFCARD.md`), and the check was +proven live by injecting a mismatch and watching it fire — + +``` +release date '2020-01-01' for v4.5.0 disagrees with other claims (majority: 2026-08-07) +``` + +— at `docs/REFCARD.md:7`, then reverted. Check 1 now emits `error`. Check 2 +stays `warning` (see below). + +An earlier version of this section claimed the choice barely mattered — that +`main()` exits 1 for warnings and errors alike, so both already fail +`/craft:check --for release` and `pre-release-check.sh`, and the only difference +was the RED/YELLOW label. **That claim was checked during the prose-check +hardening review (2026-08-15) and found false**, not softened: + +- `pre-release-check.sh:280` runs the whole script as `… || true`, discarding the + exit code — "Check 9: Docs staleness (warn-only, does not block release)". +- `.github/workflows/docs-quality.yml` sets `continue-on-error: true`. +- `skills/release/SKILL.md:143` is explicit: "RED findings block; YELLOW findings + warn but allow proceed." + +No consumer reads the process exit status. **The label is the only gate.** The +severity choice a finding gets *is* the block decision — not a cosmetic split of +an already-blocking outcome. + +Given that, severity now follows demonstrated precision per check rather than a +single phase-wide default: + +- **Check 1 is near-binary after the cross-file redesign** (release-date claims + either agree with each other or they do not), so it blocks — the evidence + gate above cleared 2026-08-15. +- **Check 2 matches prose patterns**, and produced three false-positive defects + in the PR that introduced it alone (a hyphenated-compound false match, a + zero-floor for the smallest count type, and five sub-threshold counts caught + pre-merge) — it has not earned `error` at all. `warning` here follows + ADR-003's gentle-ramp precedent: earn `error` after the check runs clean + across a few real releases, same as check 1's gate but without a fixed + promotion point yet. ## A failed authority makes a check vacuous, never universal -Every check here compares documentation against an authority — the git tag date, `plugin.json`'s -counts. When an authority is **missing**, the check skips: no tag yet is the normal state on a -feature branch. The rule this ADR adds is that when an authority is **present but unusable**, the +Every check here compares documentation against an authority — `plugin.json`'s counts for check 2, +and (after the check-1 redesign below) the other release-date claims in the repo for check 1, no +longer a git tag. When an authority is **missing**, the check skips: fewer than two release-date +claims to compare is the normal state on a feature branch, or in a repo where only one doc names a +date at all. The rule this ADR adds is that when an authority is **present but unusable**, the check skips too. -The first build did not do this. An unparseable tag date left an empty accept-window, and an empty -window matches nothing, so every release-date claim in the repo failed at once. One bad input -became a repo-wide false-positive storm — the loudest possible output from the least reliable -possible input. +The first build did not do this, back when the authority was a git tag. An unparseable tag date +left an empty accept-window, and an empty window matches nothing, so every release-date claim in +the repo failed at once. One bad input became a repo-wide false-positive storm — the loudest +possible output from the least reliable possible input. The guard survives the redesign: an +unparseable authority (now impossible in practice, since it can only come from a claim string +already validated by the collection regex) still produces an empty window, and an empty window +still rejects a claim rather than accepting it. Stated generally, for any check added to this script later: **a check may only report a finding it can positively establish.** Absence of a usable comparison is not evidence of drift. The failure diff --git a/docs/reference/REFCARD-DOCS-STALENESS.md b/docs/reference/REFCARD-DOCS-STALENESS.md index 57972d905..634d12af1 100644 --- a/docs/reference/REFCARD-DOCS-STALENESS.md +++ b/docs/reference/REFCARD-DOCS-STALENESS.md @@ -53,25 +53,23 @@ ## Phase 7 prose checks -Added 2026-08-15 — [ADR-007](../adr/ADR-007-pattern-scoped-prose-staleness-gating.md), -[SPEC](../specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md). Both emit `warning`. +Added 2026-08-15, hardened the same day (`SPEC-prose-check-hardening-2026-08-15.md`) — +[ADR-007](../adr/ADR-007-pattern-scoped-prose-staleness-gating.md), +[SPEC](../specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md). Check 1 emits `error` +(promoted 2026-08-15 — see ADR-007's Severity section for the evidence gate); +check 2 emits `warning`. | Check | What It Catches | |-------|-----------------| -| Release-date claims | A `Released: YYYY-MM-DD` on the current version's line **or the 4 lines below it**, more than one day off the version's **git tag date**. The one-day window absorbs releases published across the UTC boundary. | +| Release-date claims | A `Released: YYYY-MM-DD` claim on a markdown heading line or a version-box (`┌`…`└`) line — never a bare prose mention — compared against every **other** release-date claim in the repo, not a single external authority. A claim more than one day off the **majority** date (ties break to the later date) is flagged, and the finding names both disagreeing sites. Vacuous with fewer than 2 claims — nothing to compare. | | Count prose in structured lines | A stale count — **singular or plural** — inside one of four line shapes. Free prose is never checked. | -**The release-date check is vacuous when its authority is unusable** — no tag for the current -version (normal on a feature branch), or a tag date that will not parse. It reports nothing rather -than everything: an empty accept-window would match no claim at all, turning one bad input into a -repo-wide false-positive storm. See [ADR-007](../adr/ADR-007-pattern-scoped-prose-staleness-gating.md). - The four line shapes: | Shape | Matches | |-------|---------| -| `version-box` | lines inside a `┌` … `└` box-drawing block | -| `tldr` | a line containing `TL;DR` | +| `version-box` | lines inside a `┌` … `└` box-drawing block — closes at the first line with no box-drawing character at all, even without a matching `└`, so a truncated box can't leak into later shapes | +| `tldr` | a line that opens with `TL;DR` (after optional blockquote/emphasis markers) — not merely one that mentions it, so a doc describing this bug isn't itself flagged for the example it quotes | | `count-summary` | the bolded badge line, e.g. `**48 commands** \| **41 skills**` | | `structure-table` | a table row whose first cell is a counted directory, e.g. `` \| `agents/` \| `` — compared only against the type that cell names | @@ -95,7 +93,6 @@ positive control; a check without one is a rejected change. | Variable | Effect | |----------|--------| | `CRAFT_EXPECTED_CMDS` / `_SKILLS` / `_AGENTS` | Declare expected counts instead of deriving them from `commands/`, `skills/`, `agents/`. Lets a fixture skip materializing 48 command files. | -| `CRAFT_RELEASE_DATE` | Supply the tag date instead of reading git, keeping the fixture suite hermetic. | Unset on every production path — the derived values are what actually run. diff --git a/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md index 88073d681..6fc819091 100644 --- a/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md +++ b/docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md @@ -94,6 +94,21 @@ a narrative count about a different plugin (`kept shipping **0 skills**`). Shape narrows *where* to look; the floor is still what separates a total from a subtotal. All five are now `falsepos/` fixtures. +### Superseded by the prose-check hardening (2026-08-15) + +This SPEC's check-1 description above (E2, and the "git tag local date" line in the Acceptance +Criteria) describes the **as-shipped-then** design, not the current one. A follow-up review found +9 more defects in the shipped checks (2 HIGH); see +[`SPEC-prose-check-hardening-2026-08-15.md`](SPEC-prose-check-hardening-2026-08-15.md) and its +[`GRILL`](GRILL-prose-check-hardening-2026-08-15.md). The load-bearing change: check 1 no longer +resolves an authority from the git tag at all (D1) — the tag doesn't exist at either point the +check actually runs, so a tag-based check never fired as a real gate. It now compares every +release-date claim for the current version against every other one, with the one-day window +surviving as the agreement tolerance between claims rather than a tolerance against a tag. Left +here rather than rewritten in place, per this repo's own "positive controls are rewritten, not +deleted, when their model goes obsolete" convention (that hardening's D8) — the checkboxes above +are a historical record of what E2 decided at the time, not a live spec. + --- ## Review Outcome (adversarial review, 2026-08-07) diff --git a/docs/specs/SPEC-prose-check-hardening-2026-08-15.md b/docs/specs/SPEC-prose-check-hardening-2026-08-15.md index b70b439f9..ce0628478 100644 --- a/docs/specs/SPEC-prose-check-hardening-2026-08-15.md +++ b/docs/specs/SPEC-prose-check-hardening-2026-08-15.md @@ -96,18 +96,31 @@ sibling repos; any new script or dependency (D1/D4 of the parent SPEC still hold ## Acceptance criteria -- [ ] Every finding F1–F9 has a fixture or test that **fails before the fix and passes after**, - verified by planted mutation — not merely a passing test after the change. -- [ ] A hyphenated-compound corpus (`command-line`, `agent-facing`, `skill-authoring`, - `agents-only`) produces zero findings, and the corresponding real counts still do. -- [ ] `[e]xclude` round-trips: writing the entry actually suppresses the finding on the next run, - asserted end-to-end rather than by inspecting the written line. -- [ ] The severity decision (D5) is recorded in ADR-007 with the **verified** consumer behavior, +- [x] Every finding F1–F9 has a fixture or test that **fails before the fix and passes after**, + verified by planted mutation — not merely a passing test after the change. Every phase's + fix was reverted and re-tested before restoring (F1/F2/F4/F6/F8 confirmed via source + revert; F3/F7 confirmed by reverting D1/D6 together, since they share one redesign; + D2/D11's severity confirmed by reverting the severity string itself). This pass also + caught the broad, unscoped plural-only count scan sharing F1's hyphen-boundary bug + (`"7 agents-only"` matched as `"7 agents"`) — not separately enumerated as an F-number, + surfaced by this criterion's own hyphenated-compound corpus, fixed alongside F1/F2. +- [x] A hyphenated-compound corpus (`command-line`, `agent-facing`, `skill-authoring`, + `agents-only`) produces zero findings, and the corresponding real counts still do — + `falsepos/hyphenated-compound-tldr.md`. +- [x] `[e]xclude` round-trips: writing the entry actually suppresses the finding on the next run, + asserted end-to-end rather than by inspecting the written line — + `test_exclude_round_trips_end_to_end` drives the real `[e]` keystroke through a pty. +- [x] The severity decision (D2, GRILL numbering — the SPEC's own D5 above referred to the + pre-grill options table) is recorded in ADR-007 with the **verified** consumer behavior, and the incorrect claim is removed, not softened. -- [ ] Check 1 demonstrably fires in the environment D6/D7 select — proven by running it there, - not by reasoning that it should. -- [ ] `docs-staleness-check.sh` stays under the `test_pre_release_check_runs` 30s budget. -- [ ] Live repo stays GREEN. +- [x] Check 1 demonstrably fires — proven by running it, not by reasoning that it should. D1 + dropped the environment dependency this criterion originally asked about (no tag, no CI + availability concern left to prove); demonstrated instead by injecting a real mismatch + into `docs/REFCARD.md`'s live date and confirming check 1 caught it + (`release date '2020-01-01' ... disagrees with other claims (majority: 2026-08-07)`) + before reverting the injection. +- [x] `docs-staleness-check.sh` stays under the `test_pre_release_check_runs` 30s budget — ~11s. +- [x] Live repo stays GREEN. ## Test plan diff --git a/scripts/docs-staleness-check.sh b/scripts/docs-staleness-check.sh index dac771fc4..49fe6f7c8 100755 --- a/scripts/docs-staleness-check.sh +++ b/scripts/docs-staleness-check.sh @@ -161,6 +161,16 @@ load_counts() { # --------------------------------------------------------------------------- # Prose staleness helpers (SPEC-doc-staleness-prose-gaps-2026-08-07) # --------------------------------------------------------------------------- +# The boundary after a matched noun ("command", "agents", ...): whitespace, +# closing punctuation, or end of line. NOT `[^a-z]`, which also accepts `-` — +# that let "command-line" and "agent-facing" read as counts (F1), and let +# `[f]ix` then rewrite "30 command-line entry points" into +# "48 command-line entry points" (F2). One constant, shared by the awk +# pre-filter and the bash matcher below (and the strip in phase7 that trims it +# back off a matched span) — tuning them independently caused a 4x perf +# regression once already (see emit_shaped_lines' header note). +PROSE_COUNT_TRAILER='([])} .,;:!?*]|$)' + # emit_shaped_lines prints "file:lineno:shape:content" for lines sitting in one # of four structured shapes. Free prose is deliberately NOT emitted: an # adversarial review of a blanket "N agents" search found 90+ false positives @@ -176,23 +186,34 @@ load_counts() { # single invocation. emit_shaped_lines() { [[ $# -eq 0 ]] && return 0 - awk ' + awk -v trailer="$PROSE_COUNT_TRAILER" ' # Only lines that actually carry a count are worth emitting. Without this # filter every line of every box block is emitted (~3500 across docs/), # and the bash loop downstream spends ~6 grep subprocesses on each — the # other half of the 4x slowdown, alongside per-file awk spawns. function hascount(s) { - return s ~ /[0-9]+ (specialized )?(commands?|skills?|agents?)([^a-z]|$)/ + return s ~ ("[0-9]+ (specialized )?(commands?|skills?|agents?)" trailer) } FNR == 1 { inbox = 0 } # "version-box": a quick-reference/version box drawn with box characters. + # An unclosed box (no matching bottom border, e.g. truncated by a + # missing fence) used to leak `inbox` mode into the rest of the file, + # so a later structure-table row got measured as a version-box line + # instead and lost its own type restriction (F8). Closed on the FIRST + # line carrying no box-drawing character at all, not only on an + # explicit `└` -- that line falls through to the shape checks below + # instead of being consumed as part of the box. /┌/ { inbox = 1 } inbox { - if (hascount($0)) print FILENAME ":" FNR ":version-box:" $0 - if ($0 ~ /└/) inbox = 0 - next + if ($0 !~ /[┌┐└┘│├┤─]/) { + inbox = 0 + } else { + if (hascount($0)) print FILENAME ":" FNR ":version-box:" $0 + if ($0 ~ /└/) inbox = 0 + next + } } # "tldr": a line that OPENS with TL;DR (after optional blockquote or @@ -225,16 +246,25 @@ emit_shaped_lines() { # NEWS.md's "## v4.5.0 ..." heading followed by "**Released:** 2026-08-08". # Windowed so historical entries further down the same file are not compared # against the current version's date. +# +# The window opens ONLY when the version token sits in a markdown heading or a +# version-box content line (D6/F3). Proximity alone is not enough: an upgrade +# guide saying "Upgrading to v4.5.0 is a drop-in change" is prose, not a claim +# site, and used to open the window anyway, collecting whatever date happened +# to sit within 4 lines of it as though it were this version's release date. +# # Single awk pass over the whole file list, for the same reason as above. emit_release_date_claims() { [[ $# -eq 0 ]] && return 0 awk -v ver="v${CURRENT_VERSION}" ' FNR == 1 { win = 0 } + function is_heading(s) { return s ~ /^#+[[:space:]]/ } + function is_box_line(s) { return s ~ /^[[:space:]]*│/ } # 5, not 4: the win-- below runs on the version line itself, so win=4 # scanned that line plus only 3 more -- one short of the 4 following # lines this check documents, silently missing a layout as ordinary as # heading / blank / Type / blank / Released. - index($0, ver) { win = 5 } + (is_heading($0) || is_box_line($0)) && index($0, ver) { win = 5 } win > 0 { if (match($0, /[Rr]eleased[^0-9]{0,12}[0-9]{4}-[0-9]{2}-[0-9]{2}/)) { s = substr($0, RSTART, RLENGTH) @@ -246,17 +276,36 @@ emit_release_date_claims() { ' "$@" 2>/dev/null || true } -# Authority for the release-date check is the current version's git tag date, -# NOT .STATUS's release_date:. The two legitimately disagree by a day whenever a -# release publishes across the UTC boundary (v4.5.0: tag 2026-08-07, GitHub -# release 2026-08-08T03:44Z), so a claim within one day of the tag is not -# staleness. CRAFT_RELEASE_DATE overrides for hermetic fixtures. -resolve_release_date() { - if [[ -n "${CRAFT_RELEASE_DATE:-}" ]]; then - echo "$CRAFT_RELEASE_DATE" - return - fi - git for-each-ref --format='%(creatordate:short)' "refs/tags/v${CURRENT_VERSION}" 2>/dev/null | head -1 +# D1: check 1 has no external authority. The git tag doesn't exist at either +# point this check actually runs -- the release pipeline writes NEWS/REFCARD +# dates in Step 3b, *before* Step 8 creates the tag, and CI's checkout has no +# fetch-tags -- so a tag-based check only ever fired on a developer machine +# that had already pulled it, which is not a gate (F7). Instead, every +# release-date claim for the current version is compared against every other +# one; the one-day tolerance survives as the agreement window between claims, +# absorbing the UTC-boundary case that motivated it (v4.5.0: NEWS.md says +# 2026-08-08, REFCARD.md says 2026-08-07 -- both are correct). +# +# Given N date strings, returns the one with the most occurrences. Ties break +# to the LATER date, not the earlier one: a stale-release-date bug is a +# forgotten update, so the wrong claim is normally older than the correct one, +# never newer. (With exactly two claims -- the common case, one NEWS.md entry +# and one REFCARD.md box -- every tie is 1-1, so this tie-break is what +# decides which claim is "authority" and which is "stale".) Plain nested +# loop, not an associative array -- this script supports bash 3.2. +majority_date() { + local best="" best_count=0 d1 d2 count + for d1 in "$@"; do + count=0 + for d2 in "$@"; do + [[ "$d2" == "$d1" ]] && count=$((count + 1)) + done + if (( count > best_count )) || { (( count == best_count )) && [[ -n "$best" ]] && [[ "$d1" > "$best" ]]; }; then + best="$d1" + best_count="$count" + fi + done + echo "$best" } # Applies one `s/PATTERN/REPLACEMENT/flags` substitution to one line of one file. @@ -311,13 +360,14 @@ print("true") PYEOF } -# The acceptable window is three literal dates (tag-1, tag, tag+1), computed -# ONCE with a single python3 call rather than a python3 spawn per claim. Callers -# then do a plain string comparison. python3 is already a hard dependency here -# (load_counts reads plugin.json with it) — no new dependency. +# The acceptable window is three literal dates (authority-1, authority, +# authority+1), computed ONCE with a single python3 call rather than a python3 +# spawn per claim. Callers then do a plain string comparison. python3 is +# already a hard dependency here (load_counts reads plugin.json with it) — no +# new dependency. ACCEPTED_RELEASE_DATES="" compute_release_date_window() { - local tag_date="$1" + local authority_date="$1" ACCEPTED_RELEASE_DATES=$(python3 -c " import sys, datetime try: @@ -325,7 +375,7 @@ try: except ValueError: sys.exit(0) print(' '.join(str(d + datetime.timedelta(days=n)) for n in (-1, 0, 1))) -" "$tag_date" 2>/dev/null) +" "$authority_date" 2>/dev/null) } release_date_accepted() { case " ${ACCEPTED_RELEASE_DATES} " in @@ -365,15 +415,39 @@ TOTAL_WARNINGS=0 TOTAL_ERRORS=0 TOTAL_FIXED=0 +# Renders a finding's location the way every consumer has always seen it — +# `path:lineno`, or the bare path when there is no locator. The split above is +# internal; this keeps the JSON contract unchanged. +finding_location() { + local file="$1" locator="$2" + if [[ -n "$locator" ]]; then + echo "${file}:${locator}" + else + echo "$file" + fi +} + +# The finding record carries the file path and the location *within* it as two +# fields, never one glued string. They were glued (`path:lineno`) until +# 2026-08-15, which made pass 2's `[e]xclude` a silent no-op for every Phase 7 +# and Phase 9 finding: `is_pattern_excluded` splits the exclusion entry on its +# first colon, so an entry of `docs/x.md:3:30 command` could never match. It +# printed "Excluded" and the finding returned on the next run. +# +# `locator` is a location, not necessarily a line number: Phase 9's +# site_description finding carries `site_description`, and coverage findings +# carry none at all. Nothing may assume it is numeric — pass 1 gets its line +# number from fix_detail, which is a separate payload. add_finding() { local phase="$1" local severity="$2" # error | warning local file="$3" - local message="$4" - local fixable="${5:-false}" # true if auto-fixable - local fix_detail="${6:-}" # sed command or description + local locator="$4" # line number, named locator, or "" — never glued into file + local message="$5" + local fixable="${6:-false}" # true if auto-fixable + local fix_detail="${7:-}" # sed command or description - local entry="${severity}|${file}|${message}|${fixable}|${fix_detail}" + local entry="${severity}|${file}|${locator}|${message}|${fixable}|${fix_detail}" case "$phase" in 6) PHASE6_FINDINGS+=("$entry") ;; @@ -459,7 +533,7 @@ phase6_nav_completeness() { # Check if this file appears in nav if ! echo "$nav_files" | grep -qF "$rel_path"; then - add_finding 6 "warning" "$file" "Not in mkdocs.yml nav" + add_finding 6 "warning" "$file" "" "Not in mkdocs.yml nav" issues=$((issues + 1)) fi done < <(find docs -name "*.md" -not -path "*/\.*" 2>/dev/null | sort) @@ -467,7 +541,7 @@ phase6_nav_completeness() { # Check for nav entries pointing to missing files while IFS= read -r nav_entry; do if [[ ! -f "docs/$nav_entry" ]]; then - add_finding 6 "error" "mkdocs.yml" "Nav entry 'docs/$nav_entry' — file missing" + add_finding 6 "error" "mkdocs.yml" "" "Nav entry 'docs/$nav_entry' — file missing" issues=$((issues + 1)) fi done < <(echo "$nav_files") @@ -490,10 +564,16 @@ phase7_count_consistency() { # (small numbers like "7 commands" in prose are not total counts) local count_types=("commands" "skills" "agents") local expected_values=("$EXPECTED_CMDS" "$EXPECTED_SKILLS" "$EXPECTED_AGENTS") - # Minimum count to consider: ~40% of expected value (catches old totals, skips prose) + # Minimum count to consider: ~40% of expected value (catches old totals, + # skips prose), floored at 2. Agents' 40% is `2 * 40 / 100 == 0`, so + # without the floor the guard this comment and ADR-007 describe does not + # exist for the smallest count type — every "N agent(s)" mention, however + # small, read as a stale-total candidate (F6). local min_thresholds=() for exp in "${expected_values[@]}"; do - min_thresholds+=("$((exp * 40 / 100))") + local floor=$((exp * 40 / 100)) + (( floor < 2 )) && floor=2 + min_thresholds+=("$floor") done for i in "${!count_types[@]}"; do @@ -501,7 +581,15 @@ phase7_count_consistency() { local expected="${expected_values[$i]}" local min_count="${min_thresholds[$i]}" - # grep for "N commands/skills/agents" patterns in docs + # grep for "N commands/skills/agents" patterns in docs. One regex for + # detection AND the fix anchor -- same reasoning as check 2's span- + # anchored fix (D3): the trailing `\b` this scan used before accepted + # `-` as a boundary the same way check 2's did, so "7 agents-only" + # read as "7 agents" (expected 2). This scan's findings are + # auto-fixable, unlike check 2's, so a boundary-unsafe substitution + # here is worse than F2 -- it can silently corrupt a doc under + # `--fix --non-interactive` with no human in the loop. PROSE_COUNT_TRAILER + # is the same boundary class the shape-scoped check uses. while IFS= read -r match; do [[ -z "$match" ]] && continue local file="${match%%:*}" @@ -512,10 +600,12 @@ phase7_count_consistency() { # Skip excluded files is_file_excluded "$file" && continue - # Extract the number + local full noun + full=$(echo "$content" \ + | grep -oE "[0-9]+ ${ctype}${PROSE_COUNT_TRAILER}" | head -1) + [[ -z "$full" ]] && continue local found_count - found_count=$(echo "$content" | grep -oE "[0-9]+ ${ctype}" | head -1 | grep -oE '[0-9]+') - [[ -z "$found_count" ]] && continue + found_count=$(echo "$full" | grep -oE '^[0-9]+') # Skip if count matches [[ "$found_count" == "$expected" ]] && continue @@ -531,16 +621,21 @@ phase7_count_consistency() { # Already reported by the line-shape scan below? Report once. count_key_reported "${file}:${lineno}:${ctype}" && continue + # Trim the trailing boundary char the trailer group consumed, so + # the fix substitutes the exact matched text ("48 commands"), not + # a re-derived \b-bounded pattern that could match elsewhere. + noun=$(echo "$full" | sed -E 's/[])} .,;:!?*]$//') + # Determine if auto-fixable (simple count swap) local fixable="true" - local fix_detail="${file}:${lineno}:s/\b${found_count} ${ctype}\b/${expected} ${ctype}/g" + local fix_detail="${file}:${lineno}:s/${noun}/${expected} ${ctype}/" - add_finding 7 "warning" "${file}:${lineno}" \ + add_finding 7 "warning" "$file" "$lineno" \ "'${found_count} ${ctype}' (expected ${expected})" \ "$fixable" "$fix_detail" mark_count_key "${file}:${lineno}:${ctype}" issues=$((issues + 1)) - done < <(grep -rnE "\b[0-9]+ ${ctype}\b" docs/ CLAUDE.md README.md --include="*.md" 2>/dev/null || true) + done < <(grep -rnE "\b[0-9]+ ${ctype}${PROSE_COUNT_TRAILER}" docs/ CLAUDE.md README.md --include="*.md" 2>/dev/null || true) done # ----------------------------------------------------------------------- @@ -581,10 +676,20 @@ phase7_count_consistency() { echo "$scontent" | grep -qE "^\|[[:space:]]*\`${ctype2}/\`" || continue fi - found=$(echo "$scontent" \ - | grep -oE "[0-9]+ (specialized )?(${ctype2}|${singular})([^a-z]|$)" \ - | grep -oE '^[0-9]+' | head -1) - [[ -z "$found" ]] && continue + # One regex for detection AND the fix anchor. F2 was a second, + # looser regex used only to build `noun` (no trailer at all) — + # it re-matched "command" inside "command-line" even after the + # detection regex's own trailer would have rejected the line, + # so [f]ix rewrote "30 command-line entry points" into + # "48 command-line entry points". Capturing one span and + # deriving both the report and the substitution from it makes + # that drift impossible. + local full + full=$(echo "$scontent" \ + | grep -oE "[0-9]+ (specialized )?(${ctype2}|${singular})${PROSE_COUNT_TRAILER}" \ + | head -1) + [[ -z "$full" ]] && continue + found=$(echo "$full" | grep -oE '^[0-9]+') [[ "$found" == "$expected2" ]] && continue # Same 40%-of-expected floor the broad scan applies. Structured @@ -599,22 +704,24 @@ phase7_count_consistency() { is_pattern_excluded "$sfile" "${found} ${ctype2}" && continue is_pattern_excluded "$sfile" "${found} ${singular}" && continue + # Trim the single trailing boundary char the trailer group + # consumed (space/closing punctuation) back off, so the + # reported/substituted noun is "8 agent", not "8 agent ". `$` + # in the trailer consumes nothing, so an end-of-line match is + # already bare and this is a no-op for it. + local noun + noun=$(echo "$full" | sed -E 's/[])} .,;:!?*]$//') + # Not auto-fixable: the surrounding prose ("8 agent definitions") # is hand-authored, so a blind count swap can produce grammatical # nonsense. Routed to the interactive pass instead. - # Report the noun as it actually appears on the line, not a - # normalized form — the reader has to find this string. - local noun - noun=$(echo "$scontent" \ - | grep -oE "${found} (specialized )?(${ctype2}|${singular})" | head -1) - noun="${noun:-${found} ${ctype2}}" - + # # Routed to pass 2 (uncertain), not pass 1: the surrounding # prose is hand-authored, so a human should see the line before # the number changes under it. The fix_detail is still a real # substitution so that confirming it actually edits the file — # it swaps only the digits, leaving "agent definitions" intact. - add_finding 7 "warning" "${sfile}:${slineno}" \ + add_finding 7 "warning" "$sfile" "$slineno" \ "prose[${shape}]: '${noun}' (expected ${expected2})" \ "uncertain" "${sfile}:${slineno}:s/${noun}/${expected2}${noun#"$found"}/" mark_count_key "${sfile}:${slineno}:${ctype2}" @@ -623,32 +730,60 @@ phase7_count_consistency() { done < <(emit_shaped_lines "${prose_files[@]+"${prose_files[@]}"}") # ----------------------------------------------------------------------- - # Check 1 — release-date claims tied to the current version. - # Vacuous (skipped, not failed) when the current version has no tag yet, - # which is the normal state on a feature branch before release. + # Check 1 — release-date claims agree with each other for the current + # version. No external authority (D1): the tag doesn't exist at either + # point this check runs (see the comment on majority_date). Vacuous + # (skipped, not failed) with 0 or 1 claims -- there is nothing to compare + # a lone claim against, which is the normal state on a feature branch + # before release, or when only one doc in the repo names a release date at + # all. Accepted cost: a date that is uniformly wrong in every file agrees + # with itself and is never caught. # ----------------------------------------------------------------------- - local tag_date claim cfile crest clineno cdate - tag_date="$(resolve_release_date)" - compute_release_date_window "$tag_date" - # Skip when the authority is missing OR unparseable. A broken authority must - # make this check vacuous, never universal: with an empty accept-window every - # release-date claim in the repo fails at once, turning one bad input into a - # repo-wide false-positive storm. Same posture as the no-tag case (normal on - # a feature branch before release). - if [[ -n "$ACCEPTED_RELEASE_DATES" ]]; then - while IFS= read -r claim; do - [[ -z "$claim" ]] && continue - cfile="${claim%%:*}"; crest="${claim#*:}" - clineno="${crest%%:*}"; cdate="${crest#*:}" - - release_date_accepted "$cdate" && continue - is_pattern_excluded "$cfile" "$cdate" && continue - - add_finding 7 "warning" "${cfile}:${clineno}" \ - "release date '${cdate}' for v${CURRENT_VERSION} (tag: ${tag_date})" \ - "uncertain" "${cfile}:${clineno}:s/${cdate}/${tag_date}/" - issues=$((issues + 1)) - done < <(emit_release_date_claims "${prose_files[@]+"${prose_files[@]}"}") + local claim cfile crest clineno cdate + local -a claim_files=() claim_lines=() claim_dates=() + while IFS= read -r claim; do + [[ -z "$claim" ]] && continue + cfile="${claim%%:*}"; crest="${claim#*:}" + clineno="${crest%%:*}"; cdate="${crest#*:}" + is_file_excluded "$cfile" && continue + claim_files+=("$cfile") + claim_lines+=("$clineno") + claim_dates+=("$cdate") + done < <(emit_release_date_claims "${prose_files[@]+"${prose_files[@]}"}") + + if [[ "${#claim_dates[@]}" -ge 2 ]]; then + local authority + authority="$(majority_date "${claim_dates[@]}")" + compute_release_date_window "$authority" + # Empty window means the majority date itself failed to parse -- + # cannot happen in practice (it came from the [0-9]{4}-[0-9]{2}-[0-9]{2} + # regex that fed emit_release_date_claims), but a broken authority must + # make this vacuous, never universal, same posture as the old + # missing-tag case: an empty accept-window would otherwise fail every + # claim in the repo at once. + if [[ -n "$ACCEPTED_RELEASE_DATES" ]]; then + for i in "${!claim_dates[@]}"; do + cdate="${claim_dates[$i]}" + release_date_accepted "$cdate" && continue + cfile="${claim_files[$i]}"; clineno="${claim_lines[$i]}" + is_pattern_excluded "$cfile" "$cdate" && continue + + # error (D2, promoted per D11): shipped as "warning" while + # the redesign (D1, D6) itself was unproven -- a passing + # unit suite written by the same author in the same sitting + # isn't evidence. Promoted once a live-repo run came back + # clean (0 findings) across every tracked doc AND both real + # claim sites (docs/NEWS.md, docs/REFCARD.md), plus a + # transcript of the check actually firing: injecting + # 2020-01-01 into docs/REFCARD.md:7 produced "release date + # '2020-01-01' for v4.5.0 disagrees with other claims + # (majority: 2026-08-07)", reverted after confirming. + add_finding 7 "error" "$cfile" "$clineno" \ + "release date '${cdate}' for v${CURRENT_VERSION} disagrees with other claims (majority: ${authority})" \ + "uncertain" "${cfile}:${clineno}:s/${cdate}/${authority}/" + issues=$((issues + 1)) + done + fi fi print_phase_status "$issues" @@ -699,7 +834,7 @@ phase8_skill_agent_coverage() { fi if ! $documented; then - add_finding 8 "warning" "$cmd_file" \ + add_finding 8 "warning" "$cmd_file" "" \ "Command '${cmd_name}' not in docs (commands.md, docs/commands/)" \ "uncertain" "command:${cmd_file}" issues=$((issues + 1)) @@ -721,7 +856,7 @@ phase8_skill_agent_coverage() { if [[ -f "$skill_file" ]]; then desc=$(sed -n '/^---$/,/^---$/{ /^description:/s/^description:[[:space:]]*//p; }' "$skill_file" 2>/dev/null | head -1) fi - add_finding 8 "warning" "$skill_file" \ + add_finding 8 "warning" "$skill_file" "" \ "Not documented in $skills_doc" \ "uncertain" "skill:${skill_file}:${desc}" issues=$((issues + 1)) @@ -736,7 +871,7 @@ phase8_skill_agent_coverage() { local agent_name agent_name=$(basename "$(dirname "$agent_file")")/$(basename "$agent_file" .md) if ! grep -q "$agent_file\|$agent_name" "$skills_doc" 2>/dev/null; then - add_finding 8 "warning" "$agent_file" \ + add_finding 8 "warning" "$agent_file" "" \ "Not documented in $skills_doc" \ "uncertain" "agent:${agent_file}" issues=$((issues + 1)) @@ -761,7 +896,7 @@ phase8_skill_agent_coverage() { # Map block→error, warn→warning for staleness check convention local sev="warning" [[ "$severity_raw" == "block" ]] && sev="error" - add_finding 8 "$sev" "commands/${cmd//:///}.md" \ + add_finding 8 "$sev" "commands/${cmd//:///}.md" "" \ "$message" "uncertain" "doc-coverage:${surface}:${cmd}" issues=$((issues + 1)) done < <(echo "$cov_json" | grep '"cmd"' || true) @@ -800,7 +935,7 @@ phase9_cross_doc_freshness() { if [[ "$found_ver" != "$CURRENT_VERSION" ]]; then local fixable="true" local fix_detail="${file}:${lineno}:s/${found_ver}/${CURRENT_VERSION}/g" - add_finding 9 "warning" "${file}:${lineno}" \ + add_finding 9 "warning" "$file" "$lineno" \ "Version '${found_ver}' (current: ${CURRENT_VERSION})" \ "$fixable" "$fix_detail" issues=$((issues + 1)) @@ -818,7 +953,7 @@ phase9_cross_doc_freshness() { if [[ -n "$site_ver" ]]; then site_ver="${site_ver#v}" if [[ "$site_ver" != "$CURRENT_VERSION" ]]; then - add_finding 9 "warning" "mkdocs.yml:site_description" \ + add_finding 9 "warning" "mkdocs.yml" "site_description" \ "References v${site_ver} (current: v${CURRENT_VERSION})" \ "uncertain" "" issues=$((issues + 1)) @@ -851,7 +986,7 @@ phase9_cross_doc_freshness() { if $stale; then is_pattern_excluded "$file" "$content" && continue - add_finding 9 "warning" "${file}:${lineno}" \ + add_finding 9 "warning" "$file" "$lineno" \ "Stale counts in summary (skills: ${see_skills:-?}/${EXPECTED_SKILLS}, agents: ${see_agents:-?}/${EXPECTED_AGENTS})" \ "uncertain" "" issues=$((issues + 1)) @@ -871,7 +1006,7 @@ phase9_cross_doc_freshness() { [[ -z "$found_count" ]] && continue [[ "$found_count" == "$EXPECTED_CMDS" ]] && continue is_pattern_excluded "$file" "${found_count} commands" && continue - add_finding 9 "warning" "${file}:${lineno}" \ + add_finding 9 "warning" "$file" "$lineno" \ "Architecture doc: '${found_count} commands' (current: ${EXPECTED_CMDS})" \ "uncertain" "" issues=$((issues + 1)) @@ -899,7 +1034,9 @@ pass1_auto_fix() { fi for item in "${FIXABLE_ITEMS[@]}"; do - # Parse: phase|severity|file|message|fixable|fix_detail + # Parse: phase|severity|file|locator|message|fixable|fix_detail + # fix_detail is a separate payload (path:lineno:sed_cmd), not derived + # from the record's own file/locator fields. local fix_detail="${item##*|}" local file="${fix_detail%%:*}" local rest="${fix_detail#*:}" @@ -955,10 +1092,12 @@ pass2_interactive_review() { local idx=0 for item in "${UNCERTAIN_ITEMS[@]}"; do idx=$((idx + 1)) - # Parse: phase|severity|file|message|fixable|fix_detail - IFS='|' read -r phase severity file message fixable fix_detail <<< "$item" + # Parse: phase|severity|file|locator|message|fixable|fix_detail + IFS='|' read -r phase severity file locator message fixable fix_detail <<< "$item" + local location + location="$(finding_location "$file" "$locator")" - echo -e "[${idx}/${#UNCERTAIN_ITEMS[@]}] ${YELLOW}${file}${NC}" + echo -e "[${idx}/${#UNCERTAIN_ITEMS[@]}] ${YELLOW}${location}${NC}" echo " ${message}" local response="" @@ -992,7 +1131,10 @@ pass2_interactive_review() { echo " -> Skipped" ;; e) - # Add to exclusions file + # Add to exclusions file. $file is the bare path now that the + # locator is a separate field (D4) — the written entry is + # path:pattern, matching what is_pattern_excluded parses, not + # path:lineno:pattern which could never match. local excl_entry="$file" # For pattern items, extract the pattern portion if [[ "$message" == *"'"*"'"* ]]; then @@ -1210,13 +1352,18 @@ findings_to_json() { echo -n "[" for (( _i=0; _i<_count; _i++ )); do eval "entry=\${${_arr_name}[$_i]}" - IFS='|' read -r severity file message fixable fix_detail <<< "$entry" + IFS='|' read -r severity file locator message fixable fix_detail <<< "$entry" + # Render the location the way every consumer has always seen it — + # path:locator, or the bare path — so the JSON contract is unchanged + # even though the record now carries the two as separate fields. + local location + location="$(finding_location "$file" "$locator")" # Escape JSON strings - file=$(echo "$file" | sed 's/\\/\\\\/g;s/"/\\"/g') + location=$(echo "$location" | sed 's/\\/\\\\/g;s/"/\\"/g') message=$(echo "$message" | sed 's/\\/\\\\/g;s/"/\\"/g') if ! $first; then echo -n ","; fi first=false - echo -n "{\"severity\":\"${severity}\",\"file\":\"${file}\",\"message\":\"${message}\"}" + echo -n "{\"severity\":\"${severity}\",\"file\":\"${location}\",\"message\":\"${message}\"}" done echo -n "]" } diff --git a/skills/release/references/pipeline-steps.md b/skills/release/references/pipeline-steps.md index d27d18b74..5058f3384 100644 --- a/skills/release/references/pipeline-steps.md +++ b/skills/release/references/pipeline-steps.md @@ -122,11 +122,20 @@ After `bump-version.sh` handles mechanical version substitution, update these fi | `VERSION-HISTORY.md` | Insert new version section with highlights | | `README.md` | Update release title line (if present) | | `docs/index.md` | Update `!!! info` box title and description text | -| `docs/REFCARD.md` | Update summary line ~11 title text (after the version) | +| `docs/REFCARD.md` | Update summary line ~11 title text (after the version); update the version box's release date (line ~7) | +| `docs/NEWS.md` | Insert new `## vX.Y.Z` entry with a `**Released:** YYYY-MM-DD` date — not template-generated, added by hand | | `mkdocs.yml` | Update `site_description` tagline after "adds" to describe new release | | `commands/hub.md` | Update version in banner template, test count, skill count | | `docs/commands/hub.md` | Same updates as `commands/hub.md` (published copy) | +**Release-date convention (D9, `GRILL-prose-check-hardening-2026-08-15.md`):** `docs/NEWS.md`'s +`**Released:**` date and `docs/REFCARD.md`'s box date must be the **same date** — the git tag's +local date, not the GitHub API's UTC release timestamp (the two differ whenever a release +publishes across the UTC boundary, e.g. v4.5.0: tag `2026-08-07` local, GitHub release +`2026-08-08T03:44Z`). `docs-staleness-check.sh`'s check 1 (`ADR-007`) compares these two claims +against each other and tolerates a one-day gap for the UTC-boundary case — using the same date at +both sites keeps that tolerance as headroom instead of load-bearing. + **Key distinction:** Step 3 handles mechanical `X.Y.Z` substitution. Step 3b handles semantic content that requires the release title and human judgment. For **other project types**, update manually: diff --git a/tests/fixtures/prose-staleness/clean/release-date-companion.md b/tests/fixtures/prose-staleness/clean/release-date-companion.md new file mode 100644 index 000000000..5d9c4505a --- /dev/null +++ b/tests/fixtures/prose-staleness/clean/release-date-companion.md @@ -0,0 +1,11 @@ + + +# News + +## v4.5.0 — repo-triage skill + ecosystem-tool tutorials + +**Released:** 2026-08-07 · **Type:** Minor diff --git a/tests/fixtures/prose-staleness/falsepos/hyphenated-compound-tldr.md b/tests/fixtures/prose-staleness/falsepos/hyphenated-compound-tldr.md new file mode 100644 index 000000000..90fc4bc2b --- /dev/null +++ b/tests/fixtures/prose-staleness/falsepos/hyphenated-compound-tldr.md @@ -0,0 +1,11 @@ + + +# Architecture + +**TL;DR** — craft exposes 30 command-line entry points across 3 agent-facing surfaces, documented via 5 skill-authoring guides and 7 agents-only workflows. diff --git a/tests/fixtures/prose-staleness/falsepos/release-date-prose-mention.md b/tests/fixtures/prose-staleness/falsepos/release-date-prose-mention.md new file mode 100644 index 000000000..97de1343e --- /dev/null +++ b/tests/fixtures/prose-staleness/falsepos/release-date-prose-mention.md @@ -0,0 +1,12 @@ + + +# Upgrade Guide + +Upgrading to v4.5.0 is a drop-in change. + +**Released:** 2020-01-01 diff --git a/tests/fixtures/prose-staleness/falsepos/tldr-agent-subset-count.md b/tests/fixtures/prose-staleness/falsepos/tldr-agent-subset-count.md new file mode 100644 index 000000000..2b8141dd6 --- /dev/null +++ b/tests/fixtures/prose-staleness/falsepos/tldr-agent-subset-count.md @@ -0,0 +1,10 @@ + + +# Migration Notes + +**TL;DR** — this patch only touches 1 agent config file; the other agent is untouched. diff --git a/tests/fixtures/prose-staleness/falsepos/unclosed-box-leaks-into-table.md b/tests/fixtures/prose-staleness/falsepos/unclosed-box-leaks-into-table.md new file mode 100644 index 000000000..d10302083 --- /dev/null +++ b/tests/fixtures/prose-staleness/falsepos/unclosed-box-leaks-into-table.md @@ -0,0 +1,18 @@ + + +# Notes + +```text +┌ untouched legacy snapshot, never finished +``` + +| Directory | Purpose | +|-----------|---------| +| `commands/` | 48 commands, once listed 3 agent placeholders here too | diff --git a/tests/test_docs_staleness_prose.py b/tests/test_docs_staleness_prose.py index 0da6b0f35..b0e0b553d 100644 --- a/tests/test_docs_staleness_prose.py +++ b/tests/test_docs_staleness_prose.py @@ -14,10 +14,12 @@ * ``falsepos/``— content that LOOKS like a stale count but is not, drawn from real pages an earlier build of these checks wrongly flagged -Hermetic by construction: no network, no git-history walk, no TTY. The one git -read check 1 needs (the version's tag date) is injected via ``CRAFT_RELEASE_DATE``, -and the expected counts via ``CRAFT_EXPECTED_*``, so the fixtures do not have to -materialize 48 command files apiece. +Hermetic by construction: no network, no git-history walk, no TTY. Expected +counts are injected via ``CRAFT_EXPECTED_*``, so the fixtures do not have to +materialize 48 command files apiece. Check 1 (release-date consistency) has no +external authority (D1) -- it compares every release-date claim in the repo +against every other one -- so its fixtures need a peer claim to agree or +disagree with; see ``build_repo_multi`` and the ``release date`` tests below. """ from __future__ import annotations @@ -62,19 +64,10 @@ id="clean-structure-table", ), pytest.param( - "clean/release-date-utc-boundary.md", "docs/news.md", False, - "E2: a one-day gap is the UTC boundary, not staleness", - id="clean-utc-boundary", - ), - pytest.param( - "defect/version-box-stale-date.md", "docs/refcard.md", True, - "check 1 catches a release date well off the tag", - id="defect-stale-date", - ), - pytest.param( - "defect/release-date-far-edge.md", "docs/news.md", True, - "check 1 reaches the last line its window documents (off-by-one guard)", - id="defect-release-date-far-edge", + "clean/release-date-companion.md", "docs/news.md", False, + "D1 accepted cost: a lone release-date claim has no peer to compare " + "against, so it is vacuous, not verified", + id="clean-release-date-single-claim", ), pytest.param( "defect/tldr-eight-agents.md", "docs/skills-agents.md", True, @@ -106,6 +99,21 @@ "a doc describing a TL;DR bug is not itself making a TL;DR claim", id="falsepos-tldr-mentioned", ), + pytest.param( + "falsepos/hyphenated-compound-tldr.md", "docs/architecture.md", False, + "F1/F2: a hyphenated compound (command-line, agent-facing) must never read as a count", + id="falsepos-hyphenated-compound", + ), + pytest.param( + "falsepos/tldr-agent-subset-count.md", "docs/migration.md", False, + "F6: a legitimate small subset count for the smallest count type must clear the floor", + id="falsepos-agent-subset-count", + ), + pytest.param( + "falsepos/unclosed-box-leaks-into-table.md", "docs/notes.md", False, + "F8: an unclosed version-box must not leak into a later structure-table row's own-type restriction", + id="falsepos-unclosed-box", + ), ] @@ -122,20 +130,42 @@ def build_repo(tmp_path: Path, fixture: str, dest: str) -> Path: return repo -def run_check(repo: Path) -> dict: - """Run the real script against `repo` and return its parsed JSON report.""" - env = { +def build_repo_multi(tmp_path: Path, files: list[tuple[str, str]]) -> Path: + """Materialize a throwaway repo holding several fixture documents at once. + + Check 1 (release-date consistency, D1) needs at least two real claims to + exercise at all -- a lone claim is vacuous by design -- so its tests pair + a fixture against `clean/release-date-companion.md` in one repo, unlike + every other check here, which is fully exercised by a single document. + """ + repo = tmp_path / "repo" + (repo / ".claude-plugin").mkdir(parents=True) + (repo / ".claude-plugin" / "plugin.json").write_text( + json.dumps({"name": "craft", "version": FIXTURE_VERSION}) + "\n" + ) + for fixture, dest in files: + target = repo / dest + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(FIXTURES / fixture, target) + return repo + + +def env_for(repo: Path) -> dict: + return { "PATH": "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin", "HOME": str(repo), "CRAFT_PLUGIN_DIR": str(repo), - "CRAFT_RELEASE_DATE": FIXTURE_TAG_DATE, "CRAFT_EXPECTED_CMDS": FIXTURE_COUNTS["CMDS"], "CRAFT_EXPECTED_SKILLS": FIXTURE_COUNTS["SKILLS"], "CRAFT_EXPECTED_AGENTS": FIXTURE_COUNTS["AGENTS"], } + + +def run_check(repo: Path) -> dict: + """Run the real script against `repo` and return its parsed JSON report.""" proc = subprocess.run( ["bash", str(SCRIPT), "--json"], - env=env, capture_output=True, text=True, timeout=120, + env=env_for(repo), capture_output=True, text=True, timeout=120, ) assert proc.stdout.strip(), f"no JSON emitted; stderr:\n{proc.stderr}" return json.loads(proc.stdout) @@ -160,37 +190,63 @@ def test_prose_staleness_fixture(tmp_path, fixture, dest, expect_finding, proves ) -def test_unparseable_authority_date_is_vacuous_not_universal(tmp_path): - """A broken release-date authority must skip the check, not flag everything. +def _call_release_date_helpers(script_body: str) -> str: + """Source majority_date/compute_release_date_window/release_date_accepted + out of the real script and run `script_body` against them.""" + preamble = "\n".join( + f"eval \"$(sed -n '/^{fn}()/,/^}}/p' {SCRIPT})\"" + for fn in ("majority_date", "compute_release_date_window", "release_date_accepted") + ) + proc = subprocess.run( + ["bash", "-c", f"{preamble}\n{script_body}"], + capture_output=True, text=True, timeout=60, + ) + assert proc.returncode == 0, f"helper script failed:\n{proc.stderr}" + return proc.stdout + - Found in review of PR #334. `compute_release_date_window` exits silently on - an unparseable date, leaving an empty accept-window — and an empty window - matches nothing, so every release-date claim in the repo failed at once. One - bad input became a repo-wide false-positive storm. The guard is that the - check is vacuous unless the authority actually parsed. +def test_broken_authority_window_is_vacuous_not_universal(): + """A release-date window that failed to compute must reject every claim, + not accept everything (F7-era regression, re-verified after D1). - Uses the `clean/` fixture on purpose: it is correct against a real tag date, - so any finding here is caused by the broken authority alone. + `compute_release_date_window` exits silently on an unparseable date, + leaving `ACCEPTED_RELEASE_DATES` empty. Nothing in the current design can + feed it anything but a real `YYYY-MM-DD` (`majority_date` only ever + returns one of the claim strings, all already validated by the regex that + collected them) -- this is defense in depth, not a live path -- but the + guard must still hold: an empty window matches nothing, so a single bad + input must not silently become "everything is accepted" instead. """ - repo = build_repo(tmp_path, "clean/release-date-utc-boundary.md", "docs/news.md") - env = { - "PATH": "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin", - "HOME": str(repo), - "CRAFT_PLUGIN_DIR": str(repo), - "CRAFT_RELEASE_DATE": "not-a-date", - "CRAFT_EXPECTED_CMDS": FIXTURE_COUNTS["CMDS"], - "CRAFT_EXPECTED_SKILLS": FIXTURE_COUNTS["SKILLS"], - "CRAFT_EXPECTED_AGENTS": FIXTURE_COUNTS["AGENTS"], - } - proc = subprocess.run( - ["bash", str(SCRIPT), "--json"], - env=env, capture_output=True, text=True, timeout=120, + out = _call_release_date_helpers( + 'compute_release_date_window "not-a-date"\n' + 'release_date_accepted "2026-08-07" && echo ACCEPTED || echo REJECTED\n' ) - findings = json.loads(proc.stdout)["phases"]["count_consistency"]["findings"] - rendered = "\n".join(f" {f['file']}: {f['message']}" for f in findings) - assert not findings, ( - f"unparseable authority date produced findings instead of skipping:\n{rendered}" + assert out.strip() == "REJECTED", ( + f"a broken window accepted a claim instead of rejecting it: {out!r}" + ) + + +def test_majority_date_ties_break_to_the_later_date(): + """With claims tied 1-1 (the common case: one NEWS.md entry, one REFCARD.md + box), the later date must win -- a stale-release-date bug is a forgotten + update, so the wrong claim is normally older than the correct one, never + newer. Getting this backwards would make the check flag the correct claim + and accept the stale one. + """ + out = _call_release_date_helpers('majority_date "2026-07-19" "2026-08-07"\n') + assert out.strip() == "2026-08-07" + + out = _call_release_date_helpers('majority_date "2026-08-07" "2026-07-19"\n') + assert out.strip() == "2026-08-07", "order of arguments must not change the outcome" + + +def test_majority_date_prefers_the_larger_cluster_over_recency(): + """Two files agreeing beats one newer outlier -- majority by count comes + first; the later-date tie-break only applies when counts are equal.""" + out = _call_release_date_helpers( + 'majority_date "2026-08-07" "2026-08-07" "2026-09-01"\n' ) + assert out.strip() == "2026-08-07" def _call_apply_line_fix(path: Path, lineno: int, fix_detail: str) -> str: @@ -247,6 +303,250 @@ def test_every_check_has_a_planted_defect(): ) +def _build_repo_with_own_scripts(tmp_path: Path, fixture: str, dest: str) -> tuple[Path, Path]: + """Like build_repo, but copies scripts/ alongside so EXCLUSIONS_FILE (which + is resolved relative to the running script's own directory) lands inside + the throwaway tree instead of this repo's real + scripts/config/exclusions.txt. Returns (repo, script_copy). + """ + repo = build_repo(tmp_path, fixture, dest) + scripts_copy = repo / "scripts" + shutil.copytree(REPO_ROOT / "scripts", scripts_copy) + return repo, scripts_copy / "docs-staleness-check.sh" + + +def _run_check(script: Path, repo: Path) -> dict: + env = { + "PATH": "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin", + "HOME": str(repo), + "CRAFT_PLUGIN_DIR": str(repo), + "CRAFT_EXPECTED_CMDS": FIXTURE_COUNTS["CMDS"], + "CRAFT_EXPECTED_SKILLS": FIXTURE_COUNTS["SKILLS"], + "CRAFT_EXPECTED_AGENTS": FIXTURE_COUNTS["AGENTS"], + } + proc = subprocess.run( + ["bash", str(script), "--json"], env=env, capture_output=True, text=True, timeout=120, + ) + assert proc.stdout.strip(), f"no JSON emitted; stderr:\n{proc.stderr}" + return json.loads(proc.stdout) + + +def test_exclude_round_trips_end_to_end(tmp_path): + """[e]xclude must actually suppress the finding on the next run (F4/D4). + + Before D4, `file` was `path:lineno` glued into one field, so the entry + pass 2 wrote (`path:lineno:pattern`) could never match + `is_pattern_excluded`'s `path:pattern` parse — it printed "Excluded" and + the finding came back on the next run. + + Drives the REAL interactive `[e]` keystroke through a pty (pexpect), + not a reimplementation of pass 2's write logic in Python — a hand-copy + of the same bug pass 2 had would pass either way and prove nothing. + """ + pexpect = pytest.importorskip("pexpect") + + # structure-table-singular, not tldr-eight-agents: its noun ("8 agent") + # matches exactly what the check-time exclusion lookup passes + # (`${found} ${singular}`). tldr's noun ("8 specialized agents") does not + # — a separate, pre-existing exclusion-matching gap unrelated to D4 — and + # would make this test fail for a reason this phase does not fix. + repo, script = _build_repo_with_own_scripts( + tmp_path, "defect/structure-table-singular.md", "docs/structure.md" + ) + + before = _run_check(script, repo) + findings = before["phases"]["count_consistency"]["findings"] + assert findings, "fixture did not produce the finding this test excludes" + + env = { + "PATH": "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin", + "HOME": str(repo), + "CRAFT_PLUGIN_DIR": str(repo), + "CRAFT_EXPECTED_CMDS": FIXTURE_COUNTS["CMDS"], + "CRAFT_EXPECTED_SKILLS": FIXTURE_COUNTS["SKILLS"], + "CRAFT_EXPECTED_AGENTS": FIXTURE_COUNTS["AGENTS"], + } + child = pexpect.spawn("bash", [str(script), "--fix"], env=env, cwd=str(repo), timeout=30) + try: + child.expect_exact("[f]ix [s]kip [e]xclude permanently:") + child.sendline("e") + child.expect_exact("Excluded") + child.expect(pexpect.EOF) + finally: + child.close() + + exclusions_file = script.parent / "config" / "exclusions.txt" + assert exclusions_file.exists(), "no exclusions.txt was written" + written = exclusions_file.read_text() + assert "docs/structure.md:8 agent" in written, ( + f"exclusions.txt does not carry the expected pattern:\n{written}" + ) + + after = _run_check(script, repo) + after_findings = after["phases"]["count_consistency"]["findings"] + assert not after_findings, ( + f"[e]xclude did not suppress the finding on the next run — " + f"round-trip broken. exclusions.txt:\n{written}\nfindings:\n{after_findings}" + ) + + +def test_fix_preserves_surrounding_prose(tmp_path): + """[f]ix on a real shape-scoped finding must swap only the digits (F2/D3). + + Before D3, the fix payload was built from a second, un-anchored regex + ("N noun" with no trailer) rather than the exact span the detection regex + matched -- on a hyphenated compound that would have rewritten "30 + command-line" into "48 command-line" (the two regexes disagreed on where + the match ended). Drives the real interactive `[f]` keystroke through a + pty and asserts the rest of the line is untouched. + """ + pexpect = pytest.importorskip("pexpect") + + repo, script = _build_repo_with_own_scripts( + tmp_path, "defect/structure-table-singular.md", "docs/structure.md" + ) + target = repo / "docs" / "structure.md" + + env = { + "PATH": "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin", + "HOME": str(repo), + "CRAFT_PLUGIN_DIR": str(repo), + "CRAFT_EXPECTED_CMDS": FIXTURE_COUNTS["CMDS"], + "CRAFT_EXPECTED_SKILLS": FIXTURE_COUNTS["SKILLS"], + "CRAFT_EXPECTED_AGENTS": FIXTURE_COUNTS["AGENTS"], + } + child = pexpect.spawn("bash", [str(script), "--fix"], env=env, cwd=str(repo), timeout=30) + try: + child.expect_exact("[f]ix [s]kip [e]xclude permanently:") + child.sendline("f") + child.expect_exact("Fixed") + child.expect(pexpect.EOF) + finally: + child.close() + + lines = target.read_text().splitlines() + assert "| `agents/` | 2 agent definitions |" in lines, ( + f"fix did not land cleanly -- surrounding prose corrupted:\n{target.read_text()}" + ) + + +def test_release_date_agrees_across_files(tmp_path): + """Two independently-correct claims one day apart (the real v4.5.0 case: + NEWS.md says 2026-08-08, REFCARD-style boxes say 2026-08-07) must not + disagree with each other (E2, re-verified after D1's redesign).""" + repo = build_repo_multi(tmp_path, [ + ("clean/release-date-companion.md", "docs/news.md"), + ("clean/release-date-utc-boundary.md", "docs/other-news.md"), + ]) + report = run_check(repo) + findings = report["phases"]["count_consistency"]["findings"] + assert not findings, f"agreeing claims wrongly flagged:\n{findings}" + + +def test_release_date_disagreement_flags_the_stale_claim(tmp_path): + """A release date three weeks off must be caught once it has a peer to + disagree with -- this is the original REFCARD.md bug the parent SPEC + exists for, re-verified under D1's cross-file design.""" + repo = build_repo_multi(tmp_path, [ + ("clean/release-date-companion.md", "docs/news.md"), + ("defect/version-box-stale-date.md", "docs/refcard.md"), + ]) + report = run_check(repo) + findings = report["phases"]["count_consistency"]["findings"] + assert findings, "planted defect went undetected once it had a peer to disagree with" + assert any(f["file"].startswith("docs/refcard.md") for f in findings), ( + f"finding landed on the wrong file:\n{findings}" + ) + assert not any(f["file"].startswith("docs/news.md") for f in findings), ( + f"the correct companion claim was flagged instead of the stale one:\n{findings}" + ) + + +def test_release_date_far_edge_still_caught_cross_file(tmp_path): + """The window's off-by-one guard (win=5, not 4) must still hold once check + 1 compares claims to each other instead of to a tag.""" + repo = build_repo_multi(tmp_path, [ + ("clean/release-date-companion.md", "docs/news.md"), + ("defect/release-date-far-edge.md", "docs/news-old.md"), + ]) + report = run_check(repo) + findings = report["phases"]["count_consistency"]["findings"] + assert findings, "the far-edge claim was not collected -- window off-by-one regressed" + assert any(f["file"].startswith("docs/news-old.md") for f in findings), ( + f"finding landed on the wrong file:\n{findings}" + ) + + +def test_release_date_prose_mention_does_not_open_window(tmp_path): + """A version mention in running prose must not open the claim window + (D6/F3) -- paired against the companion so a wrongly-collected claim would + have a peer to disagree with and surface as a finding.""" + repo = build_repo_multi(tmp_path, [ + ("clean/release-date-companion.md", "docs/news.md"), + ("falsepos/release-date-prose-mention.md", "docs/upgrade-guide.md"), + ]) + report = run_check(repo) + findings = report["phases"]["count_consistency"]["findings"] + assert not findings, ( + f"prose mention of the version opened the claim window (D6 regressed):\n{findings}" + ) + + +def test_check1_promoted_check2_stays_warning(tmp_path): + """D11: check 1 was *designed* to block (D2) but shipped `warning` until + it earned `error` via the Phase 6 evidence gate -- a clean live-repo run + across every tracked doc and both real claim sites (docs/NEWS.md, + docs/REFCARD.md), transcript quoted, not a passing unit suite alone (see + `docs-staleness-check.sh`'s check-1 comment for the quoted transcript). + That gate is now met, so check 1 emits `error`. Check 2's severity was + never part of D11 and stays `warning`. + """ + repo = build_repo_multi(tmp_path, [ + ("clean/release-date-companion.md", "docs/news.md"), + ("defect/version-box-stale-date.md", "docs/refcard.md"), + ]) + findings = run_check(repo)["phases"]["count_consistency"]["findings"] + date_findings = [f for f in findings if "release date" in f["message"]] + assert date_findings, f"expected a release-date finding:\n{findings}" + assert all(f["severity"] == "error" for f in date_findings), ( + f"check 1 not promoted to error post-D11-gate:\n{date_findings}" + ) + + repo2 = build_repo(tmp_path / "case2", "defect/tldr-eight-agents.md", "docs/skills-agents.md") + findings2 = run_check(repo2)["phases"]["count_consistency"]["findings"] + assert findings2, f"expected a count-prose finding:\n{findings2}" + assert all(f["severity"] == "warning" for f in findings2), ( + f"check 2 finding is not severity warning:\n{findings2}" + ) + + +def test_phase_status_goes_red_on_any_error_finding(): + """print_phase_status / phase_status_label must coexist with mixed + severities inside one phase (Phase 4's own acceptance bullet) -- a phase + with one `error` finding among any number of `warning` findings is RED, + and an all-`warning` phase is YELLOW. Tested directly against + phase_status_label rather than through a live check, so it stays + independent of which checks currently emit `error` (check 1, post-D11) + vs `warning` (check 2) -- this pins the mechanism itself, not today's + severity assignment. + """ + preamble = f"eval \"$(sed -n '/^phase_status_label()/,/^}}/p' {SCRIPT})\"" + + mixed = 'ARR=("error|a.md||msg|false|" "warning|b.md||msg2|false|")' + proc = subprocess.run( + ["bash", "-c", f"{preamble}\n{mixed}\nphase_status_label ARR\n"], + capture_output=True, text=True, timeout=30, + ) + assert proc.stdout.strip() == "RED", f"mixed severity did not go RED: {proc.stdout!r}" + + warnings_only = 'ARR=("warning|a.md||msg|false|" "warning|b.md||msg2|false|")' + proc = subprocess.run( + ["bash", "-c", f"{preamble}\n{warnings_only}\nphase_status_label ARR\n"], + capture_output=True, text=True, timeout=30, + ) + assert proc.stdout.strip() == "YELLOW", f"warning-only phase was not YELLOW: {proc.stdout!r}" + + def test_live_repo_stays_green_on_count_consistency(): """The checks must not fire on craft's own already-corrected docs. From 01377e21da50f14a59ec8498a26f2943276ca7ff Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Sat, 15 Aug 2026 21:53:54 -0700 Subject: [PATCH 23/24] docs(.status): record PR #335 merge, drop leaked ORCHESTRATE artifact Consolidated the prose-check-hardening worktree row into a MERGED entry (squash 7d792285) with the D11 promotion, arch-review, and mid-flight rebase-conflict evidence; cross-referenced the superseded #334 row. Also deletes ORCHESTRATE-prose-check-hardening.md, which the squash-merge carried onto dev -- a feature-branch working artifact per craft's own CLAUDE.md convention, not dev content. Co-Authored-By: Claude Sonnet 5 --- .STATUS | 4 +- ORCHESTRATE-prose-check-hardening.md | 197 --------------------------- 2 files changed, 2 insertions(+), 199 deletions(-) delete mode 100644 ORCHESTRATE-prose-check-hardening.md diff --git a/.STATUS b/.STATUS index e4afff268..9b7587acb 100644 --- a/.STATUS +++ b/.STATUS @@ -709,8 +709,8 @@ next occurrence, not a date. | Branch | Path | Purpose | |--------|------|---------| -| `feature/prose-check-hardening` | `~/.git-worktrees/craft/feature-prose-check-hardening` | Hardens PR #334's prose checks per `SPEC-prose-check-hardening-2026-08-15.md` + `GRILL-prose-check-hardening-2026-08-15.md` (11 locked branches). All 6 phases implemented and verified: F1-F9 fixed (trailer/floor/fix-payload safety, cross-file release-date redesign dropping the git-tag authority, `[e]xclude` record split, box-close discipline, REFCARD prose accuracy). **D11 promotion gate cleared 2026-08-15**: check 1 promoted `warning`→`error` after a clean live-repo run (0 findings) across every tracked doc and both real claim sites (`docs/NEWS.md`, `docs/REFCARD.md`), plus a quoted transcript of the check firing on an injected mismatch. Check 2 stays `warning`. Full suite 2476 passed/0 failed (better than the 2462/20-failed dev baseline the branch started from — includes 2 tripwire-test fixes found during Phase 6 verification, not in the original F1-F9 list: `PROSE_COUNT_TRAILER` was missing `*`, so markdown-bold README badges like `**777 commands**` never matched the broad scan; `bump-version.sh --verify`'s literal `Version: X.Y.Z` grep broke against the Phase 5 REFCARD wording, fixed by a wording that satisfies both the grep and D6's window). `docs-staleness-check.sh` GREEN, 8.9s; `validate-counts.sh` 4/4; `mkdocs build --strict` clean; markdownlint 0 errors. PR [#335](https://github.com/Data-Wise/craft/pull/335) open. | -| _(prior, MERGED)_ | — | `feature/doc-staleness-prose-gaps` → dev via [#334](https://github.com/Data-Wise/craft/pull/334) (squash `b5de1ae39`), 2026-08-15. Two prose checks in `docs-staleness-check.sh` Phase 7 (release-date claims; count prose scoped to 4 structured line shapes, singular noun form included), ADR-007, and a 12-fixture table-driven harness. Four bugs were caught pre-merge by four different mechanisms — a 4× perf regression by the suite's own 30s timeout, an unparseable-authority false-positive storm by self-review, a release-date off-by-one by `/code-review`, and a pass-2 `[f]ix` that reported success without touching the file by reading adjacent code. **KNOWN, NOT FIXED:** a high-effort review found 9 further defects (2 HIGH — hyphenated compounds like `command-line` read as counts, and accepting the offered fix rewrites that prose). Merged on explicit instruction, reversing grill decision D5. Hardening is fully spec'd and grilled — `SPEC-prose-check-hardening-2026-08-15.md` + `GRILL-prose-check-hardening-2026-08-15.md` (11 locked branches) — but NOT implemented. Check 2 is `warning`-only so nothing blocks, but `--fix` can corrupt a document until it lands. Worktree removed and local branch deleted 2026-08-15; the work continues on `feature/prose-check-hardening` above. | +| _(prior, MERGED)_ | — | `feature/prose-check-hardening` → dev via [#335](https://github.com/Data-Wise/craft/pull/335) (squash `7d792285`), 2026-08-15. Hardens PR #334's prose checks per `SPEC-prose-check-hardening-2026-08-15.md` + `GRILL-prose-check-hardening-2026-08-15.md` (11 locked branches). All 6 phases implemented and verified: F1-F9 fixed (trailer/floor/fix-payload safety, cross-file release-date redesign dropping the git-tag authority, `[e]xclude` record split, box-close discipline, REFCARD prose accuracy). **D11 promotion gate cleared**: check 1 promoted `warning`→`error` after a clean live-repo run (0 findings) across every tracked doc and both real claim sites (`docs/NEWS.md`, `docs/REFCARD.md`), plus a quoted transcript of the check firing on an injected mismatch. Check 2 stays `warning`. Full suite 2476 passed/0 failed (better than the 2462/20-failed dev baseline — includes 2 tripwire-test fixes found during Phase 6 verification, not in the original F1-F9 list: `PROSE_COUNT_TRAILER` was missing `*`, so markdown-bold README badges like `**777 commands**` never matched the broad scan; `bump-version.sh --verify`'s literal `Version: X.Y.Z` grep broke against the Phase 5 REFCARD wording). Independent architecture review (fresh-context agent, `/craft:arch:review` criteria) found 0 violations, 2 minor warnings (function-length cohesion drift, a dead test env var — fixed pre-merge). Rebased once onto `dev` mid-flight for a genuine `.STATUS`/`docs/REFCARD.md` conflict (2 dev-ahead commits from a parallel doc-currency pass), resolved and re-verified before pushing. Worktree removed, local + remote branch deleted; stale `feature/doc-staleness-prose-gaps` remote ref (from #334, never pruned) also deleted after confirming its merge-commit is an ancestor of `dev`. | +| _(prior, MERGED)_ | — | `feature/doc-staleness-prose-gaps` → dev via [#334](https://github.com/Data-Wise/craft/pull/334) (squash `b5de1ae39`), 2026-08-15. Two prose checks in `docs-staleness-check.sh` Phase 7 (release-date claims; count prose scoped to 4 structured line shapes, singular noun form included), ADR-007, and a 12-fixture table-driven harness. Four bugs were caught pre-merge by four different mechanisms — a 4× perf regression by the suite's own 30s timeout, an unparseable-authority false-positive storm by self-review, a release-date off-by-one by `/code-review`, and a pass-2 `[f]ix` that reported success without touching the file by reading adjacent code. **KNOWN, FIXED IN #335 ABOVE:** a high-effort review found 9 further defects (2 HIGH — hyphenated compounds like `command-line` read as counts, and accepting the offered fix rewrites that prose). Merged on explicit instruction, reversing grill decision D5. Worktree removed and local branch deleted 2026-08-15. | | _(prior, MERGED)_ | — | `feature/repo-triage` → shipped in v4.5.0 (PR [#328](https://github.com/Data-Wise/craft/pull/328) squash to dev, released via [#329](https://github.com/Data-Wise/craft/pull/329)). Worktree removed. Original dispatch note: DISPATCHED 2026-08-07. New `repo-triage` skill (batch issue/worktree/branch triage, thin orchestrator over `issue-check` + `dev/git` Op 4/5). 6-phase ORCHESTRATE, `orchestrate-dispatch` mode. Phase 0 (`dev/git` Op 5 `--dry-run` + output contract) blocks Phases 2-4. Planning: `docs/specs/BRAINSTORM-repo-triage-2026-08-07.md`, `docs/specs/GRILL-repo-triage-2026-08-07.md` (amended post-review), `docs/specs/REVIEW-repo-triage-2026-08-07.md`. | | _(prior, MERGED)_ | — | `feature/prerelease-version-check-hardening` → dev via [#311](https://github.com/Data-Wise/craft/pull/311) (squash), 2026-07-25. Audited the repo for the "grep first bare vX.Y.Z string" bug class that broke the Homebrew release gate twice (v4.2.0, v4.3.0, fixed for `docs/index.md` in #308) — found and fixed 4 more live sites: `.github/workflows/docs.yml`'s live-site poll, `verify-surfaces.sh`'s `resolve_docs_site` (explicitly reused docs.yml's mechanism), `version-sync-precommit.sh`'s `.STATUS` check (a blocking pre-commit hook), and the manual verification steps in `skills/release/SKILL.md`/`downstream-verification.md`; all anchored to the actual version signal (badge slug / `^version:` field) instead of the first bare match. Confirmed 3 other sites already safe, left unchanged. New regression tests for each fix, each verified against a planted-defect positive control. Closes the recurrence risk flagged after v4.3.0. Worktree removed, local + remote branch deleted. | | _(prior, MERGED)_ | — | `feature/issue-309-branch-guard-docs` → dev via [#310](https://github.com/Data-Wise/craft/pull/310) (squash), 2026-07-25. The documented "Claude writes `.claude/allow-once`" one-shot bypass flow is circular when the approved action *is* creating/editing that marker itself (every tool path intercepts it with its own `[CONFIRM]`); added the `CRAFT_GUARD_ALLOW_DEV_EDIT` env-var hint (#281) to the 3 marker-specific confirms only, and reconciled `REFCARD-BRANCH-GUARD.md`/`branch-guard-smart-mode.md` with a caveat pointing at `skills/dev/git/SKILL.md` Operation 10. Code review caught the negative regression test was checking the wrong verbosity encounter (2nd-call brief mode never renders suggestions regardless of content, so it passed vacuously); fixed to assert against the 1st-encounter full-verbosity capture, verified via planted-defect positive control. Closes #309. Worktree removed, local + remote branch deleted. | diff --git a/ORCHESTRATE-prose-check-hardening.md b/ORCHESTRATE-prose-check-hardening.md deleted file mode 100644 index 3e8116988..000000000 --- a/ORCHESTRATE-prose-check-hardening.md +++ /dev/null @@ -1,197 +0,0 @@ -# ORCHESTRATE: Prose-Check Hardening - -**Spec:** [`docs/specs/SPEC-prose-check-hardening-2026-08-15.md`](docs/specs/SPEC-prose-check-hardening-2026-08-15.md) -**Grill:** [`docs/specs/GRILL-prose-check-hardening-2026-08-15.md`](docs/specs/GRILL-prose-check-hardening-2026-08-15.md) — 11 branches locked (D1–D6 spec, D7–D11 plan) -**Worktree:** `~/.git-worktrees/craft/feature-doc-staleness-prose-gaps` (exists) -**Branch:** `feature/doc-staleness-prose-gaps` · **PR:** #334 (open, CI green, **do not merge until P6**) - -## Objective - -Close the 9 defects a high-effort review found in PR #334, per the grill's locked decisions, on the -same branch — so #334 never ships a check that emits false positives or corrupts prose. - -## Phase Overview - -**Reordered per D7:** the record change goes first. As originally written, Phases 1–3 authored -`add_finding` calls and tests against the old signature and Phase 4 then rewrote all 12 call sites — -every prose finding written twice, its tests touched twice, and the mechanical sweep landing where -a missed site is likeliest. - -| # | Phase | Findings | Locked by | Est | -|---|-------|----------|-----------|-----| -| 1 | Finding record: line number as its own field | F4 | D4, D7 | 45 min | -| 2 | Matcher, fix payload, floor | F1, F2, F6 | D3 | 25 min | -| 3 | Check 1 → cross-file consistency | F3, F7 | D1, D6, D8 | 40 min | -| 4 | Severity split + ADR correction | F5 | D2, D11 | 20 min | -| 5 | Low findings, date normalization, doc sync | F8, F9 | D9 | 25 min | -| 6 | Verify, promote, update PR #334 | — | D5, D10, D11 | 30 min | - -**Estimate honesty (D11):** ~3 hours, not the 2 first written. Phase 1 alone is 12 call sites plus -JSON rendering plus both fix passes plus an end-to-end exclusion test; 25 minutes was wishful. - -Sequential. Phase 1 must complete before any other phase touches `add_finding`. - -## Phase 1 — Finding record: line number as its own field (D4, D7) - -Goes first so every later phase is written once, against the final signature, and so the -largest-blast-radius change lands against an unmodified baseline where the full suite is a clean -control. - -- [ ] `add_finding` takes line number as its own argument instead of glued into `file` -- [ ] Update all 12 call sites: Phase 6 (×2), Phase 7 (×3), Phase 8 (×3), Phase 9 (×4) -- [ ] JSON output preserves today's `file` rendering (`path:lineno`) so consumers do not break — - the split is internal -- [ ] `[e]` writes `path:pattern`, matching what `is_pattern_excluded` parses -- [ ] **E2E, not inspection:** write the exclusion, re-run, assert the finding is gone -- [ ] Mutation control: revert the record change → the round-trip test fails -- [ ] **Full suite after this phase**, not just the prose tests — this is the widest change here - -## Phase 2 — Matcher, fix payload, floor (D3) - -- [ ] Trailer: `([^a-z]|$)` → whitespace / closing punctuation / end-of-line, in the check-2 - matcher **and** awk's `hascount()` pre-filter (they must agree, or the pre-filter drops lines - the matcher would have caught) -- [ ] Floor: `expected * 40 / 100` → `max(2, expected * 40 / 100)` -- [ ] Fix payload: build the substitution from the full matched span, not the bare `N noun` prefix -- [ ] Fixture `falsepos/hyphenated-compounds.md`: `command-line`, `agent-facing`, - `skill-authoring`, `agents-only` — all GREEN -- [ ] Fixture `defect/small-count-agents.md`: a wrong agent count the zero floor used to pass only - by accident, now caught deliberately -- [ ] **Pin E1 survives:** `clean/structure-table-correct.md` and - `defect/structure-table-singular.md` pass unchanged — `8 agent definitions` has a space after - the noun and `8 ≥ max(2, 0)` -- [ ] Mutation control per change: revert the trailer → hyphen fixture fails; revert the floor → - small-count fixture fails; revert the span anchor → a fix-application test corrupts prose - -## Phase 3 — Check 1 → cross-file consistency (D1, D6, D8) - -- [ ] Window opens only on a **markdown heading** or a version-box line containing the version - token — not on any line mentioning it (D6, closes F3) -- [ ] Replace `resolve_release_date` / `compute_release_date_window` / `release_date_accepted` - with claim collection + pairwise agreement (one-day tolerance retained) -- [ ] Delete `CRAFT_RELEASE_DATE`; the harness no longer injects an authority -- [ ] Report shape names **both** disagreeing sites — a finding naming one file cannot be acted on -- [ ] Vacuous when fewer than 2 claims exist (ADR-007: report only what you can establish) -- [ ] **Rewrite, never delete, the two existing check-1 fixtures (D8).** The window still exists - under D6, so the off-by-one class is still live and `defect/release-date-far-edge.md` is its - only positive control — the exact bug `/code-review` caught. Rewrite it as: heading at line N, - disagreeing claim at the window's last line → still RED. Rewrite - `defect/version-box-stale-date.md` as an in-box disagreement, since boxes are where the - original REFCARD bug lived. -- [ ] Fixture `falsepos/version-mentioned-in-prose.md`: "Upgrading to v4.5.0 is a drop-in change" - followed by an unrelated `Released:` date → GREEN -- [ ] Fixture `defect/release-date-disagreement.md` pair; `clean/release-date-utc-boundary.md` - rewritten to a two-claim form one day apart → GREEN - -**Real claim sites, confirmed 2026-08-15** — the check must reach both: - -| Site | Shape | Reached via | -|---|---|---| -| `docs/NEWS.md:9` | `**Released:** 2026-08-08` | `## v4.5.0 …` heading two lines above | -| `docs/REFCARD.md:7` | `│ Version: 4.5.0 (released 2026-08-07)` | version-box line, claim on the same line | - -## Phase 4 — Severity split + ADR correction (D2, D11) - -- [ ] Check 1 ships as **`warning`** in this phase. Promotion to `error` happens in Phase 6, gated - on evidence (D11) — the check is being redesigned and promoted in one PR, and the only - evidence it is sound would otherwise be tests written alongside it. -- [ ] `print_phase_status` / phase-label handling copes with mixed severities in one phase -- [ ] ADR-007 Severity section: **remove** the false claim (that both severities fail the gate - identically), replace with verified behavior — `pre-release-check.sh:280` swallows the exit - code, `docs-quality.yml` sets `continue-on-error`, `skills/release/SKILL.md:143` blocks on - the RED label. Record the D11 gate as the reason RED is earned, not assumed. -- [ ] Test: a check-1 finding makes overall status RED once promoted; a check-2-only finding stays - YELLOW - -## Phase 5 — Low findings, date normalization, doc sync (D9) - -- [x] F8: close `version-box` mode at the first line carrying no box character -- [x] F8 fixture: unclosed `┌` followed by a `commands/` structure-table row → the row is still - measured against commands, not agents (`falsepos/unclosed-box-leaks-into-table.md`; uses - the singular noun form so the fixture isolates the shape-classification bug from the - broad, unscoped plural scan, which would otherwise independently catch the same text) -- [x] F9: REFCARD `tldr` row → "a line that **opens** with `TL;DR`" - (`docs/reference/REFCARD-DOCS-STALENESS.md`) -- [x] **Normalize the two claim sites (D9).** `docs/NEWS.md`'s v4.5.0 entry moved from - 2026-08-08 to 2026-08-07 (tag-local); `docs/REFCARD.md`'s box line gained the `v4.5.0` - prefix it was missing, which is what makes it reachable under D6's heading-or-box-line - window in the first place (it sat 4 lines *before* the box's other `v4.5.0` mention, so - the forward-only window never reached it). Recorded the convention in - `skills/release/references/pipeline-steps.md`. Verified live by injecting a temporary - mismatch into REFCARD.md and confirming check 1 caught it before reverting. -- [x] Sync REFCARD, both CHANGELOGs, and the parent - `SPEC-doc-staleness-prose-gaps-2026-08-07.md` — its check-1 description is now wrong - (added a "Superseded" pointer section rather than rewriting the historical record) -- [x] Mark the hardening SPEC's acceptance criteria - -## Phase 6 — Verify, promote, update PR #334 (D5, D10, D11) - -- [x] Full suite in-tree; compare against the dev baseline (20 failed / 2462 passed) — **2476 - passed / 0 failed**, better than baseline. Also fixed 2 regressions surfaced by this run - (not in F1-F9): `PROSE_COUNT_TRAILER` missing `*` broke the broad scan against markdown-bold - README badges (`**777 commands**`); `bump-version.sh --verify`'s literal `Version: X.Y.Z` - grep broke against the Phase 5 REFCARD wording, reconciled with a wording that satisfies - both it and D6's window. -- [x] `docs-staleness-check.sh` GREEN; runtime under the 30s `test_pre_release_check_runs` budget - — 8.9s -- [x] `validate-counts.sh` (4/4 ✓), markdownlint (0 errors, 10 changed files), `mkdocs build - --strict` (clean) -- [x] **D11 promotion gate:** check 1 ran clean (0 findings) across every tracked `.md` and both - known claim sites, transcript quoted → **promoted to `error`**. Transcript: injecting - `2020-01-01` into `docs/REFCARD.md:7` produced `"release date '2020-01-01' for v4.5.0 - disagrees with other claims (majority: 2026-08-07)"`, reverted after confirming. Check 2 - stays `warning`. -- [x] **D10:** refreshed the `.STATUS` worktree row with the hardening summary -- [ ] Update the PR #334 body with the hardening summary and quoted E2E transcripts — N/A: #334 - already merged: this is a new PR from `feature/prose-check-hardening` → `dev` -- [ ] Watch CI to green — **then stop.** Merging is a separate, explicit instruction. -- [ ] **At merge (D10):** delete `ORCHESTRATE-prose-check-hardening.md`. It is a feature-branch - working artifact, not `dev` content (craft CLAUDE.md). Nothing enforces this — not - `.gitignore`, not `exclusions.txt`, not a test — so it only happens if someone does it. - -## Friction Prevention - -- **The awk pre-filter and the bash matcher must stay in sync** (Phase 2). Two regexes expressing - one rule; if the pre-filter is tighter, lines silently never reach the matcher. This already - caused a 4× perf regression once when they were tuned independently. -- **Runtime budget is real, not theoretical.** `test_pre_release_check_runs` times out at 30s and - is the only thing that caught the last perf regression. Re-time after Phase 3. -- **Do not widen the broad plural-only scan.** Every hardening here is inside the line shapes; the - unscoped scan stays plural-only or the 90+ false positives return. -- **Phase 1 has the largest blast radius** — a record consumed by four phases, JSON output, and - both fix passes. Full suite after it, not just the prose tests. -- **Never delete a positive control to make a redesign easier** (D8). If a fixture's model is - obsolete, rewrite it so the boundary it pinned stays pinned. - -## Acceptance Criteria - -- [x] Every finding F1–F9 has a test that **fails before its fix and passes after**, each verified - by planted mutation -- [x] Hyphenated-compound corpus produces zero findings; real counts still caught -- [x] `[e]xclude` round-trips end-to-end -- [x] ADR-007's incorrect severity claim is removed, not softened -- [x] Check 1 reaches both real claim sites (`docs/NEWS.md`, `docs/REFCARD.md`) — transcript quoted -- [x] The two claim sites agree exactly, so the one-day tolerance is headroom (D9) -- [x] RED promotion is evidence-gated, not assumed (D11) — promoted 2026-08-15, see Phase 6 -- [x] Live repo GREEN; runtime under budget (8.9s); dev-baseline parity (2476/0 vs. 2462/20) - -## Commit Strategy - -One commit per phase, conventional prefix, each naming the finding IDs it closes. Phase 1 commits -alone given its blast radius. No squashing before review — the phase boundaries are the review -units. - -## Verification - -```bash -cd ~/.git-worktrees/craft/feature-doc-staleness-prose-gaps -python3 -m pytest tests/test_docs_staleness_prose.py -q # per phase -python3 -m pytest tests/ -q # after Phase 1 and at Phase 6 -./scripts/docs-staleness-check.sh # GREEN -time ./scripts/docs-staleness-check.sh # under 30s -``` - -## Session Instructions - -Implementation continues in this session per craft's ORCHESTRATE convention (no handoff to a new -session). Every phase ends with its mutation controls demonstrated, not merely a passing suite. From 68344a92029f735bcd645e8584a11beeddfa4ea9 Mon Sep 17 00:00:00 2001 From: Stat-Wise Date: Sat, 15 Aug 2026 22:13:50 -0700 Subject: [PATCH 24/24] chore: bump version to v4.6.0 for release --- .STATUS | 2 +- .claude-plugin/marketplace.json | 4 +-- .claude-plugin/plugin.json | 2 +- CHANGELOG.md | 51 ++++++++++++++++++++++++--------- CLAUDE.md | 2 +- README.md | 2 +- commands/hub.md | 2 +- docs/CHANGELOG.md | 51 ++++++++++++++++++++++++--------- docs/DEPENDENCY-ARCHITECTURE.md | 2 +- docs/NEWS.md | 21 ++++++++++++++ docs/REFCARD.md | 4 +-- docs/commands/hub.md | 2 +- docs/index.md | 4 +-- docs/reference/configuration.md | 2 +- mkdocs.yml | 2 +- package.json | 2 +- 16 files changed, 113 insertions(+), 42 deletions(-) diff --git a/.STATUS b/.STATUS index 9b7587acb..327fdbf30 100644 --- a/.STATUS +++ b/.STATUS @@ -1,5 +1,5 @@ status: Active -version: 4.5.0 +version: 4.6.0 progress: 100 release_date: 2026-08-07 milestone: **v4.5.0 SHIPPED 2026-08-07** — PR #329 (dev→main merge, tag v4.5.0, MINOR): new `repo-triage` skill (41st skill) batch-grounds open GitHub issues and stale worktrees/branches against current repo state, offering confirmed (never automatic) deletion/closure — built via brainstorm→grill (`GRILL-repo-triage-2026-08-07.md`, 2 factual corrections)→dispatched ORCHESTRATE worktree, code-reviewed (8 findings, all fixed pre-merge: jq-null pr_state bug, swallowed git-diff failures, not-merged-branch passthrough, tracked ORCHESTRATE artifact, stale skill-count prose, duplicated evidence-normalization + fence-extraction logic consolidated into `classifier_loader.py`, cross-repo file-check bug), merged PR #328 squash to dev first. Also shipped: 8 new ADHD-friendly ecosystem-tool cheat-sheet tutorials (Codex, Remember, i-have-adhd, Token Optimizer, Security Guidance, Claude HUD, Dropbox, Agent Skills) under a new nested `Ecosystem Tool Cheat Sheets` mkdocs subsection (`SPEC-ecosystem-tool-tutorials-2026-08-07.md`), plus a `docs/commands/hub.md` nav-completeness fix (`TUTORIAL-opencode-plugins.md` was missing from mkdocs.yml). Pre-flight found 2 non-blocking local-only failures (not regressions, CI confirmed green on the same commit): `test_branch_guard_dogfood.py` (the installed hook is a symlink to the sibling `cc-config` repo, out of scope per cross-repo permission boundary) and `test_no_live_legacy_check_skill_references` (was walking gitignored `.remember/` via raw filesystem `rglob()` — fixed to scope to `git ls-files`, genuine bug, fixed pre-release). Full suite 2632 passed/0 failed (post-fix); `pre-release-check.sh 4.5.0` clean; docs-staleness GREEN; live site + Homebrew formula + aggregator sync (claude-plugins PR #46, auto-merged) all confirmed v4.5.0; `verify-surfaces.sh` needed a local `homebrew-tap` `git pull` + `brew upgrade` + `claude plugin update` refresh before reporting ALIGNED (known pattern — the script reads the local tap checkout, not live GitHub). KNOWN, NOT FIXED: `aggregator-sync.yml` returns 404 by that exact workflow name in `claude-plugins` (the release skill's own docs name a workflow that doesn't exist under that filename) — propagation still worked via some other mechanism (PR #46 auto-merged), doc/workflow-name discrepancy left unswept, out of scope for this release. Release: https://github.com/Data-Wise/craft/releases/tag/v4.5.0 · PR: https://github.com/Data-Wise/craft/pull/329, https://github.com/Data-Wise/craft/pull/328. **Post-release same-day (2026-08-07):** doc audit found + fixed 3 stale-prose bugs the count-consistency gate can't catch (`REFCARD.md`'s version-box date/highlight + skill count, `skills-agents.md`'s "8 specialized agents", `docs/index.md`'s 5× "47 commands" + missing real install instructions — Quick Start only had the dev-only local-plugins symlink, added Homebrew + Data-Wise marketplace paths); added a `triage-your-repo` cookbook recipe for the repo-triage skill. Brainstormed → spec'd → adversarially reviewed a gate fix for this exact blind spot (`SPEC-doc-staleness-prose-gaps-2026-08-07.md`: 2 new Phase 7 checks, release-date claims + structured-line-scoped count prose; review killed a 3rd check that was falsified by its own target and caught 90+ false positives in the 2nd, both fixed pre-build; not yet implemented). Cross-repo: `homebrew-tap#211` fixed a stale `/craft:git:unprotect` caveat reference (command removed in v4) — first push tripped Formula Drift Guard (content changed, no revision bump), fixed with `revision: 1`; merged, verified live via `brew upgrade` (4.5.0 → 4.5.0_1, new caveat text confirmed). Follow-up noted, not built: `homebrew-tap`'s own `manifest.json` caveats prose has the identical staleness blind spot, out of scope for the craft-only SPEC. Prior: **v4.4.2 SHIPPED 2026-07-27** — PR #318 (dev→main merge, tag v4.4.2, PATCH): the one fix merged to dev since v4.4.1 (PR #317, closes #316). `preflight-check` skill made reachable by its declared identity — it lived at the pre-rename `skills/check/` location while declaring `name: preflight-check`, so directory-based skill discovery couldn't find it; moved to `skills/preflight-check/`, `/craft:check`'s public argument contract unchanged. New recursive fixture-backed identity tests inventory every command/skill directory-vs-frontmatter mismatch and command/skill name collision plugin-wide, rejecting new drift; 8 pre-existing mismatches + 3 intentional shim collisions ledgered as owned debt through v5.0.0. Test automation now targets stable Python 3.14 (`.python-version` pin, 5 GitHub Actions workflows aligned, drift-guard policy test) — the local-interpreter assertion in that test was found too brittle on review (hard-failed for any contributor not on 3.14) and fixed to skip instead before merge. `docs/NEWS.md`/`docs/index.md`'s own v4.4.2 release-note prose initially contained the literal `skills/check/` string, which the new #317 regression guard (`test_no_live_legacy_check_skill_references`) correctly caught as a repo-wide scan, not scoped to code — rephrased without the literal path, second commit. Full suite 2627 passed/0 failed/52 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.2` clean (10/10); docs-staleness GREEN; live site + Homebrew formula (fresh sha256) + aggregator sync + Craft MCP Release all confirmed v4.4.2; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.2 · PR: https://github.com/Data-Wise/craft/pull/318, https://github.com/Data-Wise/craft/pull/317. Prior: **v4.4.1 SHIPPED 2026-07-26** — PR #315 (dev→main merge, tag v4.4.1, PATCH): post-4.4.0 fixes only, no features. `scripts/branch-guard.sh`'s `2>&1`/`1>&2` false-positive (a command like `git commit -m "...(7 -> 9)" 2>&1` misdetected as file creation) was fixed live on the installed hook earlier this session but never ported to the repo's canonical copy, leaving `test_repo_copy_matches_installed` red and the bug reintroducible on reinstall — ported with 2 regression tests (implements `GRILL-rtk-hook-safety-close-out-2026-07-26.md` decision #3). `docs/help/refine-flag.md` and `docs/reference/COMMAND-PARAMETERS.md` fixed for PR #313's 9-command declarer set (was still 7, missing `/craft:plan`/`/craft:smart-help`) — new dogfood test guards against recurrence. `docs/index.md`'s "Latest" callout and `docs/NEWS.md` synced (both still described v4.3.0's content under a v4.4.0 label; NEWS.md had no v4.4.0 entry at all). Also triaged 7 remaining open issues (#228, #213, #199, #170, #169, #133, #108): closed #108/#213/#133, re-scope comments on #228/#170/#169, confirmed #199 valid. Full suite 2654 passed/0 failed/16 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.1` clean; docs-staleness GREEN; live site + Homebrew formula confirmed v4.4.1; post-release sweep ALL CLEAN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.1 · PR: https://github.com/Data-Wise/craft/pull/315. Prior: **v4.4.0 SHIPPED 2026-07-26** — PR #314 (dev→main merge, tag v4.4.0, MINOR): the one `feat:` commit since v4.3.1. `smart-help` declares `--refine` (D7, PR #313) — opt-in, default-OFF, closing the last dangling item on #268's prompt-refiner umbrella. `prompt-refiner`'s Default Policy table made exhaustive and re-keyed on command file path — fixed 3 stale display names, added 2 live callers it was silently missing (`commands/plan.md`, `commands/arch/plan.md`); a new dogfood test (`test_refine_default_policy_table_exhaustive`) guards against future drift. #268's other two proposed items (named destinations, `--scope global`) and #266 (multi-round grill mode) were grilled then killed on adversarial review before implementation — see `docs/specs/GRILL-prompt-refiner-remaining-2026-07-26.md`'s amendment; #266 closed pointing at savant's `edit-loop` (shipped 2026-07-13), which already solves the observed pattern with tooling craft structurally lacks. Full suite 2618 passed/0 failed/49 skipped/1 xfailed/1 xpassed; `pre-release-check.sh 4.4.0` clean (10/10); docs-staleness GREEN; live site + Homebrew formula + aggregator sync all confirmed v4.4.0. Release: https://github.com/Data-Wise/craft/releases/tag/v4.4.0 · PR: https://github.com/Data-Wise/craft/pull/314, https://github.com/Data-Wise/craft/pull/313. Prior: **v4.3.1 SHIPPED 2026-07-25** — PR #312 (dev→main merge, tag v4.3.1, PATCH): two bug fixes merged to dev since v4.3.0, both traced to the same session. `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281) surfaced in the guard-bypass-marker confirms themselves (#309, PR #310) — the documented "Claude writes `.claude/allow-once`" one-shot flow was circular for the one case where the approved action IS creating/editing that marker; added the env-var hint to the three marker-specific confirms, reconciled both branch-guard reference docs. Bare-`vX.Y.Z` version-grep bug class swept repo-wide (PR #311) — the same false positive that broke the Homebrew release gate twice (v4.2.0, v4.3.0; fixed for `docs/index.md` in #308) also affected `docs.yml`'s live-site poll, `verify-surfaces.sh`'s `resolve_docs_site`, `version-sync-precommit.sh`'s blocking `.STATUS` pre-commit check, and manual release-verification docs — all re-anchored to the actual version signal instead of the first bare match, each with a regression test verified via planted-defect positive control. Full suite 2650 passed/0 failed/16 skipped/1 xfailed (tracked)/1 xpassed (known perf flake); `pre-release-check.sh 4.3.1` clean (10/10); docs-staleness GREEN. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.1 · PR: https://github.com/Data-Wise/craft/pull/312, https://github.com/Data-Wise/craft/pull/310, https://github.com/Data-Wise/craft/pull/311. Prior: **v4.3.0 SHIPPED 2026-07-25** — PR #307 (dev→main merge, tag v4.3.0): `CRAFT_GUARD_ALLOW_DEV_EDIT` escape hatch (#281, PR #305) closing the guard-bypass-marker creation deadlock, reusing the #168 env-var pre-authorization pattern; new advisory-only `reference-scope-guard.sh` hook (#286, PR #306) warning on non-conforming filenames under `~/.claude/reference/`, sharing `install-guards.sh`/`guards.json` with `branch-guard`/`no-switch-guard`. Full suite 2648 passed/0 failed. **Homebrew Release recurrence:** the exact same `pre-release-check.sh` false positive that broke v4.2.0's automated tap push (worked around manually then) broke v4.3.0's too — `docs/index.md`'s version check grepped the first bare `vX.Y.Z` string in the file, landing on a historical "since v4.0.0" migration note instead of the actual version badge. Fixed at the source this time (PR #308, fast-tracked dev→main): matched the badge pattern specifically (`version-X.Y.Z`), same as the adjacent README.md check. Re-dispatched `homebrew-release.yml` via `workflow_dispatch --ref main -f version=4.3.0` (the original `release`-event run had checked out the tag commit before the fix existed, so a plain rerun would have failed identically) — all 3 jobs green, tap formula confirmed live (`v4.3.0.tar.gz`, fresh sha256). Craft MCP Release + Aggregator Sync both green from the original release event. Release: https://github.com/Data-Wise/craft/releases/tag/v4.3.0 · PR: https://github.com/Data-Wise/craft/pull/307, https://github.com/Data-Wise/craft/pull/308. Prior: **v4.2.0 SHIPPED 2026-07-19** — PR #296 (dev→main merge, tag v4.2.0): **BREAKING:** `/craft:done` renamed to `/craft:finish` (ADR-006) — matches the existing zsh `finish` alias, no back-compat shim, command count unchanged (48, a rename not an addition); `/craft:restore` spec/report status corrected DRAFT→SHIPPED (had already shipped in v4.1.0 when written). Full suite 2649 passed/1 failed (a genuine broken-link regression from the rename sweep, `docs/commands/brief.md` still linked pre-rename `done.md` — caught by the test run itself, fixed same-commit)/10 skipped. Also merged to dev this cycle (not yet in a tagged release): PR #297 (`utils/docs_detector.py`'s `_extract_command_name()` truncated multi-hyphen command names past a 500-char read window, bumped to 2000), PR #298 (extends `/craft:finish` Step 1.10.5 with a `~/.claude/projects//memory/`+`.remember/` staleness check, 14-day threshold, report-only), and a `docs/reports/` folder established as the `/savant:report` output convention (moved `REPORT-craft-restore-2026-07-17.md` there, added to `scripts/config/exclusions.txt` alongside `docs/specs/`). **Homebrew:** v4.2.0's automated tap-formula push failed — `homebrew-tap`'s `main` gained branch protection (PR + 2 required checks) since `homebrew-release.yml` was last touched, so its direct `git push origin main` was rejected (GH006); recovered manually via `homebrew-tap`#164, which surfaced 2 more CI-gate bugs fixed directly in that repo (#165: `check-revision-bump.sh` false-positived on URL-tag-versioned formulas like craft's, no explicit `version` field to read; #170: the ".STATUS conflict markers" required check was path-filtered to only `.STATUS` changes, so it could never fire — and the PR stayed BLOCKED forever — on formula-only PRs). craft's own workflow fixed to match (PR #299: push a bot branch + open/auto-merge a PR instead of pushing directly), landing independently from a background-dispatched session whose worktree had gone stale after only completing 1 of the 2 related craft/homebrew-tap fixes — recovered by inspecting the worktree directly rather than trusting the dispatch's completion state. Release: https://github.com/Data-Wise/craft/releases/tag/v4.2.0 · PR: https://github.com/Data-Wise/craft/pull/296. Prior: **v4.1.0 SHIPPED 2026-07-17** — PR #295 (dev→main merge, tag v4.1.0): `/craft:restore` (NEW root command, combines `dev/git` skill's mode-aware git-activity recap with `adhd-workflow`'s `.STATUS`-based session recap into one read-only entry point; replaces the deleted `git-recap`/`recap` commands; no `--sync`; a cross-plugin `/restore` dispatcher to `savant:restore` was investigated and explicitly descoped — router can't dispatch cross-plugin) + `/craft:refine` confirm-flow fix (a 2026-07-01 design — fenced refined-prompt block + 4-way confirm: Execute now/Copy for elsewhere/Edit first/Skip — was locked but never implemented; now shipped across all 5 `--refine` callers + standalone, closing the "asks for acceptance without printing first" bug) + full post-v4.0.0 doc-staleness cleanup (47→48 count-drift sweep across ~35 files, mermaid health score 82.3→100 on new diagrams, `docs-staleness-check.sh` RED→GREEN). New architecture docs: `docs/architecture/craft-restore-pipeline.md`, `docs/architecture/prompt-refiner-pipeline.md`. Homebrew formula PR data-wise/homebrew-tap#153 open (tested locally: `brew style`/`brew audit --strict`/`brew install` all clean, 48 commands). Full suite 2650 passed/0 failed. Release: https://github.com/Data-Wise/craft/releases/tag/v4.1.0 · PR: https://github.com/Data-Wise/craft/pull/295. Prior: **v4.0.0 SHIPPED 2026-07-16** — PR #294 (dev→main merge-commit `678e4d4a`, tag v4.0.0): craft docs-authoring surface extracted to the standalone `folio` plugin (folio v1.0.0 released FIRST, per the rollback runbook's ordering). Counts 94→47 commands / 45→40 skills / 8→2 agents. Homebrew formula regenerated with real values (sha256, command_count=47) via homebrew-release.yml (3/3 jobs green); `brew audit`/`brew upgrade` verified clean for both craft and folio formulas. Post-release docs sweep (same session): commands/hub.md + its docs/ mirror had drifted badly (stale example counts, 2 stray text artifacts, a main-menu ASCII template whose header counts didn't match listed items and referenced commands moved to folio or folded into skills); rewrote to match the real 47-command roster. API-REFERENCE-COMMANDS.md (1850 lines, last synced v1.24.0) converted to a thin index into docs/commands/*.md + docs/REFCARD.md rather than re-syncing a third documentation surface indefinitely; DEPENDENCY-ARCHITECTURE.md left untouched (its craft-vs-folio ownership question is still open, test_integration_dependency_system.py:38). Full pytest suite 2641 passed/0 failed. KNOWN, NOT FIXED: docs/REFCARD.md has the same staleness pattern across multiple sections (Site/Distribution/CI/CD subtotals, 20+ stale `/craft:git:worktree` examples for a command folded into the dev/git skill) — flagged, not yet swept. Folio's Aggregator Sync workflow fails by design (folio is a new plugin, never added to the shared Data-Wise aggregator 's marketplace.json — aggregator-sync.sh deliberately refuses to silently add unknown plugins; adding folio there is a separate, reviewed decision). CP-4 tasks/ cleanup: craft's tasks/ deleted directly on dev (unprotected); folio's tasks/session-plan-T2.4-CP2.md removed via PRs #15 (dev) and #16 (main) rather than a direct-branch delete (blocked by the permission classifier). Release: https://github.com/Data-Wise/craft/releases/tag/v4.0.0 · PR: https://github.com/Data-Wise/craft/pull/294. Prior: **v4 FOLIO SPLIT MERGED to dev 2026-07-16** — PR #290 (merge-commit `4af10f27d`, phase history preserved for the eventual v4 release tag): craft's docs-authoring surface extracted to the standalone `folio` plugin. **Counts 95/45/8 → 47/40/2.** NOT yet released (v2.61.2 still the current tag); `feature/folio-split` worktree + branch PRESERVED — Phase 4 (folio `/folio:do` + coordinated release, folio first / craft tag LAST) rides it. Phase 3.6 (router consolidations) CLOSED at **1 router of 5 planned**: `code:audit` shipped shrunk 5 cmds→2 (the `audit-router` skill, D12); `orch` (D11), `arch` (D6), and `ci` (D13) all DROPPED after reading the command bodies in full; `plan:feature` stayed excluded (pre-existing D2 lock). Root cause of the 4 drops, recorded in `docs/specs/GRILL-phase-3-6-router-consolidation-2026-07-15.md`: **the grill locked load-bearing decisions from line counts and flag names without reading the bodies** — every family that looked cohesive from surface metrics collapsed on a full read. D13 also withdrew its own planned `repo`-flag normalization as a premise artifact (no validator enforces cross-family flag consistency; the distinguishing test is *find the thing that fails if it's wrong*). Real defects fixed: 4 `category:` metadata mismatches (`ci/{fix,local}.md` declared `code`; `orch/{drive,workflow}.md` declared `orchestrate` against a nonexistent dir) — flagged by `help_file_validator._check_category_mismatch`, live-wired via `docs_update_orchestrator` → `/craft:docs:update`; a repo-wide sweep found the `orch` pair (pre-existing on dev). **Repo-wide category mismatches now 0.** T3.4 command floor 60→40 (`ci.yml:92`): the floor is a mass-deletion guardrail and had gone stale TWICE (86→60) while the real count fell 94→70→47, leaving it ABOVE the surface and hard-failing CI every run; rationale now commented in-file. `tests/test_git_shim_correctness.sh` INVERTED (not deleted) — it asserted `commands/git/*.md` were thin shims, but T3.5.2 completed that migration by deleting all 11, making the assertions unsatisfiable (7 FAIL on the branch, 21/21 on dev); it now guards the migration's END STATE (shims stay deleted · Operations 1–12 survive in `skills/dev/git/SKILL.md` · `issue-check` stays live · no unsanctioned command reappears), verified by 3 planted defects. `/craft:git:issue-check` KEPT as a live command (NOT folded into the git skill, reversing the initial plan): `tests/test_issue_check_unit.py` extracts its `classify_issue()` block from `commands/git/issue-check.md` and `exec()`s it, so the command file is the classifier's single source of truth — the same constraint as `commands/ci/triage.md` (D13). Git's directory-rename detection had silently relocated dev's new command into `skills/dev/git/references/` during the merge, which would have broken that test. Merge resolved 34 conflicts (~30 count-cascade, dev 95/45/8 vs branch 46/40/2) — every tracked count recomputed mechanically via `bump-version.sh --counts-only` rather than hand-resolved; the guard-script conflicts expected in `no-switch-guard.sh`/`install-guards.sh`/`version-check.sh` did NOT materialise. Prior: **PR #291 MERGED to dev 2026-07-16** (squash `88b221da9`): `ci-bash-suites` validator (`.claude-plugin/skills/validation/ci-bash-suites.md`) — closes the **gate-divergence** gap that caused two of #290's three CI blockers. `/craft:check` never ran the shell suites CI invokes directly because pytest doesn't collect them, so the local gate reported green for a surface it never touched; T3.5.2 shipped a regression citing "full pytest 2575 passed" while a CI-required bash suite had been red for days. Derives its suite list FROM `.github/workflows/*.yml` rather than hardcoding (craft has 22 `tests/*.sh`, CI runs **6** — the parse corrected a 4-suite assumption held all session); advisory in default mode, blocking in `release`, per the release-gated validator contract (`CRAFT_MODE` can't distinguish `--for pr` from `--for commit`). Verified by replaying the REAL T3.5.2 bug (pre-fix suite restored from `d41de18ee`) → caught. No count cascade (validators sit outside the counted surface). Also 2026-07-16: two stale claims fixed in `docs/commands.md` (`5034aef7d`) — a dry-run tip saying "46 commands" (the old TOTAL pasted into a dry-run claim; now 27) and "46 commands for documentation generation, validation, and site management" (now 2 — that sentence described a surface craft no longer has). Local `~/.claude/hooks/branch-guard.sh` re-synced via `install-guards.sh` (#290 merged the v4 hook text into dev, so the installed hook predated the repo and was advising a DELETED command, `/craft:git:guard disable branch-guard`) — this cleared the last known local test failure; dogfood 51 passed/0 failed. ⚠️ KNOWN, NOT FIXED: `docs/commands.md:10`'s "17 Categories" list is stale (`discovery-usage` demoted, `orchestrate`→`orch`, no `workflow` dir) but "category" has two defensible definitions that disagree — frontmatter-declared gives 10 (…`smart`), directories give a different 10 (…`utils`), and the current list matches neither; left for a decision rather than guessed at. Prior: Guard-hardening arc SHIPPED to dev 2026-07-15 — PR #287 (cumulative-cwd cd-target resolution, both guard hooks), PR #288 (guards.json write-race mkdir-lock, both writers), PR #289 (plan-orchestrator dispatch hardening); full detail in the 2026-07-15 Session Recap below. Counts unchanged 95/45/8; NOT yet released. Prior: PR #283 SHIPPED to dev 2026-07-14 (squash `057ea6785`): `/craft:git:issue-check ` — before implementing a fix requested by an open GitHub issue, checks whether the issue's premise still holds against current code (structured verdict: valid/moot/unclear, always cited evidence, never cached, advisory-only). Also gates `/craft:orch:drive` with a cheap `#NNN`-citation pre-filter and corrects `/recap`'s pointer copy. Built via `/craft:brainstorm` → `/craft:grill` (9 locked decisions in `docs/specs/GRILL-github-attention-triage-2026-07-14.md`; adversarial-review pass caught and corrected a factually-wrong dogfood target — issue #199 assumed moot, actually still open) → `/craft:plan` (plan-orchestrator, `ORCHESTRATE-github-attention-triage.md`) → `orchestrate-dispatch`. Dispatched agent completed all 5 phases correctly but silently failed to commit Phase 5 or update its own checkboxes before stopping mid-sentence — caught by the hang-detection cross-check (notification text vs. ORCHESTRATE file state), not a code failure; recovered by verifying git log + a fresh full suite run, fixing 5 newly-stale "94 commands" doc refs the count-cascade tooling missed, then committing + correcting checkboxes. PR CI initially failed on a genuine regression (broken relative link in the ORCHESTRATE file's own header, fixed same session) plus the 3 already-known `test_v115_adhd_enhancements` pre-existing failures; merged via `--admin` once confirmed unrelated. Counts 94→95/45/8; NOT yet released (v2.61.2 still current tag). Prior: Docs/website audit COMMITTED to dev 2026-07-09 (`d6aaa9096`) — 264 deleted-command refs stripped across 40 files, 5 whole-docs archived to `docs/archive/deleted-commands-v3/`, `mkdocs build --strict` clean. FOLIO SPLIT PLANNED (new initiative, supersedes dropped Phase 3): grill ledger `docs/specs/GRILL-folio-split-2026-07-09.md` (5 locked: tools+docs-standards scope · caller-based cut · drop DOCS from /do+hub · dup count tooling · history-preserving subtree, craft v4/folio v1) + `docs/plans/ORCHESTRATE-folio-split.md` (5 phases, P0 gates). P0 caller-audit RAN via dynamic Workflow (7 agents, adversarially verified, partition_sound): STAYS = docs:update + docs:changelog + site:deploy-as-shell; MOVES = 26; flagged docs:claude-md:* trio as craft-internal governance (likely stays) — partition NOT yet recorded into the plan docs. EXECUTION STARTED 2026-07-09 ("build auto"): worktree ~/.git-worktrees/craft/feature-folio-split live (T1.1 done, ORCHESTRATE + tasks/ moved to branch root); T1.2 folio repo creation gated on ask. ⚠️ ACCOUNT CI: zero-CI mode active across private repos since 2026-07-09 (craft is public = unaffected/free) — see memory `github-actions-zero-ci-strategy-2026-07` + savant .STATUS for the full record. Prior: Phase 1 native-first prune MERGED to dev 2026-07-09 — PR #279 (squash `4fa44fbb`): pruned 21 dead-namespace commands (115→94, −5533 lines) + excluded dead docs (specs/plans/archive) from the built mkdocs site. ⏹ NATIVE-FIRST THINNING REFUTED: a Stage-2 dynamic workflow (3 pilots × 4-axis gate + cross-plugin routing test) returned NO-GO on Phase 2 — the `do`/`hub` router structurally can't dispatch cross-plugin, AND 3/3 pilot replacements (native `/simplify`, `/code-review`, `agent-skills:ci-cd`) aren't ≥ craft on all 4 axes (weaker/auto-apply, third-party dep, or generate nothing). Phases 2 (cascade thinning) + 3 (`folio` docs/publishing split) DROPPED; folio only revisitable on docs-maintenance merits, grilled fresh. ADR-002 rich-body gate held mid-build: 3 of 24 "safe" deletions (`git:docs:refcard`, `check:gen-validator`, `workflow:insights`) carried unique logic → restored, not deleted (caught by `test_skill_referenced_commands_exist`). Verification gap fixed: pytest passed 2676 but CI's Validate-Plugin-Structure bash suite (`test_git_shim_correctness`) asserted deleted `git/{sync,git-recap}.md` exist → shim array updated (see memory `pytest-doesnt-cover-craft-ci-bash-suites`). Counts now 94/45/8 on dev; NOT yet released — v2.61.2 still the current tag. Full record: `docs/specs/SPEC-craft-native-first-breakup-2026-07-09.md` OUTCOME block. Worktree `feature/craft-native-first-breakup` removed + remote branch deleted 2026-07-09; local branch ref left stale (branch-guard blocks force-delete of squash-merged branch — documented deadlock, harmless). Prior: v2.61.2 SHIPPED 2026-07-09 — PR #275 (dev→main merge, tag v2.61.2) + PR #276 (CI fix): docs/NEWS.md release highlights page, CHANGELOG/VERSION-HISTORY v2.61.1 section, REFCARD.md version box corrected (2.61.0→2.61.2, counts 117→115), exclusions.txt archived-doc path fixed. PATCH bump. Docs Staleness + Markdown Linting GREEN; Validate Plugin Structure + Link Validation pending (known hang). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.2. Counts unchanged 115/45/8. Prior: v2.61.0 SHIPPED 2026-07-06 — PR #271 (dev→main merge `6c3e2e95`, tag v2.61.0): do.md dead-agent-dispatch removal + 24-doc sweep, `verify-surfaces.sh --report-only`/`--version` + 2 new legs (GitHub release, docs site) + release-rollback runbook, 4 confirmed branch-guard/no-switch-guard gaps closed, quota tooling removed (all from PR #270, squashed to dev pre-release), plus post-merge docs-audit fixes: 2 dead-agent surface-forms PR #270's hyphenated-only grep missed (`docs/index.md`'s feature grid, `skills/orchestrator-resilience/SKILL.md`'s fallback-dispatch example), a docs-staleness false positive (`orch.md`'s mode-table "4 agents"), and backfilled `CHANGELOG.md`/`docs/CHANGELOG.md` `[Unreleased]` entries missed during #270's own PR prep. MINOR bump. Full suite 2707 passed/1 pre-existing failure (`test_roadmap_orchestrator_enhancements`, stale `ROADMAP.md` path predating this release — root-caused and FIXED post-release in `dc6ee28a`, corrected to `docs/archive/ROADMAP.md`; this exact failure had forced `--admin` overrides on PR #270, PR #271, and `main` directly, three releases running). Merged via `--admin` (same pre-existing failure, confirmed by log each time). Release: https://github.com/Data-Wise/craft/releases/tag/v2.61.0 · PR: https://github.com/Data-Wise/craft/pull/271. Downstream: docs site live at v2.61.0 (confirmed via curl), tap formula confirmed v2.61.0 (live raw.githubusercontent fetch, not local clone), Homebrew Release + Aggregator Sync + Craft MCP Release workflows all SUCCESS. `dev` fast-forwarded to `main`'s merge commit, pushed. Counts unchanged 115/45/8. Also this session: grilled and CLOSED the orchestrate token-usage-hooks backlog (captured 2026-07-02) with NO build — two review passes (backend/architecture + interface/CLI-contract lenses) found hook 1 infeasible (no live token-introspection API exists for a prompt-driven orchestrator session), hook 3 structurally uncomputable for an in-progress run; the one surviving feasible piece (`--token-report`) wouldn't have fixed the retroactive-analysis problem that motivated the backlog, and duplicates the accounting-tooling category this same session already removed (quota tooling) — see `docs/specs/GRILL-orchestrate-token-usage-hooks-2026-07-06.md`. Prior: v2.60.0 SHIPPED 2026-07-05 — PR #269 (dev→main merge `763c5e8c`, tag v2.60.0): command-namespace reorganization (full grill-and-implement cycle, `docs/specs/SPEC-command-namespace-reorganization-2026-07-05.md`) — root promotions `/craft:next`/`/craft:done`/`/craft:refine`/`/craft:brief`/`/craft:brainstorm` (heavy real-world use); `orchestrate`→`orch` rename across ~110 files; `ci:` consolidation (`code:ci-fix`/`code:ci-local`→`ci:fix`/`ci:local`); new `docs:generate` router (9 generators, none merged — genuinely distinct logic); new `task:` namespace; `/craft:quota` deleted (folded into `/craft:orch` pre-flight); `code:desktop-watch` deleted (was a pure redirect shim); `adhd-guide` converted to a skill reference. Pre-flight caught and fixed 8 real test failures before release: 6 stale "117 commands" claims (actual 115) across dist/docs files not covered by `bump-version.sh --counts-only`'s file list, one hardcoded pre-rename path in `test_drive_command_in_nav`, and a missing TOC on the newly-moved `adhd-guide.md` (>300 lines, tripped the skill-standards release gate). MINOR bump. Full suite 2707 passed/0 failed/5 skipped; pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.60.0; homebrew-release workflow SUCCESS (tap formula confirmed v2.60.0 via live `gh api`, NOT the stale local `~/projects/dev-tools/homebrew-tap` clone which still read 2.58.0); aggregator PR #19 auto-merged in `Data-Wise/claude-plugins`; post-release sweep ALL CLEAN. Counts 115/45/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.60.0 · PR: https://github.com/Data-Wise/craft/pull/269. Prior: v2.59.0 SHIPPED 2026-07-04 — PR #260 (dev→main merge, tag v2.59.0): `plugin-audit` skill (44→45, cross-plugin command/skill collision detector, schema + false-positive fixes before merge, #237) + docs-site staleness hardening H1/H2/H3 (`docs.yml` live-site post-deploy version poll, stale-count guard extended to all of `docs/**/*.md`, #259). Counts 116/45/8 (later corrected to 115 in v2.60.0 after the `resume.md` deletion cascade settled). Prior: v2.58.0 SHIPPED 2026-07-02 — PR #251 (dev→main merge, tag v2.58.0): distribution-surface hardening — curl-pipe installer fixed to clone current `main` instead of a frozen v1.16.0 mirror (D1, 11 contract tests); homebrew-tap copy-based install (no symlinks); net-new `mcp/` TS MCP server (craft-mcp Desktop bridge, 3 read-only tools, DXT/.mcpb); esbuild audit-clear. Counts 116/44/8. Prior: v2.57.0 SHIPPED 2026-07-01 — PR #242 (dev→main merge `ec5797e6`, tag v2.57.0): grill attack-angles (#238 — 7 project-agnostic adversarial axes, angle 3 reads target project's own CLAUDE.md/ADRs at runtime; skill count 43→44) + orchestrate-family wave-1 (#239 — deleted fictional 523-line `resume.md` session-teleportation command, command count 117→116; thin-shimmed `orchestrate:plan` to `plan-orchestrator` skill) + doc-impact rubric extension (#241 — 4 new scored types Tutorial/API/Cookbook/Architecture-doc with tiered thresholds + arch-doc double-count subtraction rule + advisory Site Consistency checklist) + orchestrate-dispatch mode (#240 — 3rd `--output` value dispatching a background Agent from the live planning session, with confirm-before-dispatch gate, scoped concurrency cap, failure/hang detection at 2×-effort-estimate window, resumability, scoped `.STATUS` auto-write). MINOR bump (4 feat/refactor PRs). #240/#241 were background-agent dispatches using the very pattern #240 formalizes — monitoring protocol caught 1 self-introduced broken-link regression (a cross-tree link into `~/.claude/...` from this session's own BRAINSTORM doc, fixed on dev) + recovered a killed agent's completed-but-uncommitted Phase 3 work. A CHANGELOG merge-order conflict between #240/#241 (both added `[Unreleased]` entries) was resolved by rebase, keeping both. Pre-release-check initially reported doc-coverage as BLOCKING due to a macOS bash-3.2 `mapfile` incompatibility (false positive — confirmed PASS, 0 gaps, via Homebrew bash 5.x/zsh). Full suite 2072 passed/7 failed/13 errors (documented env-caused baseline, not regressions); pre-release-check PASSED; main CI GREEN (Craft CI + Deploy Documentation + Documentation Quality); live site confirmed v2.57.0; homebrew-release workflow SUCCESS (formula confirmed v2.57.0, desc "116 commands"); post-release sweep ALL CLEAN. Counts 116/44/8. Release: https://github.com/Data-Wise/craft/releases/tag/v2.57.0 · PR: https://github.com/Data-Wise/craft/pull/242. Prior: v2.56.0 SHIPPED 2026-07-01 — PR #234 (dev→main merge `43beb7b3`, tag v2.56.0): token-usage reduction (PR #232 squash `e6c24a27` — orchestrator model pinning, `/refine` 631→42 lines, `/brainstorm` redesign, new `command-skill-token-efficiency` skill) + release-tracking hardening (namespace-refactor token-cost go/no-go measured 68.3% reduction; broken `/usage` scheduled-trigger replaced with `ccusage`+`claude-monitor`; 2 cross-spec factual bugs fixed via a second grill pass). MINOR bump (feat: #232). All CI checks GREEN on first pass — no `--admin` needed (Validate Plugin Structure resolved cleanly this time). Full suite 2089 passed; pre-release-check PASSED; main CI GREEN; live site confirmed v2.56.0; homebrew-release workflow SUCCESS; post-release sweep ALL CLEAN. Counts 117/43/8. Prior: v2.54.0 SHIPPED 2026-06-27 — `/craft:workflow:brief` (117th command): 3-line action block generator (Next step / Watch out for / Connects to) with `--plan`/`--verbose`/`--show-context` flags; `--brief` integrated into `/craft:do` Step 5.5. 29 e2e + 12 dogfood tests. Main CI GREEN; docs deployed; homebrew tap updated (117 commands); post-release sweep CLEAN (getting-started.md fixed); all BLOCK surfaces aligned. Counts: 117 / 39 / 8. Prior: v2.53.0 SHIPPED 2026-06-26 — multi-surface-aware release (surface registry + aggregator CI propagation + Cowork verify leg + `/craft:dist:surfaces`, #218/#184) + docs-staleness fix (docs/plans/ excluded from count-consistency scan). MINOR bump (feat). All tests pass; main CI GREEN; docs deployed; homebrew tap updated (116 commands); post-release sweep ALL CLEAN; aggregator sync wired (all 4 satellite repos). Counts 116/39/8. Prior: v2.52.0 SHIPPED 2026-06-26 — interactive-by-default commands (#215) + default-on test/docs scaffolding (#216) (PR #217 dev→main merge `c5f704f3`, tag v2.52.0, `--admin` on stuck Validate-Plugin-Structure): grill/orchestrate AskUserQuestion model (Recommended-first + per-option consequence) + `--yes`/`--non-interactive`; refine default-on for brainstorm/do/plan:feature/grill (opt-in elsewhere) + `--yes` cascade + orchestrate Step 0.5 structured model; AND brainstorm/plan:feature/grill emit a tier-inferred test-plan + doc-scorer Documentation section BY DEFAULT (`--no-tests`/`--no-docs`; arch:plan/spec-review opt-in via `--tests`/`--docs`), logic in skills (brainstorm-insights/plan-orchestrator, deprecation-trap-safe + guarded), doc-scorer REUSED, lifecycle-split, count-cascade-excluded. Built spec→grill→plan→subagent-driven-TDD (2 plans, 7+8 tasks, per-task spec/quality + opus whole-branch reviews; gates caught a dropped-phrase regression, a refine over-flip, a decorative deprecation-guard sentinel, and a MEMORY.md over-compaction that dropped 37/102 pointers → all fixed). MINOR bump (4 feat). Full suite 1990 passed; pre-release-check PASSED; main CI GREEN; docs deployed (live site 2.52.0); Homebrew Release auto-updated tap; verify-surfaces 6/6 ALIGNED (refreshed local lag: brew formula 2.51→2.52 via `--formula`, marketplace-update-THEN-plugin-update for the Code pin); post-release sweep ALL CLEAN. Counts 115/39/8. Prior: v2.51.0 SHIPPED 2026-06-25 — governance retro (#205) + skill-standards release gate (PRs #211/#212→dev, #214 dev→main merge `180181f8`, tag v2.51.0; CLOSED #205 + #138 via default-branch auto-close): D1 **R09 status-not-drift** governance check (`governance/checks/status_drift.py`, warn) — audits `.STATUS` `version:` vs manifest + SHIPPED/tagged vX.Y.Z vs git tag, fixture `--selftest` + LIVE in `pre-release-check.sh` (this very release was self-gated by it: ".STATUS: version + release claims match" ✓); D3 ADR-003 (release-drift advisory not hard-gate + P7 freeze); skill-standards validator graduated to a **release-tier gate** (`CRAFT_MODE=release` blocks, advisory below) + fixed a latent CRAFT_MODE no-op (LLM-prose command never exported the gating var → gate+version-check silently dead). MINOR bump (2 feat: #211/#212). Full suite 1970 passed; pre-release-check PASSED; main CI GREEN; **verify-surfaces 6/6 ALIGNED** (after refreshing local lag: brew upgrade 2.50→2.51, `claude plugin update` 2.50→2.51, pulled stale `~/projects/dev-tools/homebrew-tap` checkout — the script reads that local clone FIRST, was the only "blocker"); post-release sweep ALL CLEAN. Counts 115/39/8. Process: merged #211/#212 (conflict-free despite branches behind dev — `--stat` looked conflicted but they authored ZERO changes to `.STATUS`/`CLAUDE.md`/`governance.md`; verified via `merge-tree --write-tree`), pushed a local-only gate branch first (data-loss risk), `git:clean` removed 2 worktrees (11+2 squash-merged branch refs left dangling — branch-guard blocks `-D`, harmless). Prior: v2.50.0 SHIPPED 2026-06-25 — v2.49.x issues sprint (PRs #206/#207/#208→dev, #209 dev→main --admin on phantom Validate Plugin Structure, tag v2.50.0): advisory skill-standards validator in `/craft:check` (hot-reload, never fails); homebrew dist-gates `verify_caveats.py`+`post_install_check.py` wired into `/release` Steps 10b/10c (advisory, strict via HOMEBREW_GATE_STRICT) + 10d (BLOCKING aggregator-sync + Cowork verify) (#200/#199); SessionEnd insights facet hook + idempotent installer, per-session-id dedup (#183); ADR-001 Workflow-vs-worktree (#171); +14 e2e/dogfood tests pinning locked decisions. Built via craft:grill (10 decisions D1–D10) → superpowers writing-plans (4 TDD tracks) → 2 dynamic Workflows (impl+verify, ran from dev session under branch-guard bypass, re-protected after). MINOR bump (3 feat: commits). Full suite 1958 passed; pre-release-check PASSED; main CI GREEN; tap+brew+Code+marketplace all 2.50.0 (verify-surfaces 6/6 ALIGNED); post-release sweep CLEAN. KEY INCIDENTS (recovered): (1) #206 squash folded UNPUSHED local-dev base commits → #207/#208 phantom conflicts → rebased --onto origin/dev to untangle (memory squash-folds-unpushed-dev-base-commits; push dev before PRing off it); (2) GitHub comma-list `Closes #200, #199, #183, #171` only auto-closed #200 (+#171 via Track-0 trailer) — each issue needs its OWN keyword; closed #183/#199 manually. Counts 115/39/8. Prior: v2.49.0 SHIPPED 2026-06-23 — skill-standards auditor + /done memory/settings + ADR-002 + release re-split (PR #203 auditor→dev, #204 release dev→main --admin, tag v2.49.0): `/craft:code:skill-standards` batch-audits `skills/**/SKILL.md` vs a vendored copy of Anthropic's standards (score+exit 0/1/2, `--json`/`--markdown`, `--fix` = version-tag-strip + frontmatter-normalize + TOC-stub, never prose; `--refresh-standards`); `/done` Memory Optimize (Step 1.12) + Settings Sync (Step 1.10.5); ADR-002 consolidated `/done` into the `adhd-workflow` skill (`references/done.md` SoT + thin shim); 4 oversized skills split to `references/` (release re-split loss-gated after a lossy first attempt, 0 lines lost) → 39 skills 100/100. Counts 114→115. Release: dev was unsynced with main (v2.48.0 release merge commits) → dev-wins sync merge resolved the PR #204 conflict; full suite 1929 passed; main CI GREEN; docs deployed; tap synced. POST-RELEASE TOOLING (homebrew-tap + savant): root-caused the local Code-registered lag — the formula `post_install` ran `claude plugin update` WITHOUT first `claude plugin marketplace update local-plugins` (no-op on a stale cache); fixed in `homebrew-tap/generator/generate.py` for ALL claude-plugin formulas; reconciled manifest-drift across 6 formulas (rforge-orchestrator's `deprecate!` was at risk of silent un-deprecation; himalaya-mcp 12 skills/22 MCP tools; scholar 28→34; craft `{command_count}` single-source token) gated by regen==committed; added a Formula Drift Guard CI workflow (regen-vs-committed, first run GREEN); savant `CLAUDE.md` 19→25 skills. Memories: post-install-marketplace-refresh-before-update, deprecated-command-rich-body-trap, skill-authoring-tooling-landscape. Prior: v2.48.0 SHIPPED 2026-06-23 (grill + orchestrate Step 0.5 Clarify). Prior: v2.47.0 SHIPPED 2026-06-21 — Governance roadmap COMPLETE (PR #196/#197→dev, #198→main, tag v2.47.0): R04 automated as content-drift (manual→checks/no_drifted_copy.py — installed SKILL.md must be byte-identical to canon; DISTINCT from R07 version-pin, catches a hand-edited body even when the version matches; gates session, vacuous-skip otherwise) + #184 release pre-flight advisory (pre-release-check.sh runs run_rules.py --json, prints RED count, NEVER blocks — gentle-ramp; TestReleaseGuard184 invariant) + live-flow test coverage (e2e soak lifecycle hook→ledger→promote-check + R04-in-audit; dogfood ledger edges). Governance suite 47→59. KEY FINDING (memory r04-content-drift-not-filetype): naïve "real-dir=copy" heuristic was dead (installs are real dirs, no symlinks) → content-drift; always fire a new WARN checker on the live env first (a false positive poisons the soak ledger). #198 merged CLEAN (no --admin). Homebrew tap synced to v2.47.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Governance now: R01–R04+R06 automated · R05 advisory · R07 external · R08; 5 surfaces (visibility·prevention·promotion·cross-repo·release-advisory). Prior: v2.46.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #3 (PR #194→dev, #195→main, tag v2.46.0): soak-then-flip promotion + cross-repo wrapper, completing Phase 2 (visibility · prevention · promotion · cross-repo). soak.py = LOCAL gitignored STATE.json ledger (first_seen/last_seen/last_red per rule) fed by the SessionStart hook each audit; run_rules.py --promote-check (--window 14d, --state) lists warn rules soaked clean long enough to recommend a human warn→error flip (machinery recommends, human promotes; advisory exit 0). run.sh = cross-repo wrapper (consumers invoke the one installed engine, cwd-portable, no drift, R07). 8 dogfood tests (injected today → no flake), 47 governance suite green. Bundled #193: test_performance_cached_run → xfail(strict=False) (wall-clock microbenchmark flaked on shared CI, forced --admin; now a soft signal) — and #195 MERGED CLEAN with NO --admin (the xfail paid off). Homebrew tap synced to v2.46.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. R04 automation + #184 release-pre-flight guard remain deferred. Prior: v2.45.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #2 (PR #190→dev, #192→main, tag v2.45.0): SessionStart visibility hook (governance/session_hook.py) — audits the live ~/.claude/skills tree at session open and injects a compact RED-only summary into context via SessionStart additionalContext; silent when clean, mtime-cached, no-op where skills tree absent, never breaks a session on error. Visibility not prevention (SessionStart hooks inject context, can't block). Completes the three-surface model: pre-commit + CI (prevention) · SessionStart (visibility). Implemented direct-to-worktree from the dev session (worktree-path-aware). #192 admin-merged (strict up-to-date BEHIND from the v2.44.0 merge-commit divergence; PR-context full suite GREEN). Homebrew tap synced to v2.45.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 / 39 / 8. Global install of the hook DEFERRED to post-v2.45.0 (point at released plugin path; docs in governance/README.md). PR #3 (soak-then-flip + cross-repo wrapper) + R04 deferred. Also: removed leaked ORCHESTRATE-guard-suite.md (PR #191). Prior: v2.44.0 SHIPPED 2026-06-21 — Governance Phase 2 PR #1 (PR #188→dev, #189→main, tag v2.44.0): R03 automated (manual→script — checks/no_private_in_public_marketplace.py scans a marketplace manifest, fails on a public-marketplace ref to a private/PII repo, denylist {savant}, dual-mode file/dir + good/bad fixtures; closes the last error-rule enforcement gap), pre-commit governance-gate hook (^governance/ → selftest + render --check drift) + CI gate in ci.yml (with explicit "CI does NOT evaluate R01/R07 live-env" note), engine {marketplace} substitution through audit()+selftest() + --marketplace. Built from a draft→adversarial-verify→ultra-finalize workflow chain (verified packet); implemented directly into the worktree from the dev session (branch-guard is worktree-path-aware — corrected a stale assumption). Also corrected stale "not yet wired" governance docs. #189 admin-merged (only failure was the flaky test_performance_cached_run timing test, unrelated to the diff; PR-context full suite GREEN). Homebrew tap synced to v2.44.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 PR #2 (SessionStart hook + soak-then-flip + cross-repo wrapper) NEXT; R04 deferred. Prior: v2.43.0 SHIPPED 2026-06-20 — Skill-Ecosystem Governance (Phase 0) (PR #185→dev, #187→main, tag v2.43.0): policy-as-code under governance/ — RULES.yaml (8 skill-location rules, single source of truth), run_rules.py (audit + --selftest meta-validation, fail-closed), render_rules.py (CLAUDE.md block generation + --check drift gate), portable checkers + good/bad fixtures, guide + Plugin Release Runbook, 24 e2e/dogfood tests + governance pytest marker. PR #185 review hardening (all 5 issues): fail-closed gating (broken checker on error rule exits 1), recursive broken-symlink detection, R01 gates session (not ci) + visible canon-skip, --check exit-2, --selftest surfaces external rules, UTF-8. Homebrew tap synced to v2.43.0, main CI GREEN, post-release sweep ALL CLEAN. Counts: 113 commands / 39 skills / 8 agents. Phase 2 enforced-gates roadmap spec open in PR #186. Prior: v2.42.0 SHIPPED 2026-06-19 — /craft:code:fewer-prompts curated read-only Bash allowlist (PR #181). Prior: v2.41.1 SHIPPED 2026-06-19 — Documentation Coverage (PR #180): 25 tutorial stubs for all Phase 8 commands + broken link fix in TUTORIAL-smart-help.md. Prior: v2.41.0 SHIPPED 2026-06-19 — token-efficiency (quota gate, engine flag, parity runbook). v2.40.0 SHIPPED 2026-06-19 — Guard Suite (PR #174): /craft:git:guard + guards.json registry & ADHD toggle UX, no-switch-guard promoted to scripts/, guard-consistency validator, install-guards.sh. Destructive-restore reconciled to no-switch-guard; branch-guard rm-rf-.git/reset--hard softened to confirm (catastrophic ops stay NON-muteable). 111 commands. Review found 1 CRITICAL (catastrophic-bypass) + 6 issues — ALL fixed pre-merge; +5 registry tests; ci.yml now installs both guards + runs the no-switch suite. Prior: v2.39.0 SHIPPED 2026-06-19 — doc-coverage enforcement (PR #172→dev, #173→main). Prior: v2.38.2 SHIPPED 2026-06-15 — multi-surface release arc (3 patches). v2.38.0: multi-surface engine (verify-surfaces Step 13.6, cache-prune Step 13.7, Data-Wise aggregator marketplace) + branch-guard draft=dev (PRs #158/#159/#160/#161). Adversarial review found 3 HIGH + 6 MEDIUM/LOW bugs → v2.38.1 (PR #163: branch-aware dogfood tests fixed RED main CI; cache-prune data-loss; verify-surfaces git-tag no-op) → v2.38.2 (PR #165: corrupt-surface blocks, symlink GC, arg-parse, subshell). All 3 → main, main CI GREEN, tap + aggregator (Data-Wise/claude-plugins) pinned to 2.38.2, post-release sweep CLEAN. Counts: 112 commands / 39 skills / 8 agents. Prior: v2.37.0 SHIPPED 2026-06-13 — drift-prevention chain + CI triage/watch. Bundled brainstorm items 1-7 (PRs #151/#152/#153/#154/#155): #1 strict-markers guard + #4 workflow recipe discoverability; #7 bump-version categorical-subtotal sweep; #2 CI count-drift tripwire (README live counts); #5 /craft:ci:triage + #6 /craft:ci:watch. PR #156 → main 8c21a54d (merge commit), release tag v2.37.0. Downstream all GREEN: main CI verified, homebrew-release.yml updated tap to v2.37.0 (desc "112 commands", url v2.37.0.tar.gz). Post-release sweep ALL CLEAN (drift tooling paid off). Counts: 112 commands / 39 skills / 8 agents. Prior: v2.36.0 SHIPPED 2026-06-13 (PR #150 → main eb7e143d). diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 0b5a00e2b..51b3a2c24 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "Full-stack developer toolkit for Claude Code with 48 commands, 2 agents, and 41 skills", - "version": "4.5.0" + "version": "4.6.0" }, "plugins": [ { @@ -16,7 +16,7 @@ "repo": "Data-Wise/craft" }, "description": "Full-stack developer toolkit for Claude Code with 48 commands, 2 agents, and 41 skills", - "version": "4.5.0", + "version": "4.6.0", "author": { "name": "Data-Wise", "email": "dt@stat-wise.com" diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 23262935a..16615754b 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "craft", - "version": "4.5.0", + "version": "4.6.0", "description": "Full-stack developer toolkit with integrated workflow automation - 48 commands, 2 agents, 41 skills with standardized dry-run preview mode (29 with --dry-run support). Code, git, site, docs, testing, architecture, CI, distribution, teaching workflows, planning, release pipeline, and ADHD-friendly features including brainstorming, task management, and spec capture", "author": { "name": "Data-Wise", diff --git a/CHANGELOG.md b/CHANGELOG.md index 489ba4eb3..a2689980e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,38 +11,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +--- + +## [4.6.0] - 2026-08-15 + ### Added - **Two prose-staleness checks in `docs-staleness-check.sh` Phase 7** ([SPEC](docs/specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md), - [ADR-007](docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md)) — a release-date check - (claims near the current version token must be within a day of that version's git tag) and a - count-prose check scoped to four structured line shapes (version box, TL;DR line, bolded - count-summary badge, structure-table row). The count check reads the **singular** noun form - too, closing the gap that let `CLAUDE.md`'s "8 agent definitions" read GREEN for five minors. - Both emit `warning`, per ADR-007's gentle-ramp rationale. -- **Prose-staleness test harness** — `tests/fixtures/prose-staleness/` (10 fixtures across - clean / planted-defect / known-false-positive) and `tests/test_docs_staleness_prose.py`, a - table-driven runner asserting on `--json` findings. Includes a guard that every check keeps a - planted-defect fixture, and a live-repo assertion that `count_consistency` stays clean. + [ADR-007](docs/adr/ADR-007-pattern-scoped-prose-staleness-gating.md), + [#334](https://github.com/Data-Wise/craft/pull/334), + [#335](https://github.com/Data-Wise/craft/pull/335)) — a release-date check and a count-prose + check scoped to four structured line shapes (version box, TL;DR line, bolded count-summary + badge, structure-table row). The count check reads the **singular** noun form too, closing the + gap that let `CLAUDE.md`'s "8 agent definitions" read GREEN for five minors. + **Hardened same day (#335)** after a high-effort review found 9 defects, 2 HIGH, in #334's + initial ship: the release-date check was redesigned from a single git-tag authority (unreachable + in CI, doesn't exist at release time) to **cross-file consistency** — every release-date claim + in the repo compared against every other claim, majority wins, window only opens on a heading or + version-box line, never a bare prose mention. Promoted to `error` once a clean live-repo run + (0 findings, both real claim sites reached) confirmed it. The count-prose matcher's boundary + class was fixed so hyphenated compounds (`command-line`, `agent-facing`) and markdown-bold + badges (`**48 commands**`) both classify correctly instead of misreading as counts or evading + the broad drift-tripwire scan; the offered auto-fix now anchors to the full matched span so it + can no longer corrupt surrounding prose. Check 2 (count-prose) stays `warning`. +- **Prose-staleness test harness** — `tests/fixtures/prose-staleness/` (18 fixtures across + clean / planted-defect / known-false-positive) and `tests/test_docs_staleness_prose.py` + (33 tests), a table-driven runner asserting on `--json` findings. Includes a guard that every + check keeps a planted-defect fixture, and a live-repo assertion that `count_consistency` stays + clean. ### Fixed +- **`docs-staleness-check.sh` pass 2 `[e]xclude` was a silent no-op for every Phase 7/9 finding** + — a finding's `file` field was glued `path:lineno`, breaking `is_pattern_excluded`'s + first-colon split. Split into separate `file`/`locator` fields on the finding record (predates + #334; closed alongside it). - **`docs-staleness-check.sh` pass 2 `[f]ix` applied nothing while reporting success** — it printed `-> Fixed` and incremented the counter without touching the file. Pass 1 had the same bug fixed earlier (BSD `sed -i` exits 0 on no match); the sibling pass was left behind. Both now share one `apply_line_fix`, which reports success only when the file actually changed and refuses to execute a non-substitution `fix_detail`. -- **Release-date window was one line short of its documented contract** — the decrement ran on the - version-mention line itself, so it scanned that line plus only 3 more instead of 4. - **An unparseable release-date authority flagged every claim in the repo** instead of skipping. - The check is now vacuous unless its authority parsed, matching the no-tag case. + The check is now vacuous unless it can establish a comparison, matching the no-tag case. +- **A broad, unscoped count scan shared the same hyphen-boundary bug** as the shaped-line check + (`"7 agents-only"` misread as `"7 agents"`) and, separately, never matched markdown-bold badges + (`**777 commands**`) — both share one boundary-character constant now. - `docs/specs/REVIEW-repo-triage-2026-08-07.md` pointed at a GRILL doc that `b1c4426e4` had archived, failing `test_no_broken_links` on every branch since. - `CLAUDE.md`'s Project Structure table claimed 8 agent definitions (actual: 2 since v4.0.0); `README.md`'s highlight block still headlined v2.36.0 at v4.5.0, and its tagline carried a stale hard test count. +### Removed + +- **Teaching Mode feature** — dead since the v4 command-prune split craft's teaching surface out + to the `scholar` plugin; residual references removed. + --- ## [4.5.0] - 2026-08-07 diff --git a/CLAUDE.md b/CLAUDE.md index 0f3dc0519..7a9b16649 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ > `/craft:finish` — renamed from `/craft:done` (ADR-006, v4.2.0, breaking) · `orchestrate:drive` — spec-driven autonomous /goal loop · `prompt-refiner` skill + `--refine` flag (9 commands) · `/craft:restore` — combined git+`.STATUS` recap -**Current Version:** v4.5.0 | **Tests:** full suite via `python3 -m pytest tests/` · tiers via `/craft:test ` +**Current Version:** v4.6.0 | **Tests:** full suite via `python3 -m pytest tests/` · tiers via `/craft:test ` > For project details, see `plugin.json` description and `scripts/validate-counts.sh` diff --git a/README.md b/README.md index 9ae6934d0..7d76db945 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ **main:** [![Craft CI](https://github.com/Data-Wise/craft/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Data-Wise/craft/actions/workflows/ci.yml) [![Deploy Docs](https://github.com/Data-Wise/craft/actions/workflows/docs.yml/badge.svg)](https://github.com/Data-Wise/craft/actions/workflows/docs.yml) **dev:** [![Craft CI](https://github.com/Data-Wise/craft/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/Data-Wise/craft/actions/workflows/ci.yml) [![Documentation Quality](https://github.com/Data-Wise/craft/actions/workflows/docs-quality.yml/badge.svg?branch=dev)](https://github.com/Data-Wise/craft/actions/workflows/docs-quality.yml) -[![Version](https://img.shields.io/badge/version-4.5.0-brightgreen.svg)](https://github.com/Data-Wise/craft/releases) +[![Version](https://img.shields.io/badge/version-4.6.0-brightgreen.svg)](https://github.com/Data-Wise/craft/releases) > **Docs/publishing commands moved to [`folio`](https://github.com/Data-Wise/folio)** — see > [MIGRATION-v4.md](docs/MIGRATION-v4.md) for the old-command → new-location table. diff --git a/commands/hub.md b/commands/hub.md index 8c73508b2..097d4284a 100644 --- a/commands/hub.md +++ b/commands/hub.md @@ -171,7 +171,7 @@ Display template: ``` ┌─────────────────────────────────────────────────────────────────────────┐ -│ CRAFT - Full Stack Developer Toolkit v4.5.0 │ +│ CRAFT - Full Stack Developer Toolkit v4.6.0 │ │ [PROJECT_NAME] ([PROJECT_TYPE]) on [GIT_BRANCH] │ │ {stats['total']} commands | {skill_count} skills | {agent_count} agents | {test_count} tests passing │ ├─────────────────────────────────────────────────────────────────────────┤ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ec0328729..7458e1b50 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -9,38 +9,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +--- + +## [4.6.0] - 2026-08-15 + ### Added - **Two prose-staleness checks in `docs-staleness-check.sh` Phase 7** ([SPEC](specs/SPEC-doc-staleness-prose-gaps-2026-08-07.md), - [ADR-007](adr/ADR-007-pattern-scoped-prose-staleness-gating.md)) — a release-date check - (claims near the current version token must be within a day of that version's git tag) and a - count-prose check scoped to four structured line shapes (version box, TL;DR line, bolded - count-summary badge, structure-table row). The count check reads the **singular** noun form - too, closing the gap that let `CLAUDE.md`'s "8 agent definitions" read GREEN for five minors. - Both emit `warning`, per ADR-007's gentle-ramp rationale. -- **Prose-staleness test harness** — `tests/fixtures/prose-staleness/` (10 fixtures across - clean / planted-defect / known-false-positive) and `tests/test_docs_staleness_prose.py`, a - table-driven runner asserting on `--json` findings. Includes a guard that every check keeps a - planted-defect fixture, and a live-repo assertion that `count_consistency` stays clean. + [ADR-007](adr/ADR-007-pattern-scoped-prose-staleness-gating.md), + [#334](https://github.com/Data-Wise/craft/pull/334), + [#335](https://github.com/Data-Wise/craft/pull/335)) — a release-date check and a count-prose + check scoped to four structured line shapes (version box, TL;DR line, bolded count-summary + badge, structure-table row). The count check reads the **singular** noun form too, closing the + gap that let `CLAUDE.md`'s "8 agent definitions" read GREEN for five minors. + **Hardened same day (#335)** after a high-effort review found 9 defects, 2 HIGH, in #334's + initial ship: the release-date check was redesigned from a single git-tag authority (unreachable + in CI, doesn't exist at release time) to **cross-file consistency** — every release-date claim + in the repo compared against every other claim, majority wins, window only opens on a heading or + version-box line, never a bare prose mention. Promoted to `error` once a clean live-repo run + (0 findings, both real claim sites reached) confirmed it. The count-prose matcher's boundary + class was fixed so hyphenated compounds (`command-line`, `agent-facing`) and markdown-bold + badges (`**48 commands**`) both classify correctly instead of misreading as counts or evading + the broad drift-tripwire scan; the offered auto-fix now anchors to the full matched span so it + can no longer corrupt surrounding prose. Check 2 (count-prose) stays `warning`. +- **Prose-staleness test harness** — `tests/fixtures/prose-staleness/` (18 fixtures across + clean / planted-defect / known-false-positive) and `tests/test_docs_staleness_prose.py` + (33 tests), a table-driven runner asserting on `--json` findings. Includes a guard that every + check keeps a planted-defect fixture, and a live-repo assertion that `count_consistency` stays + clean. ### Fixed +- **`docs-staleness-check.sh` pass 2 `[e]xclude` was a silent no-op for every Phase 7/9 finding** + — a finding's `file` field was glued `path:lineno`, breaking `is_pattern_excluded`'s + first-colon split. Split into separate `file`/`locator` fields on the finding record (predates + #334; closed alongside it). - **`docs-staleness-check.sh` pass 2 `[f]ix` applied nothing while reporting success** — it printed `-> Fixed` and incremented the counter without touching the file. Pass 1 had the same bug fixed earlier (BSD `sed -i` exits 0 on no match); the sibling pass was left behind. Both now share one `apply_line_fix`, which reports success only when the file actually changed and refuses to execute a non-substitution `fix_detail`. -- **Release-date window was one line short of its documented contract** — the decrement ran on the - version-mention line itself, so it scanned that line plus only 3 more instead of 4. - **An unparseable release-date authority flagged every claim in the repo** instead of skipping. - The check is now vacuous unless its authority parsed, matching the no-tag case. + The check is now vacuous unless it can establish a comparison, matching the no-tag case. +- **A broad, unscoped count scan shared the same hyphen-boundary bug** as the shaped-line check + (`"7 agents-only"` misread as `"7 agents"`) and, separately, never matched markdown-bold badges + (`**777 commands**`) — both share one boundary-character constant now. - `docs/specs/REVIEW-repo-triage-2026-08-07.md` pointed at a GRILL doc that `b1c4426e4` had archived, failing `test_no_broken_links` on every branch since. - `CLAUDE.md`'s Project Structure table claimed 8 agent definitions (actual: 2 since v4.0.0); `README.md`'s highlight block still headlined v2.36.0 at v4.5.0, and its tagline carried a stale hard test count. +### Removed + +- **Teaching Mode feature** — dead since the v4 command-prune split craft's teaching surface out + to the `scholar` plugin; residual references removed. + --- ## [4.5.0] - 2026-08-07 diff --git a/docs/DEPENDENCY-ARCHITECTURE.md b/docs/DEPENDENCY-ARCHITECTURE.md index b2fd2e6c1..0a23e7825 100644 --- a/docs/DEPENDENCY-ARCHITECTURE.md +++ b/docs/DEPENDENCY-ARCHITECTURE.md @@ -761,5 +761,5 @@ flowchart TB --- **Last Updated**: 2026-02-19 -**Version**: 4.5.0 +**Version**: 4.6.0 **Status**: Production Ready diff --git a/docs/NEWS.md b/docs/NEWS.md index 0bbb56154..e7b1801b4 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -4,6 +4,27 @@ Release announcements and notable changes for the Craft plugin. --- +## v4.6.0 — prose-staleness checks, hardened same day + +**Released:** 2026-08-15 · **Type:** Minor + +### Highlights + +- **Two new prose-staleness checks in `docs-staleness-check.sh` Phase 7** + ([#334](https://github.com/Data-Wise/craft/pull/334)) — a release-date consistency check and a + count-prose check scoped to four structured line shapes, closing a blind spot where + `CLAUDE.md`'s stale "8 agent definitions" read GREEN for five minor releases. +- **Hardened the same day** ([#335](https://github.com/Data-Wise/craft/pull/335)) after a + high-effort review found 9 defects (2 HIGH) in #334's initial ship. The release-date check was + redesigned from a single git-tag authority — unreachable in CI and nonexistent at release time + — to cross-file consistency, then promoted to `error` once a clean live-repo run confirmed it. + The count-prose matcher's boundary handling was fixed so hyphenated compounds and markdown-bold + badges both classify correctly. +- **Teaching Mode feature removed** — dead since the v4 command-prune split craft's teaching + surface out to the `scholar` plugin. + +--- + ## v4.5.0 — repo-triage skill + ecosystem-tool tutorials **Released:** 2026-08-07 · **Type:** Minor diff --git a/docs/REFCARD.md b/docs/REFCARD.md index f8dba434e..c58235ac9 100644 --- a/docs/REFCARD.md +++ b/docs/REFCARD.md @@ -4,11 +4,11 @@ ┌─────────────────────────────────────────────────────────────┐ │ CRAFT PLUGIN QUICK REFERENCE │ ├─────────────────────────────────────────────────────────────┤ -│ Version: 4.5.0 (v4.5.0, released 2026-08-07) │ +│ Version: 4.6.0 (v4.6.0, released 2026-08-15) │ │ Commands: 48 | Agents: 2 | Skills: 41 │ │ Tests: python3 -m pytest tests/ │ │ Docs: https://data-wise.github.io/craft/ │ -│ v4.5.0: repo-triage skill + ecosystem-tool tutorials │ +│ v4.6.0: prose-staleness checks, hardened same day │ └─────────────────────────────────────────────────────────────┘ ``` diff --git a/docs/commands/hub.md b/docs/commands/hub.md index 93b22e9aa..2b3e543de 100644 --- a/docs/commands/hub.md +++ b/docs/commands/hub.md @@ -171,7 +171,7 @@ Display template: ``` ┌─────────────────────────────────────────────────────────────────────────┐ -│ CRAFT - Full Stack Developer Toolkit v4.5.0 │ +│ CRAFT - Full Stack Developer Toolkit v4.6.0 │ │ [PROJECT_NAME] ([PROJECT_TYPE]) on [GIT_BRANCH] │ │ {stats['total']} commands | {skill_count} skills | {agent_count} agents | {test_count} tests passing │ ├─────────────────────────────────────────────────────────────────────────┤ diff --git a/docs/index.md b/docs/index.md index 1e86f90b1..0377f19af 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,7 +5,7 @@ [![Docs](https://github.com/Data-Wise/craft/actions/workflows/docs.yml/badge.svg?branch=dev)](https://github.com/Data-Wise/craft/actions/workflows/docs.yml) [![Homebrew Release](https://github.com/Data-Wise/craft/actions/workflows/homebrew-release.yml/badge.svg?branch=dev)](https://github.com/Data-Wise/craft/actions/workflows/homebrew-release.yml) [![Validate Dependencies](https://github.com/Data-Wise/craft/actions/workflows/validate-dependencies.yml/badge.svg?branch=dev)](https://github.com/Data-Wise/craft/actions/workflows/validate-dependencies.yml) -[![Version](https://img.shields.io/badge/version-4.5.0-brightgreen.svg)](https://github.com/Data-Wise/craft/releases) +[![Version](https://img.shields.io/badge/version-4.6.0-brightgreen.svg)](https://github.com/Data-Wise/craft/releases) | Branch | CI | Docs | |--------|----|----- | @@ -221,7 +221,7 @@ Reference documentation for all 48 Craft commands: | **Site** | 1 | Deploy to GitHub Pages (site build/status/check moved to `folio` in v4.0.0) | | **Total** | **48** | **Complete development workflow coverage** | -!!! info "Latest: v4.5.0 — repo-triage skill + ecosystem-tool tutorials" +!!! info "Latest: v4.6.0 — repo-triage skill + ecosystem-tool tutorials" New `repo-triage` skill batch-grounds open GitHub issues and stale worktrees/branches against current repo state, offering confirmed (never automatic) deletion/closure. 8 new cheat-sheet tutorials cover installed ecosystem tools (Codex, Remember, ADHD Mode, Token Optimizer, diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 4efbe8fe0..7dd14dcad 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -96,7 +96,7 @@ Execution modes can be customized per project. Version is managed across 13 files atomically using `bump-version.sh`: ```bash -./scripts/bump-version.sh 4.5.0 # Full bump +./scripts/bump-version.sh 4.6.0 # Full bump ./scripts/bump-version.sh --verify # Check for drift ./scripts/bump-version.sh --counts-only # Sync counts only ``` diff --git a/mkdocs.yml b/mkdocs.yml index 420460517..5e198bf0d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: Craft Plugin -site_description: "Full-stack developer toolkit - 48 commands, 2 agents, 41 skills for code, git, site, docs, testing, architecture, CI, distribution, and planning with ADHD-friendly interfaces. v4.5.0 adds /craft:finish (renamed from /craft:done, ADR-006). See NEWS.md for release-by-release highlights." +site_description: "Full-stack developer toolkit - 48 commands, 2 agents, 41 skills for code, git, site, docs, testing, architecture, CI, distribution, and planning with ADHD-friendly interfaces. v4.6.0 adds /craft:finish (renamed from /craft:done, ADR-006). See NEWS.md for release-by-release highlights." site_url: https://data-wise.github.io/craft/ repo_name: Data-Wise/craft diff --git a/package.json b/package.json index b70fbabc1..afd0cdfd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@data-wise/claude-craft-plugin", - "version": "4.5.0", + "version": "4.6.0", "description": "Full-stack developer toolkit - 48 commands, 2 agents, 41 skills for code, git, site, docs, testing, architecture, CI, distribution, teaching workflows, and planning with ADHD-friendly interfaces", "main": "index.js", "scripts": {