Skip to content

Commit ffe3cc0

Browse files
authored
v0.18.18.0 test: Track 6B — Track 5A test follow-ups (#73)
* test(audit): doc-type math boundary tests (Track 6B) Five new tests pinning the heuristic at its actual decision points: - empty file (0 content lines, skip) - 4 content lines all checkboxes (under MIN_CONTENT_LINES, skip) - density 0.4 (4/10, below 0.5 threshold, skip) - density 0.5 EXACTLY (5/10, fires per `>=`) - density 0.6 (6/10, fires) 10-line denominators are required to land at 0.4 / 0.5 / 0.6 cleanly -- a 5-line denominator only produces {0, 0.2, 0.4, 0.6, 0.8, 1.0}, which misses the 0.5 boundary entirely. Also folds in a mkdir-p assertion in the existing plantuml-fence test so the implicit "destination parent missing" branch can't regress to a bare git mv. * test(update): post-upgrade path-1 resolution + cache-clear (Track 6B) Adds opt-in fixture variant `createFixtureRepoWithRealSetup` that copies the real setup + bin/lib/install-safety.sh + empty placeholder skill .md files into a fixture repo, so update-run's post-pull setup invocation actually rebuilds ~/.claude/skills/{name}/SKILL.md symlinks. Kept separate from createFixtureRepo so the lighter happy/branch-switch/dirty/diverged scenarios don't take on install-safety + bun-on-PATH dependencies. Four assertions after update-run completes: 1. UPGRADE_OK fires (sanity). 2. Path-1 holds a symlink (not regular file/missing). 3. readlinkSync target resolves into the fixture (not ROOT -- proves no developer-home leakage through unscoped env vars). 4. CP#3 readlink-chain bash probe yields $_EXTEND_ROOT = fixture root. CP#3 probe spawn is hardened: env scoped to { HOME, PATH } (no process.env spread that would inherit GSTACK_EXTEND_*/BASH_ENV/PWD), cwd pinned to homeDir (so the fallback `.claude/skills/...` readlink can't match a stale workspace-local .claude/ directory), and explicit exit-0 + non-empty-stdout guards before realpathSync to defend against the failure mode where `set -u` plus command substitution silently yields an empty $_SKILL_SRC. Also folds in cache-clear seeding in the existing happy-path beforeAll: pre-seeds last-update-check and update-snoozed in stateDir before the update-run invocation, then asserts both files are removed. Previously the implicit existsSync(...).toBe(false) would have passed even if update-run did nothing. * chore: bump version and changelog (v0.18.18.0) * docs: update ROADMAP + PROGRESS for v0.18.18.0 (Track 6B shipped)
1 parent bdec07b commit ffe3cc0

7 files changed

Lines changed: 327 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.18.18.0] - 2026-05-10
6+
7+
### Added: Track 6B — Track 5A test follow-ups
8+
9+
Closes two test-coverage gaps surfaced during /ship of v0.18.14.0.
10+
11+
- **Post-upgrade path-1 resolution integration test** in `tests/update.test.ts`. New opt-in fixture variant `createFixtureRepoWithRealSetup` copies the real `setup` + `bin/lib/install-safety.sh` + empty placeholder skill `.md` files into a fixture repo so `update-run`'s post-pull `setup` invocation actually rebuilds `~/.claude/skills/{name}/SKILL.md` symlinks. Four assertions after `update-run` completes: `UPGRADE_OK` fires, path-1 holds a symlink, the symlink target resolves into the fixture (not ROOT — proves no developer-home leakage), and the CP#3 readlink-chain probe yields `$_EXTEND_ROOT` = fixture root. Bash probe runs with scoped `env: { HOME, PATH }` only (no `process.env` spread) and `cwd: homeDir`, so the test cannot pass for the wrong reason via inherited developer env or a stale workspace-local `.claude/` directory. Probe also asserts exit 0 + non-empty stdout to defend against the failure mode where `set -u` + command substitution silently produces an empty `$_SKILL_SRC`.
12+
13+
- **Doc-type math boundary tests** in `tests/checks-doc-type.test.ts`. Five new tests pin the heuristic at its actual decision boundaries: empty file (0 content lines, skip), 4 content lines all checkboxes (under `MIN_CONTENT_LINES`, skip), density 0.4 (4/10, below `>=` 0.5 threshold, skip), density 0.5 EXACTLY (5/10, fires per `>=`), density 0.6 (6/10, fires). Fixtures use 10-line content so 4/10·5/10·6/10 land at the 0.4·0.5·0.6 boundary cleanly — a 5-line denominator can only produce {0, 0.2, 0.4, 0.6, 0.8, 1.0}, which misses 0.5 entirely.
14+
15+
### Changed: cache-clear assertions in update-run happy-path test now seeded
16+
17+
The existing happy-path test now pre-seeds `last-update-check` and `update-snoozed` in `stateDir` before invoking `update-run`, then asserts both files are removed. Previously the implicit `existsSync(...).toBe(false)` would have passed even if `update-run` did nothing (the files never existed to begin with).
18+
19+
### Changed: plantuml fence test pins `mkdir -p` prefix
20+
21+
The existing plantuml-fence design-mismatch test now asserts `mkdir -p 'docs/designs' && ` appears in the suggestion. Previously the test only matched the destination filename, so removing the `mkdir -p` branch from `suggestionFor` would have left the test green.
22+
523
## [0.18.17.0] - 2026-05-10
624

725
### Added: `/roadmap-new` Group/Track ID-renames mapping at apply time

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.18.17.0
1+
0.18.18.0

docs/PROGRESS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Graft gstack's consistency patterns into extend's three daily-use skills (pair-r
66

77
| Version | Date | Summary |
88
|---------|------|---------|
9+
| 0.18.18.0 | 2026-05-10 | Track 6B — Track 5A test follow-ups. Test-only ship (~318 LOC across `tests/update.test.ts` + `tests/checks-doc-type.test.ts`); no production code touched. **Added:** opt-in `createFixtureRepoWithRealSetup` helper that copies the real `setup` + `bin/lib/install-safety.sh` + empty placeholder skill `.md` files into a fixture repo so `update-run`'s post-pull `setup` invocation actually rebuilds `~/.claude/skills/{name}/SKILL.md` symlinks. Four post-`update-run` assertions: `UPGRADE_OK` fires, path-1 holds a symlink, the symlink target resolves into the fixture (not ROOT — proves no developer-home leakage), and the CP#3 readlink-chain probe yields `$_EXTEND_ROOT` = fixture root. Probe runs with scoped `env: { HOME, PATH }` only (no `process.env` spread) and `cwd: homeDir`, so the test cannot pass for the wrong reason via inherited env or a stale workspace-local `.claude/`. Probe also asserts exit 0 + non-empty stdout to defend against the failure mode where `set -u` + command substitution silently produces an empty `$_SKILL_SRC`. **Added:** five doc-type math boundary tests in `tests/checks-doc-type.test.ts` — empty file (0 lines, skip), 4 content lines all checkboxes (under `MIN_CONTENT_LINES`, skip), density 0.4 (4/10, below `>=` 0.5 threshold, skip), density 0.5 EXACTLY (5/10, fires per `>=`), density 0.6 (6/10, fires). Fixtures use 10-line content so 4/10·5/10·6/10 land at the 0.4·0.5·0.6 boundary cleanly (a 5-line denominator can only produce {0, 0.2, 0.4, 0.6, 0.8, 1.0}, which misses 0.5 entirely). **Changed:** the existing update-run happy-path test now pre-seeds `last-update-check` and `update-snoozed` in `stateDir` before invoking `update-run`, then asserts both files are removed — previously the implicit `existsSync(...).toBe(false)` would have passed even if `update-run` did nothing. **Changed:** the plantuml-fence design-mismatch test now asserts the full `mkdir -p 'docs/designs' && ` prefix appears in the suggestion (was filename-only, so removing the `mkdir -p` branch would have left the test green). Closes the two test-coverage gaps surfaced during /ship of v0.18.14.0. Group 6 remains in progress (Tracks 6A + 6C unshipped). |
910
| 0.18.15.0 | 2026-05-07 | Session state for `/pair-review`, `/full-review`, `/roadmap` moved off workspace-local `.context/<skill>/` (which Conductor archives on merge, taking the state with it) onto `${GSTACK_STATE_ROOT:-$HOME/.gstack}/projects/<slug>/<skill>/` — mirrors gstack core's `/context-save` `checkpoints/` shape. **Added:** `bin/lib/session-paths.sh` (sourced helper; `session_dir <skill>` and `session_archive_dir <skill> <ts>` echoing functions per `effort.sh`'s idiom; slug resolution mirrors gstack-slug's `basename "$PWD"` fallback). `tests/lib-session-paths.test.ts` covers happy path + `GSTACK_STATE_ROOT` override + basename fallback + sanitization + sentinel + empty-arg errors. **Changed:** `skills/pair-review.md`, `skills/full-review.md`, `skills/test-plan.md`, `skills/roadmap.md`, `skills/review-apparatus.md` — every `.context/<skill>/` ref replaced with the helper-resolved `<SESSION_DIR>` placeholder, Active Session Guards use `session_archive_dir`, roadmap proposal artifact moved to `<PROPOSAL_DIR>/proposal-{ts}.md` (flat, sibling of gstack's `checkpoints/`). **Test drift-lock:** `tests/skill-protocols.test.ts` now asserts every affected skill sources `bin/lib/session-paths.sh`, calls `session_dir <name>` with the expected name, and contains zero `.context/<skill>/` literals. **Migration:** none (sole-user repo). **Removed:** `.context/` from `.gitignore`. Full suite: 892 pass, 0 fail. |
1011
| 0.18.14.0 | 2026-05-06 | Track 5A (Install pipeline polish) — closes the v0.16.0 known-limitation gap where `--skills-dir <custom>` shipped working symlink installs but skill preambles silently no-op'd helper resolution. Resolution mirrors gstack core's behavior instead of extending the env-var/RC-file hack: skill preambles probe `~/.claude/skills/{name}/SKILL.md` then `.claude/skills/{name}/SKILL.md` and stop. **Breaking:** `./setup --skills-dir <path>` (without `--uninstall`) now exits 1 with a migration message; the flag is preserved only when paired with `--uninstall` as a one-way escape hatch for cleaning up v0.16.0-era installs. **Added:** all 5 skill preambles (`pair-review`, `roadmap`, `full-review`, `review-apparatus`, `test-plan`) gain path-2 fallthrough for vendored installs at `<project-root>/.claude/skills/{name}/SKILL.md` (must be absolute symlinks; `setup`'s install loop already creates them via `pwd -P`); `tests/skill-protocols.test.ts` asserts the fallthrough line is present in all 5 preambles to lock against drift; `skills/test-plan.md`'s Phase 8 inline-Read of `pair-review.md` uses the same two-path probe. **Added (defense in depth):** new `bin/lib/install-safety.sh` with `is_safe_install_path` (resolved-path + numeric-uid ownership + world-writable + inside-`$HOME`, ancestor-walk for non-existent install dirs, permits Dropbox/chezmoi/GNU stow setups) and `is_safe_target_path` (refuses install when `$SKILLS_DIR/{name}` is a symlink, regular file, FIFO, or any non-directory entry). Preflight: ALL targets validated before ANY mutation, so a failed safety check on skill 3 cannot leave skills 1 and 2 partially installed. Cross-platform via `stat -f '%u'` (BSD) / `stat -c '%u'` (GNU) and `cd -P && pwd -P`. **Added:** `DOC_TYPE_MISMATCH` audit check (`src/audit/checks/doc-type.ts`) emits warnings when content disagrees with location — design doc outside `docs/designs/` (mermaid/plantuml fence) or inbox doc outside `TODOS.md` (checkbox density ≥ 0.50, files with <5 content lines skipped). Each finding includes a copy-pasteable `git mv -- <quoted-src> <quoted-dst>` suggestion (or `mkdir -p` variant when parent missing, or "review and move" when destination collides). All paths single-quoted via a POSIX `shellQuote` helper using `--` end-of-options sentinel; defends against malicious filenames like `a;curl evil|sh.md` (codex ship review caught this as P0). Inbox-mismatch destination resolution honors whichever `TODOS.md` location actually exists (root vs `docs/`) per the existing `PROJECT_DOC_PAIRS` rule, defaulting to `docs/TODOS.md` when neither exists — avoids the bug where suggestions would create a shadow root-level `TODOS.md` masking `docs/TODOS.md`. Filename allowlist exempts `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `RUNBOOK.md`, `SECURITY.md`, `SUPPORT.md`, `GOVERNANCE.md`, `*checklist*`. Registered as a CANONICAL section adjacent to `DOC_LOCATION` in `src/audit/sections.ts`; 23 existing snapshot fixtures regenerated mechanically (all `pass`); new `tests/roadmap-audit/doc-type-mismatch/` fixture exercises both heuristics end-to-end. New `tests/lib-install-safety.test.ts` (15 unit tests for both helper functions via shell-out); `tests/update.test.ts` drops `--skills-dir` install cluster, adds rejection test, expanded D6 coverage (world-writable, outside-`$HOME`, per-target symlink, regular file at target), CP#3 integration tests (default install + vendored fallthrough + truly-broken silent no-op). README.md drops the v0.16.0 "Per-project installs (`--skills-dir`)" section + the known-limitation note pointing at this Track. 3 of 5 Track 5A tasks shipped; the layout-scaffolding and update-run-dir-propagation tasks were deferred (codex outside-voice flagged scope creep — first reshapes into a separate Project Bootstrapping Group, second into TODOS.md follow-up since `bin/update-run` was unchanged in this Track and post-upgrade resolution goes through path-1 which is exercised). |
1112
| 0.18.13.0 | 2026-05-05 | Auto-create GitHub Releases on `VERSION` change. The existing `auto-tag.yml` workflow already pushed an annotated tag whenever `VERSION` changed on `main`, but no `gh release create` ever ran — 53 tags accumulated on the repo with zero entries on the Releases page. The workflow now extracts the matching `## [X.Y.Z]` section from `CHANGELOG.md` (via the new `scripts/extract-changelog-section.sh`, reusable from local scripts and CI) and creates a GitHub Release with those notes, falling back to gh's `--generate-notes` plus a `::warning::` if the section is missing. The release step is idempotent (`gh release view "$TAG"` short-circuits on re-runs), composes cleanly with the tag step's existing idempotency, and runs under the workflow's existing `contents: write` permission. Backfill: all 53 historical tags (`v0.1.1` → `v0.18.12.1`) now have releases, with `v0.18.12.1` marked Latest; the two pre-CHANGELOG tags got a placeholder note. The extractor handles the trailing-`.0` mismatch where 4-digit tags like `v0.18.5.0` map to 3-digit CHANGELOG entries like `## [0.18.5]`. |

docs/ROADMAP.md

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@ adjacency list in the Execution Map under Current Plan.
1111

1212
## In Progress
1313

14-
_(none — no Group has both shipped and unshipped Tracks)_
15-
16-
---
17-
18-
## Current Plan
19-
20-
### Group 6: Audit Polish + Track 5A Test Follow-ups + /roadmap-new Refactor
14+
### Group 6: Audit Polish + Track 5A Test Follow-ups + /roadmap-new Refactor _(in progress)_
2115

2216
##### Track 6A: Audit polish + FRESHNESS coverage
2317
_3 tasks . ~300 LOC . low risk . [src/audit/checks/, tests/audit-checks/, skills/roadmap.md]_
@@ -26,11 +20,7 @@ _touches: src/audit/checks/, tests/audit-checks/, tests/audit-snapshots.test.ts,
2620
- **Rename STALENESS → VERSION_TAG_STALENESS + skill prose clarifier** -- mechanical rename in `src/audit/checks/staleness.ts` (+ `expected.txt` fixtures), plus a one-line clarifier in `skills/roadmap.md` Interpreting Audit Findings: "VERSION_TAG_STALENESS only fires on items with explicit (shipped vN.N.N) annotations; broader recency belongs to FRESHNESS." Closes the dogfood-noted misread that `STALENESS: pass` settles the freshness question. _src/audit/checks/, tests/, skills/roadmap.md, ~30 lines._ (S)
2721
- **Extend FRESHNESS scan to TODOS.md `## Unprocessed`** -- new `_inferred_freshness_for_todo` walks Unprocessed items, extracts referenced file paths from prose, runs the same per-file commit-since-introduction lookup as the ROADMAP scan (incl. Track-ID-or-title-fuzzy-match relaxation). Surfaces shipped-but-unclosed inbox items in the FRESHNESS AskUserQuestion flow. _src/audit/checks/freshness.ts, tests/, ~120 lines._ (M)
2822

29-
##### Track 6B: Track 5A test follow-ups
30-
_2 tasks . ~50 LOC . low risk . [test files only]_
31-
_touches: tests/update.test.ts, tests/checks-doc-type.test.ts_
32-
- **`bin/update-run` upgrade-flow integration test** -- trigger `bin/update-run` and verify post-upgrade skills still resolve via path-1. Closes a Track 5A test-coverage gap surfaced during /ship of v0.18.14.0; low priority because (a) `bin/update-run` was unchanged in 5A scope, (b) post-upgrade resolution goes through path-1 which IS exercised, (c) update-run has its own non-Track-5A test coverage. _tests/update.test.ts, ~20 lines + git-fetch fixture._ (S)
33-
- **Doc-type math unit tests** -- boundary cases (empty file, exactly 4 content lines, exactly 5 content lines with 25% density vs 60% density) for the `_inferred_doc_type` math. End-to-end coverage already exists via `tests/roadmap-audit/doc-type-mismatch/`; this Track adds isolated unit tests for the boundary cases. _tests/checks-doc-type.test.ts, ~30 lines._ (S)
23+
##### Track 6B: Track 5A test follow-ups ✓ Shipped (v0.18.18.0)
3424

3525
##### Track 6C: `/roadmap-new` refactor — cut overhead + add ID-renames helper
3626
_3 tasks . ~250 LOC . low risk . [skill prose + new lib + tests]_
@@ -39,6 +29,10 @@ _touches: skills/roadmap-new.md, src/audit/lib/renames-diff.ts, tests/lib-rename
3929
- **Rewrite Step 5 PROGRESS.md flow** -- replace direct row-write with detect-staleness → AskUserQuestion → optional scoped general-purpose subagent appends rows from CHANGELOG. Honors the documentation taxonomy (PROGRESS.md content owned by /document-release) without invoking the full skill. _skills/roadmap-new.md, ~+25 lines._ (S)
4030
- **Add ID-renames helper + apply-summary integration** -- new `src/audit/lib/renames-diff.ts` (parseEntities, computeRenames, formatRenamesTable) with `tests/lib-renames-diff.test.ts` (15 tests). Skill prose at apply-summary time runs the helper against pre/post ROADMAP.md and includes the table in the apply summary + commit message body. _src/audit/lib/renames-diff.ts, tests/lib-renames-diff.test.ts, skills/roadmap-new.md, ~+200 lines._ (S)
4131

32+
---
33+
34+
## Current Plan
35+
4236
### Group 7: Tighten `git commit` Failure Handling
4337

4438
All three review skills currently treat any non-zero exit from `git commit`
@@ -184,7 +178,7 @@ Track detail per group:
184178
```
185179
Group 6: Audit Polish + Track 5A Test Follow-ups + /roadmap-new refactor
186180
+-- Track 6A ........... ~M . 3 tasks (audit polish)
187-
+-- Track 6B ........... ~S . 2 tasks (5A test follow-ups)
181+
+-- Track 6B ........... ✓ Shipped (v0.18.18.0) — 2 tasks (5A test follow-ups)
188182
+-- Track 6C ........... ~M . 3 tasks (/roadmap-new refactor)
189183
190184
Group 7: Tighten git commit failure handling
@@ -218,7 +212,7 @@ Group 15: SKILL.md.tmpl promotion
218212
+-- Track 15A .......... ~M . 1 task
219213
```
220214

221-
**Total: 0 phases . 10 groups . 15 tracks remaining.**
215+
**Total: 0 phases . 10 groups . 14 tracks remaining.**
222216

223217
---
224218

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@
1010
"test": "bun scripts/select-tests.ts",
1111
"test:full": "bun test tests/"
1212
},
13-
"version": "0.18.17.0"
13+
"version": "0.18.18.0"
1414
}

0 commit comments

Comments
 (0)