Skip to content

fix(release): close plan-doctor false positives and lint parity gap; prep v0.9.0 - #149

Merged
joshuaboys merged 6 commits into
mainfrom
claude/aps-release-review-dlaszq
Sep 12, 2026
Merged

joshuaboys merged 6 commits into
mainfrom
claude/aps-release-review-dlaszq

Conversation

@joshuaboys

@joshuaboys joshuaboys commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Release review of v0.8.1 plus the unreleased work on main. CI was green and every distribution pin agreed, but three defects surfaced. Two are fixed here; the third is left to the tool being built for it.

Closes #132.

Defects fixed

plan-doctor flooded healthy plans with false warnings (CIB-005, ISS-010)

The bundled skill judged plans against a stricter private convention than plans/aps-rules.md documents. On a consumer plan with no real defects it drew roughly 385 warnings: 162 Done and 132 Proposed statuses false-flagged by W03, and all 91 module filenames flagged by W02. Because the skill bytes are embedded in the binary, a consumer could not patch it locally without desyncing .aps-managed.json.

Status now normalises through the documented alias table before W03, W04, W05 and W07 evaluate. W04 counts Done as terminal. The filename-prefix half of W02 becomes a new Info code reported once per directory with a count, and a consistently applied undocumented status such as Archived gets its own Info code rather than sharing W03.

The fix also added a general collapsing rule to the report section: a finding that hits 100% of files is house style, not signal. That addresses the cause rather than the three symptoms. The skill now agrees with its sibling aps-planning skill, which already accepted the aliases.

Release-plan lint existed in one of three CLIs (CIB-006, ISS-011)

R001 to R004 were implemented in the Rust linter only, so the bash and PowerShell linters never discovered plans/releases/v*.md at all. On this repo's own plans bash checked 42 files where Rust checked 50, and a malformed release plan passed both fallbacks silently. D-039 makes bash and PowerShell maintained peers, which supersedes REL-003's "aps lint is now the Rust CLI" rationale.

Discovery was the larger half of the defect: get_file_type needed a release branch at the Rust classifier's precedence and find_aps_files needed a releases/ clause, pinned to byte-order sorting to match Rust. Full output including --json is now byte-identical across all three CLIs.

CI could not have caught this either. The parity harness carried no release fixtures, and its findings regex matched only (E|W)[0-9]{3}, so R codes were invisible even when present. Both are fixed.

Installer manifests did not carry the new rule module

Sourcing a rule module from bin/aps without listing it in the installer manifests breaks a vendored or scaffolded bash CLI on startup. Fixed across all fourteen sites, and a new test derives the required list from bin/aps itself so the next rule module cannot repeat it.

aps release — REL-005, Rust phase

Scope grew after the release prep landed, so this PR now also carries the top-ranked Ready work item. Four subcommands: new scaffolds a record from the template, status reports modules and work-item completion, notes drafts markdown from Complete items since the previous release, and close advances Merged/Released items to Complete with a release-evidence line.

The closeout reads the prose record plus the plan tree directly, with no JSON sidecar, which is the work item's stated design constraint. Dry-run is the default and was verified to write nothing by diffing a scratch copy of plans/ afterwards. Two safety properties are worth calling out: --apply refuses a record whose Status is not Shipped/Released/Archived unless --force, so a release cannot be closed out mid-prep, and a record missing ship evidence warns that it would stamp today rather than doing so silently.

Two defects were caught by running it against this repo's real records rather than fixtures, and fixed:

  • status reported an unshipped release as shipped today, defaulting the ship date to the planning date. A release tool that asserts an unshipped release shipped is worse than one that says nothing.
  • The (cut) label used by the v0.8.0 and v0.8.1 records was not understood, so the cut date was reported as the ship date and the real one dropped.

REL-005 stays In Progress, not Complete. D-039 requires bash and PowerShell to carry the same surface with shared-fixture coverage before a new command surface is done, and only the Rust phase has landed. Phasing is explicitly allowed by the work item; shipping one-CLI-only is not. The changelog entry therefore sits under Unreleased rather than in the v0.9.0 section, so the release narrative's out-of-scope list stays accurate.

Plan reconciliation

aps audit went from two findings to none, and the only lint warning is gone.

  • PROMPTS-001 and EXAMPLES-001 marked Complete against their real merge evidence; the prompts and examples modules close.
  • cli-redesign moved Draft to In Progress with six of seven items done; D-045 recorded as decided rather than proposed.
  • Roadmap overview rewritten. It still listed the MCP server and GitHub Action as Future, though both shipped in v0.7.0.
  • The completed archive was filing v0.3.0 through v0.7.0 task tables under an Unreleased heading, and v0.4.0 through v0.8.1 were never rolled at all. The false heading is fixed; the roughly sixty-item backfill is left to REL-005 rather than reconstructed by hand, which would have meant inventing attribution. Tracked as ISS-012.

New backlog items

Each carries an issue-log entry:

  • CIB-007 / ISS-013 — neither fallback CLI can answer aps --version, though both know their version well enough to stamp it into project config. That is the exact question the pin-mismatch warning raises.
  • CIB-008 / ISS-014, ISS-015 — three residual cross-CLI lint divergences (symlink traversal, PowerShell's culture-aware sort, W020/W021 grouping order) and release rules weaker than they read (R001 accepts v0garbage.md). Both reported rather than fixed in one CLI, which would have re-opened the gap just closed.

v0.9.0 prep

Version stamped to 0.9.0 across fifteen files; the release checklist's stray-version grep comes back empty and both CLIs report 0.9.0. Release narrative, changelog section and README highlights written.

Not done, and deliberately left to a human: the tag push, cargo publish, and the Scoop hash, which needs the published SHA256SUMS. The release narrative marks those channels pending.

Verification

cargo test                 230 passed
cargo clippy -D warnings   clean
cargo fmt --check          clean
test/run.sh                all tests passed
test/cli-parity.sh         bash = Rust across 12 fixtures
markdownlint **/*.md       clean
aps lint plans             50 files, no issues (bash and Rust identical)
aps audit --no-run         no findings

PowerShell parity was verified under pwsh 7.4.6, including that the ported rules actually fire rather than silently matching nothing. The local parity run skips that leg because pwsh is not on PATH here; CI covers it and passed green on the two earlier commits.

🤖 Generated with Claude Code

https://claude.ai/code/session_01N2expUMF61CKDC5zxZUeev

…prep v0.9.0

Release review of v0.8.1 plus the unreleased work on main. Three defects
found, two fixed here; plan state reconciled and v0.9.0 prepped.

plan-doctor rules (CIB-005, closes #132 / ISS-010)
  The bundled skill judged plans against a stricter private convention than
  plans/aps-rules.md documents, drawing ~385 warnings on a consumer plan with
  no real defects. Status now normalises through the documented alias table
  (Proposed -> Draft, Done -> Complete) before W03/W04/W05/W07 evaluate, W04
  counts Done as terminal, and the filename-prefix half of W02 becomes Info
  reported once per directory. A collapsing rule was added: a finding that
  hits 100% of files is house style, not signal. The skill now agrees with
  its sibling aps-planning skill, which already accepted the aliases.

release-plan lint parity (CIB-006, closes ISS-011)
  R001-R004 existed in the Rust linter only, so bash and PowerShell never
  discovered plans/releases/v*.md at all - 42 files checked against Rust's
  50, and a malformed release plan passed both fallbacks silently. Ports
  added with matching codes, severities and messages; discovery fixed in
  get_file_type and find_aps_files; output byte-identical across all three.
  The parity harness also dropped R codes from its findings regex and
  carried no release fixtures, so CI could not have caught this either.

installer manifests
  Sourcing a new rule module from bin/aps without listing it in the
  installer manifests broke a vendored bash CLI on startup. Fixed across all
  fourteen sites; a new test derives the required list from bin/aps itself.

plan reconciliation
  PROMPTS-001 and EXAMPLES-001 marked Complete against their merge evidence,
  prompts and examples modules closed, cli-redesign moved to In Progress,
  D-045 recorded as decided, roadmap overview rewritten to match what
  shipped. aps audit goes from two findings to none. The completed archive
  was filing v0.3.0-v0.7.0 tables under an Unreleased heading and never
  rolled v0.4.0-v0.8.1 at all; the false heading is fixed and the ~60-item
  backfill left to REL-005 rather than reconstructed by hand (ISS-012).

new backlog items
  CIB-007 (ISS-013): neither fallback CLI can answer --version, though both
  stamp a version into project config - the exact question the pin-mismatch
  warning raises. CIB-008 (ISS-014, ISS-015): three residual cross-CLI lint
  divergences and release rules weaker than they read, both reported rather
  than fixed in one CLI only.

v0.9.0 prep
  Version stamped to 0.9.0 across fifteen files; the checklist's stray-version
  grep is empty. Release narrative, changelog section and README highlights
  written. Tag, crates.io publish and Scoop hash remain outstanding.

Verification: cargo test (201), clippy -D warnings, cargo fmt --check,
test/run.sh, test/cli-parity.sh, markdownlint, aps lint (50 files, no
issues), aps audit (no findings). PowerShell parity verified by the porter
under pwsh 7.4.6; the local parity run skips that leg and CI covers it.

APS: CIB-005 CIB-006

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2expUMF61CKDC5zxZUeev
The release template writes module references as markdown links
(`**APS modules:** [name](../modules/name.aps.md)`), and v0.5.0 follows
that; v0.9.0 was using plain module names. Linking them makes the record
machine-readable for release tooling, which resolves work-item scope by
following those links out of `## What Ships`.

With the links in place the record resolves 4 modules and 20 work items in
scope, correctly identifying CLI-003 as the one item awaiting release
closeout.

APS: CIB-006

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2expUMF61CKDC5zxZUeev
Implements the four subcommands REL-005 specifies, in the Rust CLI only.

  new     scaffold plans/releases/v<version>.md from the template
  status  modules, lifecycle dates, and work-item completion for a release
  notes   draft markdown from Complete items since the previous release
  close   advance Merged/Released items to Complete with release evidence

The closeout reads the prose release record plus the plan tree directly.
No JSON sidecar: APS is markdown-first, and a sibling project's equivalent
script was unusable precisely because it expected structured input the
records do not carry.

Two safety properties worth keeping. Dry-run is the default and was
verified to write nothing, by diffing a scratch copy of plans/ afterwards.
And --apply refuses a record whose Status is not Shipped/Released/Archived
unless --force, so a release cannot be closed out mid-prep; a record with
no ship evidence warns that it would stamp today rather than doing so
silently, with --date and --tag to override.

Two defects were found by running the commands against this repo's real
release records rather than fixtures, and fixed:

  - status reported an unshipped release as shipped today, defaulting the
    ship date to the planning date. v0.9.0 now renders Cut and Shipped as
    absent, which is the honest answer.
  - the (cut) label used by the v0.8.0 and v0.8.1 records was not
    understood, so the cut date was reported as the ship date and the real
    one dropped.

Module resolution follows markdown links to module plans out of
## What Ships, which the release template already specified; the v0.9.0
record was updated to use them in bc8d637.

REL-005 stays In Progress, not Complete. D-039 requires the bash and
PowerShell CLIs to carry the same surface with shared-fixture coverage
before a new command surface is done, and only the Rust phase has landed.
Phasing is allowed by the work item; shipping one-CLI-only is not. The
CHANGELOG entry sits under Unreleased rather than in v0.9.0 for the same
reason, so the release narrative's out-of-scope list stays accurate.

Verification: cargo fmt --check, clippy -D warnings, cargo test (230),
test/run.sh, test/cli-parity.sh, markdownlint, aps lint (50 files, no
issues).

APS: REL-005

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2expUMF61CKDC5zxZUeev
CI's Rust CLI job failed on 40a0517 with clippy::unnecessary_sort_by at
src/release.rs:95. Applied the lint's own suggestion: sort_by_key with an
owned key, which version_key already returns.

Root cause of the miss: CI installs dtolnay/rust-toolchain@stable and the
repo pins no toolchain, so CI ran clippy 0.1.98 while this environment had
0.1.94 — a version where the lint does not exist. Local clippy reported
zero errors on the same code. Installed 1.98.1 to match CI and re-verified
rather than guessing:

  cargo clippy --locked --all-targets -- -D warnings   0 errors
  cargo fmt --check                                    clean
  cargo test                                           230 passed
  test/run.sh                                          all passed
  test/cli-parity.sh                                   bash = Rust
  aps lint plans                                       50 files, no issues

Behaviour is unchanged: version_key returns an owned
(Vec<u64>, u8, String), so sort_by_key produces the same ordering.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2expUMF61CKDC5zxZUeev
…ain gap

Follow-up bookkeeping from the aps release implementation and the CI
failure it caused.

REL-005 parity debt made specific. The ports implement a handover spec
covering flags, byte-exact output strings, exit codes, version ordering,
lifecycle-bucket classification, and write order. One prerequisite is
recorded: the pre-sweep v0.5.0 corpus that proves the closeout lives
inline in the Rust test module and must move to test/fixtures/release/ so
all three CLIs assert against the same bytes.

Three deliberate deviations from REL-005's spec are now on the record
rather than left implicit:
  - the v0.5.0 sweep is reproduced semantically, not textually, because
    the hand sweep wrote free-form status prose with evidence inside the
    Status value; the tool writes a canonical status plus a separate
    Released line that is lint-correct and idempotent
  - notes windows by date, not by git tag range, since shelling out to
    git would breach the no-runtime-dependencies constraint
  - release.rs duplicates a small filename check rather than reusing the
    private one in lint.rs

Also recorded: a latent defect found only by running the real binary.
propagate_version on the root Cli auto-adds --version to every subcommand
and collides with a positional named version, which clap detects at parse
time rather than build time, so it was a runtime panic. It is now pinned
by a clap_surface_builds test that debug_asserts the whole CLI surface.

CIB-009 (ISS pending): CI installs the latest stable Rust and the repo
pins no toolchain, so local clippy silently disagrees with CI's. That is
what let the unnecessary_sort_by lint through despite a clean local run.
The item proposes rust-toolchain.toml with channel = "stable" and names
the trade-off against pinning an exact version, leaving the decision open.

aps lint clean (50 files), aps audit no findings, markdownlint clean.

APS: REL-005

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2expUMF61CKDC5zxZUeev
The previous commit referenced a pending issue entry for CIB-009 without
creating it. Every other CIB item in this review carries an issues.md
entry; this one now does too, and CIB-009 cross-references it.

APS: CIB-009

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2expUMF61CKDC5zxZUeev
@joshuaboys
joshuaboys marked this pull request as ready for review September 12, 2026 18:09
@joshuaboys
joshuaboys merged commit 16d3529 into main Sep 12, 2026
13 checks passed
@joshuaboys
joshuaboys deleted the claude/aps-release-review-dlaszq branch October 7, 2026 06:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plan-doctor W02/W03/W04 false-positive on plans that follow aps-rules.md

2 participants