Skip to content

Release: v7.17.0 - #508

Merged
Data-Wise merged 12 commits into
mainfrom
dev
Aug 24, 2026
Merged

Data-Wise merged 12 commits into
mainfrom
dev

Conversation

@Data-Wise

Copy link
Copy Markdown
Owner

Release v7.17.0 — 12 commits since the last dev → main.

Minor bump: 2 feat, 3 fix, no breaking changes since v7.16.0.

Headline: teach deploy safety

  • --dry-run is genuinely read-only. It aborted in CI mode (auto-detected whenever stdin is
    not a TTY, so every automation caller got it) on conditions a dry run cannot be affected by, and
    interactively it prompted to commit — which a pty wrapper auto-accepted, creating a real commit
    in a live course repo. Both gates now skip on a dry run; real deploys still abort on both.
  • --direct merges with --no-ff. Without it a deploy of N commits fast-forwarded production
    to draft's tip, leaving no single commit representing the deploy — so --rollback, which reverts
    the deploy's commit, could only undo the last of the N.

Also em undo, em move --recent, an em help ALIASES section, and an em move production fix.

CI and docs coverage (#506)

  • 60 suites that had never executed on a runner now run in a non-blocking Extended Suite soak
    job. 118 unreferenced test-*.zsh files were audited: 74 pass locally, 60 of those also pass on
    ubuntu-latest. The 14 needing brew/atlas/keychain are excluded and named.
  • Changelog parity is gated on [Unreleased], so an entry can no longer land in one file only.
  • mkdocs strict: true — broken links now fail the build instead of deploying, since docs.yml
    runs gh-deploy --force on push.
  • Fixed a real bug: .markdownlintignore is a markdownlint-cli v1 file while lint-docs.sh
    runs cli2, which never read it — the intent to skip .archive/ was inert (49 of 443 errors).

Verification

All three checks green on #507. Full suite 82 passed / 2 failed locally; both failures
(e2e-em-dispatcher, test-atlas-contract) reproduce on unmodified dev and are green in CI.
Man-page version guard 12/12. mkdocs build --strict exit 0. No 7.16.0 references remain outside
the changelogs and release archives.

Post-merge

Tag v7.17.0, GitHub release, then the Homebrew tap formula bump.

Test User and others added 12 commits July 7, 2026 13:03
…eased

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…495)

Implements the protocol proposed in docs/planning/PROPOSAL-claude-chat-to-code-handoff.md.

- lib/handoff-helpers.zsh: _flow_handoff() scaffolds docs/planning/HANDOFF-<slug>.md
  from a fixed schema (Summary, Key Decisions, Traps to Avoid, Working Agreements,
  Relevant Files, Open Work, Verification Note, Origin)
- Relevant Files pre-filled from 'git diff --name-only' against base branch
  (default dev, falls back to main)
- Refuses to overwrite an existing handoff for the same slug
- --issue flag files a GitHub issue via gh CLI using the handoff as the body
- Wired into commands/flow.zsh dispatcher and completions/_flow
- docs/commands/handoff.md command reference + mkdocs.yml nav entry
- CHANGELOG.md entry under Unreleased

Tests:
- tests/e2e-handoff.zsh: 7 tests against an isolated temp repo (help, fresh
  slug, overwrite refusal, real-diff pre-fill, empty-diff placeholder,
  --issue-without-gh failure, and a zsh -f regression test for a stray-stdout
  bug hit during prototyping) - all passing
- tests/dogfood-handoff.zsh: real-repo smoke test, self-cleaning artifact -
  passing

Fixes a zsh special-variable collision found during prototyping (local var
named 'fpath' collides with zsh's function-autoload array) by using
git diff --name-only instead of parsing --stat output.

Co-authored-by: Test User <test@example.com>
…496)

- mcprestart: one-command quit+relaunch of Claude Desktop to reset a
  stalled (4-min timeout) or dead ('tool not found') MCP bridge
- mcprestart-log: shows recent restarts + this-month count, turning
  the '5+/month' flicker estimate into a measured number

Context: docs-standards ADR-001 scoped a full McpDoctorUseCase in atlas
for automated detection — parked pending queue priority. This is the
zero-engineering interim fix for the manual relaunch step, not a
replacement for the eventual automated audit.

Co-authored-by: Test User <test@example.com>
PR #495 shipped the command but missed the usual doc surfaces (QUICK-
REFERENCE.md, flow.1, MASTER-API-REFERENCE.md), and its own docs/commands/handoff.md
linked to docs/specs/ which mkdocs excludes from the built site.

- man/man1/flow.1: add handoff to Setup & Diagnostics + an example
- docs/help/QUICK-REFERENCE.md: add Session Handoff section
- docs/reference/MASTER-API-REFERENCE.md: add Handoff Helpers section
- mkdocs.yml: exclude the planning proposal doc from nav (working doc, like the ADHD templates)
- docs/commands/handoff.md: point the spec link at GitHub instead of the excluded specs/ path

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JnVugoVjTHRK41krTTMi8L
…dd em undo + recent-folders (Phase 3) (#497)

em move <ID> <folder> was broken in production: _em_move and its adapter
_em_hml_move were each defined twice in email-dispatcher.zsh/em-himalaya.zsh,
and zsh's last-definition-wins semantics meant the two LIVE definitions were
incompatible with each other — the live _em_move called _em_hml_move with the
numeric message ID in the source-folder position. Every move would attempt to
move a message from a folder named after its own ID.

Same failure class as the flag/star case-duplicate bug fixed earlier this
session (PR #493), this time a duplicate function definition rather than a
duplicate case label. Caught by tests/test-em-move-restore.zsh, which was
never wired into tests/run-all.sh (orphaned, like test-em-ai-switch.zsh was) —
it now fails 3/8 -> passes 8/8 against the fixed source and is registered.

Kept the multi-ID interface (em move <FOLDER> <ID>...) since it matches
em move --help, MASTER-DISPATCHER-GUIDE.md, and the orphaned test file.
Corrected the same stale <ID>-first interface across QUICK-REFERENCE.md,
MASTER-DISPATCHER-GUIDE.md, REFCARD-EMAIL-DISPATCHER.md (which had both a
stale and a correct em move row), EMAIL-DISPATCHER-GUIDE.md,
EMAIL-COOKBOOK.md, and man/man1/em.1 — plus a leftover "em flag = alias for
em star" line the Phase 1+2 docs sweep missed.

Phase 3 (docs/specs/SPEC-em-ux-refactor-2026-07-07.md):
- em undo: single-step undo of the last star/flag/unflag/move (1hr window,
  no stack), built on lib/em-cache.zsh's existing get/set API
- em move --recent <ID>...: quick-pick target folder from last 3
  destinations, no fzf required
- Centralized ALIASES section in em help (single source of truth instead of
  scattered per-command mentions)

Tests: tests/test-em-undo.zsh (16 new tests), tests/test-em-move-restore.zsh
(8/8, newly registered), tests/test-em-help-guards.zsh (undo help coverage).
Full suite: 80 passed, 0 failed, 1 skipped. mkdocs build --strict clean.
Man-page guard: 12/12.


Claude-Session: https://claude.ai/code/session_01JnVugoVjTHRK41krTTMi8L

Co-authored-by: Test User <test@example.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Mirrors the settings.json change (fallback for env-block being
silently ignored, issue #63186) — keeps the two in sync.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Points at the uv-managed venv (~/.venvs/savant-render) created to run
savant's xlsx-render dogfood test with a real openpyxl instead of the
degrade path, without touching the externally-managed system Python.
Not added to PATH by default.
…lk to a free port

Fixes the recurring OSError 48 (Address already in use) when port 8000 is
held by another process: an explicit -a/--dev-addr still passes through
untouched; otherwise a busy port already serving the savant site is opened
in the browser instead of crashing, and any other conflict walks up
8000-8020 to the first free port. E2E: with 8000-8002 busy, auto-picked
8003 and served (curl-verified), then cleaned up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…no-ff (#505)

* fix(teach-deploy): keep --dry-run read-only and unblocked in CI mode

ci_mode auto-detects whenever stdin is not a TTY, so every automation or
agent caller gets it. In that mode two preflight conditions were fatal
even under --dry-run:

  - "production has new commits" aborted before the plan could render
  - "uncommitted changes" aborted before the plan could render

Neither can affect a dry run, which mutates nothing. Both teaching repos
tested hit the first one, so a dry run could not render a plan on the
normal state of a working course repo.

The uncommitted-changes handler had a second, worse edge: interactively
it PROMPTS to commit. A pty wrapper answers the default Y and silently
creates a real commit. Observed once against a live course repo.

Fix: skip both mutation-oriented gates when dry_run is true. Real deploys
are untouched - they still abort on both conditions, which the two
controls in the new suite assert.

tests/test-teach-deploy-dryrun-readonly.zsh: 10 assertions over sandboxed
fixtures, each checking repository state before and after so any write is
caught. 10 passed, 0 failed.

* docs(changelog): record the teach deploy --dry-run read-only fix

Added to [Unreleased] in both the root and docs/ changelogs. Note the two
files already differ by ~20KB of pre-existing drift; this entry is mirrored
into both rather than reconciling that separately.

* fix(teach-deploy): merge with --no-ff on the direct path

A deploy of N draft commits used to fast-forward production to draft's own
tip, leaving no single commit representing the deploy. Since
teach deploy --rollback reverts the deploy's commit, a fast-forwarded
deploy could only be rolled back one commit at a time - it silently lost
rollback granularity rather than failing.

The codebase already assumed merge commits exist: git-helpers.zsh:693
filters "merge commits from --no-ff deploys" when detecting production
conflicts.

Verified in a sandboxed fixture. Before: linear history, production tip
was draft's own content commit. After: production tip is a merge commit
with parents [base, draft-tip].

tests/test-teach-deploy-merge-topology.zsh asserts both properties.
Positive control: reverting only the --no-ff fails both assertions, and
restoring it passes both.

One trap documented in the test header - after a successful deploy, draft
and production legitimately point at the SAME commit, because the deploy
syncs draft from production afterwards. Ref equality is therefore not
evidence of a fast-forwarded deploy; parent count is. An earlier check of
mine got this wrong.

All 8 deploy suites: 246 passed, 1 failed, 1 skipped. The failure is
Test 24 in test-teach-deploy-unit.zsh, which reproduces identically on
unmodified dev.

* fix(teach-deploy): dry run now names the files it is NOT showing

Self-review of this PR: skipping the uncommitted-changes handler on a dry
run has a side effect the first commit did not account for. The handler
previously committed dirty work before the plan rendered, so the
"Would deploy N files" list included it. Skipping the handler is the right
call - a dry run should not assume you would commit - but it means the
list is committed work only, and can silently understate what a real
deploy would ship.

The report now names the count of excluded uncommitted files and says a
real deploy would offer to commit them first. The preflight already showed
"[--] Working tree has uncommitted changes"; this connects that line to the
file count below it.

Covered by a new assertion in test-teach-deploy-dryrun-readonly.zsh
(11 passed). All 8 deploy suites re-run: 247 passed, 1 failed, 1 skipped -
the failure is the same pre-existing Test 24 that reproduces on dev.

---------

Co-authored-by: Test User <test@example.com>
…gating) (#506)

* ci: close four coverage gaps found in a gap analysis

1. TWO SUITES MERGED WITHOUT CI EVER RUNNING THEM (my regression, from #505).
   run-all.sh takes an explicit run_test list, not a glob, so
   test-teach-deploy-dryrun-readonly and test-teach-deploy-merge-topology
   went in green while CI never executed their 12 assertions. Both wired;
   both run in under 2s, well inside the 30s default.

2. 74 SUITES THAT HAD NEVER RUN ON A RUNNER. Audited all 118 test-*.zsh
   files unreferenced by run-all.sh: 74 pass, 37 fail, 6 exceed 20s, 1 is
   TTY-only. The 74 are now in tests/run-extended.sh behind a NON-BLOCKING
   soak job. Deliberately not promoted straight into the required check -
   "passes on a Mac" is not evidence about ubuntu-latest, and one
   environment-dependent suite would break the gate for everyone. This
   mirrors how full-suite itself was introduced, per test.yml's own comment.
   Verified locally: 74 passed, 0 failed.

   The 37 broken ones are left out and are NOT silently dropped - the count
   is recorded in run-extended.sh's header. They fail in the main checkout
   too, so this is rot, not worktree sensitivity.

3. CHANGELOG PARITY, GATED WHERE IT CAN ACTUALLY BE GATED. CHANGELOG.md and
   docs/CHANGELOG.md are meant to mirror and nothing checked it. They have
   drifted BIDIRECTIONALLY: 23 sections only in root, 41 only in docs, and
   of 33 shared versions only 12 have identical bodies. Neither file is a
   superset, so this cannot be fixed by copying one over the other - 21
   shared versions need a human to pick the correct body.

   Reconciling that is explicitly NOT attempted here; picking a winner 21
   times would destroy release history. Instead test-changelog-parity.zsh
   gates the [Unreleased] section, which is identical today, so new entries
   can no longer land in one file only. Historical drift is reported as
   advisory. Positive control: planting an entry in one file's [Unreleased]
   fails the test and names the offending line.

4. DOCS QUALITY WAS UNGATED. mkdocs.yml had no strict setting while docs.yml
   runs `mkdocs gh-deploy --force` on push, so broken links deployed
   silently. strict: true enabled, verified safe first (current tree builds
   with 0 warnings). scripts/lint-docs.sh existed but was wired into nothing
   - now runs in CI as ADVISORY, because 394 errors would fail every run and
   138 of those are MD040, which is real work rather than a config toggle.

   Also fixes a real bug: .markdownlintignore is a markdownlint-cli **v1**
   file and this script runs cli2, which never reads it - so the repo's
   intent to skip .archive/ was inert and 49 errors came from archived docs.
   Excluded via a negated glob cli2 honours. 443 -> 394.

NOT DONE, deliberately: no `lint-docs.sh --fix`. It resolves 302 of 443 but
renumbers ordered lists (MD029) and rewrote `#| label:` to `# | label:` in
docs/plans/2026-01-31-teach-validate-lint.md, breaking a Quarto chunk
directive. ~20 of 48 touched files had genuine content edits including a
security spec whose items 3-9 were renumbered to 1-7. Measured, reverted,
and documented in the CI step so the next person does not repeat it.

Full blocking suite locally: 2 failures (e2e-em-dispatcher,
test-atlas-contract), both reproducing on unmodified dev and both green in
CI - local environment, not this branch.

* ci: narrow the soak list to the 60 that pass on the runner

First CI run of the soak job did its job: 60 of the 74 passed on
ubuntu-latest, 14 failed. They need a tool or service a fresh runner does
not have - brew, atlas, the macOS keychain, a real mail account - which is
exactly the 'passes on a Mac is not evidence about CI' case the job exists
to surface.

The 14 are excluded and named in the file with the reason, not silently
dropped. A soak job that is permanently red gets ignored, and then it
protects nothing; green means a later failure is real signal.

The right fix for each is to exit 77 (this repo's clean-skip code) when its
dependency is absent, at which point it can rejoin the list. Until then they
sit where they already were - outside CI.

Local: 60 passed, 0 failed.

---------

Co-authored-by: Test User <test@example.com>
Minor bump: 2 feat, 3 fix, no breaking changes since v7.16.0.

Highlights - teach deploy safety:
  - --dry-run is genuinely read-only. It no longer aborts in CI mode (which
    every non-TTY caller gets), no longer prompts to commit (a pty wrapper
    auto-accepted that and created a real commit in a live course repo), and
    now names the uncommitted files its plan excludes.
  - --direct merges with --no-ff, so a deploy is one revertable commit again
    and --rollback can undo a multi-commit deploy as a unit.

Plus em undo / em move --recent, and the CI coverage work from #506.

Release mechanics:
  - scripts/release.sh 7.17.0 updated package.json, CLAUDE.md,
    flow.plugin.zsh, README badge and 20 man pages.
  - CHANGELOG.md and docs/CHANGELOG.md both cut [7.17.0] with identical
    bodies; tests/test-changelog-parity.zsh confirms [Unreleased] parity.
  - docs/index.md updated by hand: release.sh does NOT touch it, so its
    "What's New" banner and footer still advertised v7.16.0. Worth folding
    into release.sh so the next release does not repeat it.

Verified: no 7.16.0 references remain outside the changelogs and release
archives; man-page version guard 12/12; mkdocs --strict exit 0; full suite
82 passed / 2 failed, both (e2e-em-dispatcher, test-atlas-contract)
reproducing on unmodified dev and green in CI.

Co-authored-by: Test User <test@example.com>
@Data-Wise
Data-Wise merged commit 79f5369 into main Aug 24, 2026
6 checks passed
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.

1 participant