# @aperant/framework — Changelog

All notable changes to `@aperant/framework` are documented here. See
[`VERSIONING.md`](VERSIONING.md) for the per-tier contract.

## Unreleased

### Fixed

- **`init` stamps the APT block into every selected runtime's instruction
  file.** Previously init picked exactly ONE instruction file (CLAUDE.md if
  present, else AGENTS.md) regardless of which runtimes were selected, so a
  `--claude --codex` user got the skill catalog in CLAUDE.md only and native
  Codex (which reads AGENTS.md) never saw it; `--gemini` users never got a
  GEMINI.md block at all. `RuntimeDescriptor` now carries a nullable
  `nativeInstructionFile` (claude → CLAUDE.md, codex/opencode → AGENTS.md,
  gemini → GEMINI.md; the other 11 runtimes install to their own surface and
  map to `null`). init resolves the deduplicated target set from the selected
  runtimes, runs an all-target foreign-row preflight (any offending target →
  exit 2, NOTHING written), then writes every target. The result envelope gains
  a plural `instructionFiles[]` array; the singular `instructionFile` stays as a
  backwards-compat alias = first entry. The single-runtime fallback is
  preserved: `--claude` against a project with a pre-existing AGENTS.md (and no
  CLAUDE.md) still writes AGENTS.md.
- **`uninstall` strips the APT block from GEMINI.md too.** `INSTRUCTION_FILES`
  expanded to `['CLAUDE.md', 'AGENTS.md', 'GEMINI.md']` so a `--gemini` install
  no longer leaves a stale managed block behind.
- **The chat-agent instruction loader dedupes identical APT blocks.**
  `loadInstructionFiles` (packages/core) now loads a given verbatim
  `<!-- APT:framework-* -->` block once even when it appears in multiple walk-up
  files (the post-fix normal state for multi-runtime users) — preventing the
  skill catalog from rendering twice in the system prompt. Narrow: only
  identical managed blocks are deduped; non-managed content and distinct blocks
  are preserved.

## 0.9.0 — 2026-05-25 — github-issues triage backend

Replaces the v1 `github-issues` triage stub with a working `gh`-CLI adapter,
so projects on `task_tracking.backend: "github-issues"` get real GitHub
issues synced to triage state.

### Added

- **`github-issues` triage backend is now implemented.** The first triage
  mirror CREATES a GitHub issue via `gh issue create` (`triage: <task-id>`,
  labelled `<category>` + `<vocab>:<state>`); subsequent transitions UPDATE
  the SAME issue (`gh issue edit` label swap); a transition to `wontfix`
  CLOSES it as "not planned" (`gh issue close --reason "not planned"`). The
  issue pointer (`github.{issue_number, issue_url, repo}`) persists on
  `triage.json`, so transitions are idempotent — no duplicate issues. Honors
  `task_tracking.tracker_label_vocabulary` (absent/`"default"` →
  `triage:<state>`; otherwise `<vocab>:<state>`). Transport is the `gh` CLI
  via `execFileSync` arg-arrays (no shell-string interpolation), inheriting
  the user's existing `gh auth` — zero new credential plumbing. When `gh` is
  missing / unauthenticated / there is no resolvable git remote, the adapter
  returns a structured error (`reason: gh-unavailable | gh-unauthenticated |
  no-remote | gh-command-failed`) and exits non-zero — never a crash or
  stacktrace. Label operations are best-effort: an unknown label degrades to
  a `warnings[]` entry while the open/closed state change still lands. A
  deterministic `APT_TRIAGE_GH_FIXTURE` test hook (mirroring `pr-review.mjs`'s
  `APT_GH_FIXTURE_JSON`) keeps the unit tests hermetic — no real network.
  The `app-inbox` backend remains a not-implemented stub (out of scope,
  post-app-launch).

### Changed

- **`## User Outcomes` coverage gate is now a HARD BLOCK (cutover from the
  0.8.x warn-only grace window).** As telegraphed in the 0.8.6 release notes
  ("v0.9.0 will **HARD BLOCK** specs that [lack `## User Outcomes`]"), the
  `coverage-check` gate flips from `warn` to `block` for STANDARD/DEEP plans
  whose spec is missing the `## User Outcomes` section, starting this 0.9.0
  release. The version-gated logic already lived in
  `userOutcomesGateResult` (warn in 0.8.x, block in 0.9.0+); bumping the
  kernel to 0.9.0 activates it. Refactor-only specs use the
  `_No user-observable changes — pure refactor._` empty-with-note escape to
  pass. QUICK-track tasks remain exempt (Fast Path Guarantee). The
  `migration_deadline` advisory note (only emitted during the warn window) is
  no longer surfaced — the deadline has passed.

## 0.8.7 — 2026-05-24 — Codex parity: drift false-positive fix + agent inlining

Brings the Codex runtime to parity with Claude Code on two fronts.

### Fixed

- **Codex skills no longer perpetually flagged `missing_header`.** Codex
  whitelists skill frontmatter to `{name, description}` (it rejects unknown
  keys), which strips the `apt-skill-version` header the drift-checker looked
  for — so every Codex skill showed as stale forever and `/apt:update` could
  never clear it. The install pipeline now records `headerless: true` on
  manifest entries whose post-transform content legitimately carries no version
  header, and `inspectManifestFiles` skips the `missing_header` check for them
  (`installedHash` remains the drift signal). Data-driven, so any future
  header-stripping transform is covered. Pre-0.8.7 manifests (no `headerless`
  field) keep the old behavior until re-install.

### Added

- **Codex agent inlining.** Codex has no markdown agent-spawn surface, so a
  skill declaring `spawns_agent: true` + `agent_name: X` previously left Codex
  referencing an `agents/X.md` file that was never installed — the multi-agent
  pipeline skills (plan/execute/verify/review/improve/create-docs) ran degraded
  without the agent methodology. `codex.mjs` now inlines the referenced agent's
  prompt into the skill body under a `## Inlined agent: X` header ("you ARE that
  agent for this task"), so Codex carries the full methodology even without a
  spawn primitive. The pipeline supplies agent bodies to transforms via a new
  `agentContentByName` map on `TransformInput` (transforms stay pure — no I/O).

## 0.8.6 — 2026-05-24 — Outcome-first verify-proof contract + driver-sdk inline (recovery from 0.8.4 npm 404)

> **Migration window.** This release introduces a new `## User Outcomes`
> section that becomes the verify-proof input contract. Specs WITHOUT
> the section emit a `coverage-check` **warning** on v0.8.x and a
> **HARD BLOCK** starting v0.9.0. Refactor-only specs SHOULD use the
> empty-with-note escape (`_No user-observable changes — pure
> refactor._`) to declare absence-of-outcomes deliberately. See spec
> §ID-07 of `verify-proof-monorepo-contract` for the gate table.

> **Recovery.** 0.8.4 shipped with `@aperant/driver-sdk: 0.1.0` as a
> direct dep, but the driver-sdk package was never published — every
> `npm install @aperant/framework@0.8.4` since 2026-05-22 failed with
> a 404 on the transitive dep. 0.8.6 inlines the 875-LOC zero-dep SDK
> into the framework tarball, removing the broken dep. Users on 0.8.4
> MUST upgrade to 0.8.6 to install successfully.

### Fixed

- **`@aperant/driver-sdk` inlined into framework src** (recovery from
  the 0.8.4 npm 404). The SDK lived at `packages/framework-driver-sdk/`
  with the contract that third-party driver authors would `pnpm add
  @aperant/driver-sdk` and avoid pulling all of framework. The SDK
  was never published; pnpm's `workspace:*` rewrite baked an
  unresolvable `0.1.0` spec into framework's published `package.json`.
  Fix: 875 LOC of zero-runtime-dep SDK moved to
  `packages/framework/src/driver-sdk/`, framework imports rewritten to
  relative paths, the `import-direction.test.ts` invariant repurposed
  from a package boundary to a subdirectory boundary so re-extraction
  stays mechanical if a real third-party driver author ever asks for a
  standalone install. The `@aperant/driver-sdk` npm package remains
  unpublished — no consumers existed to migrate.

### Added

- **`## User Outcomes` PRD section** — verify-proof's input contract.
  STANDARD/DEEP specs ship a section between `## Solution` and
  `## Implementation Decisions` carrying one `**Epic:**` headline + N
  atomic outcomes tagged by surface (`electron | web | cli | api |
  electron+web`). Authors write the literal `**ON**` token; IDs are
  POSITIONAL and synthesized by the parser (O1, O2, …). Optional
  cross-refs via `*(requires ON N)*`. Empty-with-note refactor escape
  for non-UX tasks. QUICK track is hard-exempt per the Fast Path
  Guarantee (ID-05).
- **`drivers/browser/manifest.json:transport.skill_id = "agent-browser"`**
  — second transport option alongside `mcp_server_id: "puppeteer"`.
  On Claude Code / OpenCode hosts the workflow runner dispatches via
  `Skill({skill: "agent-browser", args: ...})` (one fewer external
  dep — no Puppeteer MCP install needed); on legacy hosts the MCP
  path is preserved. `chooseTransport({has_skill_tool})` helper.
- **`resolveDriversForOutcomes(opts)`** — new resolver entrypoint that
  returns one entry per (outcome, surface) pair. For `[electron+web]`
  outcomes emits TWO entries with the same `outcome_id` and different
  drivers. Delegates to existing `resolveDriver()` for scoring.
- **`verification.monorepo_globs: string[]`** config key (default
  `["apps/*"]`) — `loadRuntimeCapabilities` walks each glob match's
  `package.json` and OR-merges its capabilities with the root.
  Disable the walk entirely with `monorepo_globs: []`.
- **`transport` field on `verification.json:tests[]`** — `skill | mcp
  | binary | http`. Populated by the workflow runner from the
  dispatcher's directive (Step 7a). Documented in
  `templates/proof-verification.md`.
- **`surface` field on `verification.json:tests[]`** — `electron | web
  | cli | api`. Drives the per-surface screenshot path and the
  `coverage.by_surface` rollup. Cross-surface outcomes produce TWO
  test entries sharing the same `outcome_id`.

### Changed

- **`apt-tools detect-runtime`** — now walks `apps/*/package.json` by
  default. Fixes the canonical Aperant monorepo defect (root
  package.json with no Electron dep + `apps/desktop/package.json`
  carrying Electron previously returned `is_electron: false` because
  the detector only read the root). No user-visible config change
  needed; opt out via `verification.monorepo_globs: []`.
- **`apt-tools coverage-check validate`** — payload gains a
  `user_outcomes` block when the plan is STANDARD/DEEP. Block carries
  `header_present`, `body_kind` (`empty | empty-with-note | outcomes
  | absent`), `outcomes_count`, `validation_warnings?`, and
  `gate_result` (`pass | warn | block`). QUICK plans omit the block
  entirely per Fast Path Guarantee.
- **`workflows/verify-proof.md` Step 7** — dispatches per outcome via
  `resolveDriversForOutcomes()` instead of a single per-run
  `resolveDriver()` call. Writes screenshots under
  `${PROOF_DIR}/${surface}/screenshots/${outcome-id}.png` (per-surface
  split tree). Step 7a gains the MUST-invoke Skill directive
  (TD-08 grep guard pins the language).

### Deprecated

- **Specs without `## User Outcomes` (STANDARD/DEEP only).** v0.8.x
  emits a `coverage-check` **warning** with a `migration_deadline`
  string referencing v0.9.0. v0.9.0 will **HARD BLOCK** specs that
  lack the section. Refactor-only specs SHOULD use the
  empty-with-note escape (`_No user-observable changes — pure
  refactor._`, italic underscores + em dash + period required) to
  declare absence-of-outcomes deliberately. QUICK-track plans are
  hard-exempt at every version.

### v0.9.0 telegraph (one-minor window)

The v0.9.0 cutover lands the hard-block on missing `## User Outcomes`
in STANDARD/DEEP specs. Adopters should retrofit specs in the v0.8.x
window OR adopt the empty-with-note escape for refactor specs. No
silent migration of existing specs is provided — the migration is
deliberate per author.

### Out-of-scope (deferred follow-ups)

- **`verify-proof-executable-runner`** — the v1 verify-proof runner is
  the host LLM (Claude Code / Codex / OpenCode / Gemini) following
  `workflows/verify-proof.md` as a prose recipe. The framework does
  NOT ship an executable `apt-tools verify-proof` command in 0.8.6.
  The `transport` field, per-outcome dispatch, and MUST-invoke
  directive are LLM-honored conventions enforced via grep guards on
  workflow text (TD-08) and schema assertions on `verification.json`
  shape (AC23). Follow-up `verify-proof-executable-runner` task is
  deferred per spec §Out of Scope; the triggers that would promote
  Path B → Path A are documented there.
- **`apt-tools outcomes lint`** — sentence-shape lint for User
  Outcomes (catches squishy outcomes, validates surface tags).
  Deferred to a follow-up framework task.

## 0.8.5 — 2026-05-22 — `/apt:verify-proof` driver layer fixes (bundled drivers + hard override precedence)

### Fixed

- **`drivers/` missing from published tarball.** `packages/framework/package.json:files`
  did not include `drivers/`, so the four bundled IDL drivers (`browser`,
  `electron`, `cli`, `api` — each with `manifest.json` + `driver.mjs` +
  `README.md`) were stripped at `pnpm pack` / `npm publish` time. Downstream
  effect: `apt-tools detect-runtime` returned `discovered_drivers: []` and
  `selected_driver_id: null` for every installed kernel, which made the
  strict `/apt:verify-proof` IDL path unusable (resolver throws
  `unsatisfied_required_capability`). Fix is a one-line `files` entry —
  re-pack now ships 4 manifests + 4 driver modules. Self-host installs
  fix automatically on the next `/apt:update --from-source`; npm-install
  consumers fix on the next published version bump.

- **`verification.runtimes[]` override was decorative.** The resolver
  in `packages/framework/src/cli/verify-proof/resolver.mjs` placed
  override-listed drivers first in the candidate list but then sorted
  the list by score, wiping the override order. With a Next.js root
  `package.json` (e.g. a monorepo where Electron lives at `apps/desktop`),
  forcing `verification.runtimes: ["electron"]` still selected `browser`
  because the latter had higher `priority` (100 vs 90). The override
  docstring promised "explicit driverId precedence" but delivered a
  tie-breaker. Fix: override is now **hard precedence** — listed
  drivers are tried in override order, first one satisfying every
  required capability wins regardless of score; non-matching override
  entries are recorded in `fallback_attempts` with reason
  `override-listed but missing required capabilities: ...` and the
  resolver then falls through to the score-based pick on remaining
  drivers. Two test cases in `verify-proof-resolver.test.ts` lock the
  new contract (force-pick + fall-through).

## 0.8.4 — 2026-05-22 — `/apt:update` polish + `apt:handoff`, `apt:caveman` skills + `apt:resume` HANDOFF discovery

Bundled release: five `/apt:update` fix bundles (converged from a 3-round
Codex sparring session at `.aperant/spar/apt-update-fixes-20260522-083919.md`,
resolving 9 reported issues + 2 hidden defects) plus two new skills and one
extended skill.

> **VERSIONING.md note.** New skills normally trigger MINOR per the
> contract. Operator opted for PATCH here because the surface area is
> small (2 skills, 1 extension) and the apt:update polish is the load-
> bearing change. Future MINOR bumps remain reserved for transform /
> manifest / CLI flag additions.

### Added

- **`/apt:handoff`** (new skill at `packages/framework/skills/apt-handoff/SKILL.md`).
  Agent-to-agent handoff — compacts the current conversation into a
  `HANDOFF.md` artifact under `.aperant/handoffs/{id}/` (or
  `{task_dir}/handoffs/{id}/` when inside a routed task), indexed via a
  new `.aperant/HANDOFFS_INDEX.md`. Distinct from `/apt:pause` along
  four axes: trigger (agent vs human), audience (different agent vs
  future-self), uncommitted-work handling (does NOT stash), and index
  file. `stage: pause`, `intent: capture`, `task_context: none`.
- **`/apt:caveman`** (new skill at `packages/framework/skills/apt-caveman/SKILL.md`).
  Ultra-compressed terse output mode — toggles a persistent "smart
  caveman" style that cuts conversational token usage while keeping
  full technical accuracy. `stage: ops`, `intent: configure`,
  `allowed-tools: ""`.

### Changed

- **`/apt:resume`** now discovers HANDOFF artifacts in addition to
  CONTINUE artifacts. New step `1b` reads `.aperant/HANDOFFS_INDEX.md`;
  step `1d` scans `.aperant/{tasks/*/handoffs,handoffs}/*/HANDOFF.md` as
  fallback. The Present Sessions list tags each row `[pause]` or
  `[handoff]` (handoffs render first — higher urgency). Step 3 splits
  into `3a` (pause: read CONTINUE.md, pop stash) vs `3b` (handoff: read
  Focus, verify external state, do NOT pop stash). Cleanup removes the
  matching index row regardless of type.

### Fixed

- **Bundle A — Workspace-aware version detection.** New helper
  `findWorkspaceFrameworkVersion(targetDir)` walks up to a sibling
  `packages/framework/package.json` (max 6 frames, mirroring
  `route/drift-detect.mjs:32`). The SessionStart worker cache + the
  `apt-tools check-version --json` envelope now carry an additive
  `framework.workspace_version: string | null` field. When the
  workspace is ahead of the running kernel, `update_available` flips
  true regardless of npm cache state — the workspace is the source of
  truth on self-host. `--from-source` flag preserved as explicit
  override per ID-03. Resolves issues 1, 7, 9.
- **Bundle B — Idempotent kernel install.** `installKernel` short-
  circuits when the kernel package.json already reports the target
  version AND the kernel binary exists. Returns the same
  `status: 'installed'` envelope with `reason: 'already at target
  version'` + `durationMs: 0` so the three downstream consumers
  (init.mjs:1441, init.mjs:1735, SKILL.md Scenario D) keep their
  existing branching. No new enum value (D-02). Resolves issues 5, 6.
- **Bundle C — SKILL.md doc gaps.** Adds the `pnpm install --filter
  @aperant/framework... --frozen-lockfile=false` precondition before
  the `pnpm pack` recipe (AC6); adds a hard warning against
  substituting `npm pack` — names the `workspace:*` literal +
  `EUNSUPPORTEDPROTOCOL` keyword (AC7); adds a `Migrated:` line slot
  to all three Scenario A/B/C templates, conditional on
  `legacy_cleanup?.ran === true && rmdirs.some(p => p.includes('.codex'))`
  (AC8). Resolves issues 2, 4, 8.
- **Bundle D — Workspace-dep chain.** New CLI subcommand
  `apt-tools install-from-source <project-dir>` walks workspace
  `dependencies` + `peerDependencies` for `workspace:*` entries
  (matching by package `name`, not directory name —
  `@aperant/driver-sdk` lives at `packages/framework-driver-sdk/`),
  pnpm-packs each, then invokes a single `npm install --prefix
  .aperant/deps <tarball-list>...` with the existing kernel-install
  flag set (`--no-save`, `--no-package-lock`, `--no-audit`,
  `--no-fund`). Handles the same-PR unpublished-pair case
  (`framework@X` + `driver-sdk@Y` both at `workspace:*`, both
  unpublished) without `EUNSUPPORTEDPROTOCOL`. SKILL.md §1.7
  simplified to a single apt-tools invocation; the manual recipe
  preserved as a debugging-aid footnote. Resolves issue 3.
- **Bundle E — Init comment inversion + Scenario A null-latest.**
  Fixed `init.mjs:1253-1268` comment + stderr direction (LEGACY =
  `.codex/`, CURRENT = `.agents/skills/<folder>/SKILL.md`; was
  reversed; the v0.8.2 inversion is documented in
  `runtime-migrate.mjs:7-12`). Scenario A now requires
  `latest_version != null` to avoid false "version bump" chips when
  npm-latest is unknown (cache stale, not published, network down).
  New Scenario E ("no version info available") branch handles that
  case explicitly — refreshes runtime files for safety, tells the
  user the bump is deferred. Resolves 2 hidden defects.

### Schema status

Envelope changes are strictly additive — the new
`framework.workspace_version` field is nullable, existing callers
ignore unknown fields. **No `schema_version` bump** (cache stays at
`1`, locked by `update-chips.mjs:97-98` + the
`check-version-cache-merge.test.ts:137-148` schema-2 rejection
regression test).

### Decision source

Codex sparring session 2026-05-22 (3 rounds, CONVERGED).

### Tests

- **New:** `find-workspace-framework-version.test.ts`,
  `install-kernel-idempotent.test.ts`,
  `apt-update-skill-doc-gaps.test.ts`, `install-from-source.test.ts`,
  `init-legacy-cleanup-direction.test.ts` (5 files / 23 new cases).
- **Augmented:** `check-version-cache-merge.test.ts` (+2 cases for
  the workspace_version envelope field).
- **Regression-locked:** `apt-update-from-source-skill-structure.test.ts`,
  `route-source-drift-detection.test.ts`, `legacy-cleanup-codex.test.ts`,
  `check-version-cache-merge.test.ts` (existing schema_version=2
  rejection at `:137-148`). All still pass.

## 0.8.3 — 2026-05-21 — FRAMEWORK-RFC-001 — `/apt:spar` pipeline integration with install-time consent

Minor patch release. Flips `/apt:spar` from manual-only (SPAR-07 v1) to
pipeline-integrated under explicit install-time consent. Adds a per-device
opt-in question to `/apt:setup` Batch 2 Pipeline; wires `/apt:run` and the
`/apt` router to honor it with track-aware gates (QUICK rejects, STANDARD
= `plan` gate, DEEP = `plan,execute` gates); updates SPAR-07 wording.
Per-invocation `--spar-gates` flag remains as the manual override path;
rounds always defer to the skill default to avoid reintroducing
FRAMEWORK-BUG-038.

### What

- **`SparConfig` schema delta** — three per-device fields land in
  `.aperant/config.local.json` per AUDIT-001: `spar.enabled: boolean`,
  `spar.partner: 'codex' | 'claude' | 'gemini' | null`, `spar.hint?: boolean`.
  Added to `packages/framework/src/types/config.ts` as
  `AperantConfig.spar?: SparConfig`. No migration path needed — spar lands
  in `config.local.json` from day one with no shared-config legacy.

- **`apt-setup` Sparring Auto-Fire (Batch 2)** — 3-phase question block
  inserted after PR Review Auto-Suggest:
  - Phase 1 (Yes/No): master switch.
  - Phase 2 (partner picker, only if Yes): reads
    `host.partner_transports.{codex,claude,gemini}` from
    `apt-tools host-detect` to filter by availability, AND excludes
    `host.cli` (Open Q1 same-host filter). Graceful skip with
    `spar.enabled: true, spar.partner: null` when zero partners detected.
  - Phase 3 (hint sub-picker, only if No): opt-in to per-stage reminder
    `[apt] {stage} complete. /apt:spar available before next stage.`
  All three fields route through `writeLocalConfigField` per AUDIT-001.

- **`apt-run` flag + gate handlers** —
  - `--spar-gates <plan|execute|plan,execute>` flag (per-invocation
    override of merged-config consent). QUICK hard-rejects with verbatim
    `[apt:run] --spar-gates ignored for QUICK track (Fast Path Guarantee)`.
  - §3b "Stage 1.5 — Spar Gate (plan)": resolves effective gates, composes
    topic frame from spec.md §Scope (task description fallback per
    Open Q4), dispatches `/apt:spar --timeout-ms 120000 [--with <partner>]`
    with NO `--rounds` flag (FRAMEWORK-BUG-038 regression guard).
  - §4b "Stage 2.5 — Spar Gate (execute)": same logic, gate name
    `"execute"`, topic frame from `build-progress.json`.
  - Pipeline-mode `--timeout-ms 120000` (2-min Bash-rung fast-fail) is
    asymmetric with manual mode (5min/15min defaults). Worst-case DEEP
    wall budget: 2 gates × 3 rounds × 2 min = 12 min (vs ~90 min before
    this clamp under MCP-rung partner degradation).
  - §5 Verify spawn context: verifier reads Layer 2 spar findings
    (`.aperant/spar/<slug>-<ts>.md`) before scoring.
  - Per-stage `spar.hint` nudge at each of plan/execute/verify/review
    Output: anchors (4 conditional prints, gated on
    `spar.hint === true && spar.enabled === false`).

- **`apt` router envelope contract (load-bearing AC11)** — `route.mjs`
  emits two deterministic consent fields:
  - `envelope.spar_inject_gates` (flag-override path): pre-computed gate
    string `"plan"` (STANDARD) or `"plan,execute"` (DEEP) when consent on
    AND user did NOT pass explicit `--spar-gates`. Absent for QUICK/DEBUG.
  - `envelope.spar_consent` (host-LLM path): block
    `{ enabled, partner, user_passed_explicit_gates }` when consent on.
    Host model composes `--spar-gates` from spar_consent + chosen track.
  Both fields ABSENT when consent off — envelope byte-equivalent to
  pre-RFC shape for users who never opted in. `apt/SKILL.md` MODE: classify
  Step 4 reads ONLY the envelope (never re-reads merged config from
  prompt-space) — structural test grep-locks this invariant.

- **`apt-plan` + `apt-execute` `--spar` flags** — end-of-process dispatch
  to `/apt:spar` against local artifacts (spec.md / build-progress.json).
  Same `--timeout-ms 120000` + no-rounds discipline. Per-skill manual
  surface for users who don't use `/apt:run` end-to-end.

- **`apt-spar/SKILL.md` SPAR-07 wording** — updated from v1
  "manually invoked only" to v2 "honors install-time consent". §1-§9
  process body is byte-equivalent to pre-edit baseline (sha256
  9c2852ce…7da7b3 preserved by `apt-spar-pipeline-manual-path-preserved.test.ts`
  hash guard). Manual `/apt:spar` invocation path UNCHANGED.

### Why

Founder usage data confirmed that cross-provider sparring surfaces
blind-spot defects that single-provider review misses. SPAR-07's v1
escape hatch was always deferred-pending-data; the data is in. Pipeline
integration removes the per-task `--spar-gates` flag drudgery while
preserving QUICK Fast Path Guarantee + manual override surfaces.

### Compatibility

- Backward-compatible. Users who never opt in to `spar.enabled: true`
  see identical behavior to 0.8.2 — envelope shape byte-equivalent,
  apt-run pipeline unchanged.
- Manual `/apt:spar` invocation path BYTE-EQUIVALENT (process body
  hash-locked).
- `--spar-gates` flag is opt-in per-invocation. QUICK hard-rejects.
- Migration: no path needed. Spar lands in `config.local.json` from day
  one; no shared-config legacy.

### Out of scope (follow-up RFC-001-b candidates)

- Verify-stage spar gate (deferred — verify already iterates fix-and-retry).
- Pre-ship spar gate (likely redundant with `/apt:pr-review`).
- Multi-partner concurrent spar (use `/apt:roundtable` for 10-agent).
- Re-plan-on-spar-cede behavioral feedback loop.
- `--mcp-timeout-ms` per-invocation flag on `apt-spar` (Open Q5 —
  MCP-rung pipeline tightening). Today MCP rung is config-only at
  `apt-spar.mcp_timeout_ms`; pipeline mode accepts the asymmetry.

### Version

- `packages/framework/package.json`: 0.8.2 → 0.8.3

### Commits

- `4d67ca1d` feat(framework): add SparConfig schema for RFC-001 pipeline consent
- `e301b776` feat(framework): apt-setup Sparring Auto-Fire prompt (RFC-001 install-time consent)
- `20dea366` feat(framework): update SPAR-07 wording to v2 install-time consent contract
- `3f9ef384` feat(framework): apt-run --spar-gates flag parsing + QUICK hard-reject
- `be06429a` feat(framework): apt-run §3b + §4b spar-gate handlers
- `36c7702d` feat(framework): emit spar_inject_gates + spar_consent envelope fields
- `d803f8c5` feat(framework): apt router consent-driven --spar-gates auto-inject
- `726ae3f5` feat(framework): apt-plan + apt-execute --spar per-skill flag
- `e796580e` feat(framework): apt-run spar.hint nudge at stage success-ends

## 0.8.2 — 2026-05-21 — Codex skill-discovery regression fix

Codex skill-discovery regression fix — install now writes to the
documented `.agents/skills/<folder>/SKILL.md` surface.

### Fixed

- Codex `installRoot` re-targeted from `.codex` to `.agents` so SKILL.md
  lands at `<targetDir>/.agents/skills/<folder>/SKILL.md` per
  https://developers.openai.com/codex/skills. The previous v0.7/v0.8
  `.codex/agents/` + `.codex/commands/` topology was a dead surface
  (Codex CLI 0.132.0 doesn't discover skills there) and a fresh
  `apt-tools init . --codex` reported 95 files installed while
  `codex debug prompt-input` listed zero apt-* skills.
- Codex frontmatter strip switched from a 2-key blacklist
  (`['tools', 'color']`) to an allowed_properties whitelist
  (`KEEP = ['name', 'description']`), matching
  `~/.codex/skills/.system/skill-creator/scripts/quick_validate.py:40`
  and the working third-party skill examples. Drift-proof: any new
  framework frontmatter key is dropped from Codex output without
  breaking the validator.
- Codex transform now emits a single output (`skills/<folder>/SKILL.md`)
  per canonical skill. The previous two-output emission
  (`agents/<folder>.md` + `commands/<folder>.md`) is dropped along with
  the `fileType === 'agent'` branch (markdown agents are not a Codex
  surface; Codex custom-agents are TOML under `.codex/agents/`).
- `runLegacyCleanup` now GCs the v0.7/v0.8 `.codex/agents/apt-*.md` +
  `.codex/commands/apt-*.md` + `.codex/apt-file-manifest.json` misdirect
  on the next `apt-tools init . --codex`. `.codex/config.toml` and any
  user-added `.codex/` content is preserved.

### Changed

- `getTrustedSkillSources` now skips the Codex install root in the
  per-cli filter (`cli !== 'codex'` guard). Keeps
  `.aperant/logs/route-dropped.jsonl` clean for router invocations on
  Codex hosts — Codex's stripped frontmatter would otherwise fail
  `SkillFrontmatterSchema`. Canonical + kernel roots still serve
  routing.
- `LEGACY_INSTALL_PATHS` schema gains a `honor_manifest?: boolean`
  field (default `true`). When `false`, a present
  `apt-file-manifest.json` at the legacy root does NOT trigger the
  managed-no-op — used for known-dead surfaces like the v0.7/v0.8
  codex misdirect. Existing legacy entries are unaffected (default true
  preserves the managed-install safety).

### Version

- `packages/framework/package.json`: 0.8.1 → 0.8.2

## 0.8.1 — 2026-05-20 — Pi runtime + idempotent re-init repair

Patch release making `apt-tools init` safe to rerun on another machine after
pulling a current `.aperant/config.json`.

### Fixed

- Current-schema `init` no longer returns `up_to_date` when explicit runtime
  flags were passed or when suggested runtime roots are missing
  `apt-file-manifest.json`; it reruns the runtime installer for those roots.
- True no-op behavior is preserved when no runtime roots are suggested and no
  explicit runtime flags are passed.

### Added

- Pi is now an installable runtime via `--pi`, with files written under
  `.pi/skills/` and `.pi/prompts/`.
- `cliToInstallRoot('pi')` now resolves to `.pi`.

### Version

- `packages/framework/package.json`: 0.8.0 → 0.8.1
- CLI golden fixtures refreshed

## 0.8.0 — 2026-05-20 — cross-task fan-out + 5 framework defects (BUG-033..038)

Minor release. Ships the new `/apt:fan-out` skill (cross-task parallelization from a single conductor terminal) and closes five framework defects surfaced during real `/apt:run` pipelines. Includes one behavioral default change (`task_isolation.from_branch` now defaults to `main`, not `current`).

### New capability: `/apt:fan-out`

Cross-task parallelization from one conductor terminal. The host CLI's main thread spawns N `apt-executor` sub-agents via Task tool `run_in_background: true`, each pinned to its own task-level worktree. Accepts `--tasks <id-list>` (canonical), `--phases <m/p>` (policy front), `--unblocked` (auto-pick from roadmap ready-state). Default concurrency cap `parallelization.max_tasks: 4`.

Built on the **generalized wave engine**: `wave.mjs` + `wfrun/dag.mjs` now accept `unit_kind: "subtask" | "task"` so task-level fan-out reuses the same primitives (`apt-tools lock`, JSONL event log, `max_agents` quota) as the existing subtask wave-parallel mode. Subtask path is bit-equivalent (locked by snapshot test).

ADRs documenting the locked decisions:
- ADR-0001 worker subagent topology (host CLI Task tool background spawn)
- ADR-0002 paired skill surface (mechanism + policy split)
- ADR-0003 conductor identity (host CLI main thread; never daemon)

### Defects fixed

- **FRAMEWORK-BUG-033** — cross-task fan-out gap (the umbrella above) bundled the four lifecycle/identity sub-defects:
  - Phase auto-completion sibling-guard bug in `lifecycle.mjs` (16-task phase flipping to `completed` on first close)
  - Durable `assigned_to` field on task records
  - `APT_AGENT_ID=fanout-${batch_id}-${task_id}` export from `apt-executor` for stable per-worker identity
  - Wave engine generalization to accept either subtask or task as `unit_id`
- **FRAMEWORK-BUG-034** — `validate-evidence` parser self-reference. Stamper inserted `- **Validation Status**: ...` using the same `- **...**:` field-form shape its own regex matched; on subsequent passes the parser dismissed 88% of LLM-validated findings as `evidence_not_literal` by reading its own stamped text as the cited evidence. Fix: writer emits trailing `### Validation Metadata` section; reader strips legacy field-form Status/Note before evidence extraction. Dual-shape reader for back-compat.
- **FRAMEWORK-BUG-035** — `pr-review-audit-fixer` parser only matched `### {ID}` finding headers, not the canonical `## Fix N` shape per FRAMEWORK-BUG-030. Returned `<none parsed>` + false-positive `hallucinated` verdict despite real commits. Fix: new `parseFixNEntries` with finding-ID extraction via priority chain `**Finding ID**:` → `**Fix**:` → `**ID**:` → synthetic.
- **FRAMEWORK-BUG-036** — lifecycle phase didn't auto-advance through `apt:execute` / `apt:verify` / `apt:review` SKILL.md success-paths. Only `/apt:ship` flipped the phase (and only from `reviewing`, which never happened). Fix: each skill adds ONE `task update --lifecycle-phase` call at success-end, advancing `executing` → `verifying` → `reviewing`. `/apt:ship` atomic `reviewing → shipped-pending-merge` flip now fires reliably.
- **FRAMEWORK-BUG-037** — `task_isolation.from_branch: "current"` default silently chained WIP commits into new task PRs. **Behavioral default change**: now `"main"` with main-detection chain (`origin/HEAD` → `origin/main` → `origin/master` → local `main` / `master`). Explicit `--from-branch current` / `--from-branch HEAD` preserved for power users who want chaining. Projects with explicit `from_branch` in their own `.aperant/config.json` are unaffected.
- **FRAMEWORK-BUG-038** — `/apt:spar` Round-1 `verdict=cede` terminated unilaterally, collapsing every multi-round session to one round. Two SKILL.md edits: partner prompt template now requires `NEXT_WEAKEST_POINT`; Step 8 splits cede semantics — `--rounds 1` explicit terminates, `--rounds >= 2` (default) emits `next-round` regardless and requires two consecutive same-disposition rounds for convergence. No new flags, no new skills.

### Version

- `packages/framework/package.json`: 0.7.4 → 0.8.0
- `cli-baseline-version.txt` + `cli-baseline-help.txt` fixtures bumped

### Commits

- feat(framework): cross-task fan-out + 4 lifecycle/identity fixes (#208) — bd099c8f
- fix(framework): apt:spar Round-1 collapse (#210) — 567a63d0
- fix(framework): validate-evidence + audit-fixer parsers (#211) — 0f28a1af
- fix(framework): lifecycle auto-advance + from-branch default (#214) — b1abcb65
- (this one) — chore(framework): 0.8.0 — cross-task fan-out + 5 framework defects (BUG-033..038)

## 0.7.4 — 2026-05-18 — apt-pr-review context-budget skip prohibition

Docs-only patch release hardening the `apt-pr-review` "Fix Everything" contract against a live failure pattern where an orchestrator dropped confirmed minor findings mid-Phase-6 because its own context window felt heavy ("my orchestrator window was getting heavy after 6 specialist rounds + Round 1 work"). Pre-existing rule 5 in §Context Budget Rules already forbade skipping *phases*; this release closes the per-finding gap inside Phase 6.

### apt-pr-review SKILL.md

Three reading depths now carry the prohibition:

- **§Principle: Fix Everything (Phase 6)** — new "Hard rule" paragraph. Orchestrator context pressure, "many fixes already done," and marginal-value judgment are explicitly NOT valid skip reasons. Reminds the orchestrator that fix work runs in fixer windows, so orchestrator pressure does not justify cutting fixer inputs.
- **§Key Principles → 2. Fix Everything Found** — invalid-reason list extended to include "my context window is heavy" / "I've already done a lot this run."
- **§Common Mistakes → new 7a (Dropping Findings Because Orchestrator Context Feels Heavy)** — names the failure pattern verbatim, labels it an unauthorized policy deviation, prescribes spawning fixers in smallest batches possible (and filing a framework bug) rather than shipping a partial fix.

Only valid skip authorities remain the three in Key Principle #2: `fixable: false`, critical finding without user approval, file assigned to another fixer.

### Version

- `packages/framework/package.json`: 0.7.3 → 0.7.4
- `cli-baseline-version.txt` fixture bumped

### Commits

- chore(framework): forbid context-budget skips in apt-pr-review (a1eb033d)
- docs(framework): changelog entry for pr-review context-budget skip fix (43c98286)
- (this one) — chore(framework): 0.7.4 — apt-pr-review context-budget skip prohibition

## 0.7.3 — 2026-05-17 — C56 roadmap reconciliation + operator-blocked notes

Docs-only patch release reconciling the roadmap with reality. No code changes.

Investigation (apt-researcher) confirmed that all 10 C56 sub-deliverables (A1-A4 + B1-B6) were already implemented + tested + committed pre-0.7.1 via PR `apt/23-04-26_c56-absorbed-c59-...`. The roadmap's `c56` phase was stale at `in-progress` with empty `deliverables[]`. This release:

### Reconciliation

- **C56 → completed.** Flipped `milestones.json` c56 `status: "in-progress"` → `"completed"`, backfilled `deliverables[]` with the 10 sub-item commit refs, set `completed_at`. Net effect: Framework P7 (bug stability) milestone + the C56 artifact-lifecycle work are both reflected as done.
- **`apt-pr-review/SKILL.md:260` stale prose fixed.** The "Persist… BOTH `pr-state.json` AND `config.json`" line referenced pre-C56-A3 behavior. Replaced with "write to `pr-state.json` only" + an explanatory parenthetical pointing at the deprecation contract (legacy config entries are auto-promoted; new code never writes to config.json).

### Operator-blocked notes

Added `OPERATOR-BLOCKED (0.7.3 reconciliation)` prefix to three planned phases that cannot be closed agentically:

- **C23 Benchmark Phase 1 (lite)** — needs sustained operator capacity for ~16 controlled runs against multiple LLM CLIs (Aperant default + Aperant quick + Claude Code native + Superpowers), local Docker harness, ANTHROPIC_API_KEY, manual verify-checklist adjudication.
- **C37 Benchmark Phase 2 (full)** — hard-blocked on C23 signal; estimated ~3 months for 2-person team per original spec.
- **C52 Re-baseline route-eval paraphrase set** — needs ANTHROPIC_API_KEY in CI for multi-provider baselines + multi-reviewer human re-labeling. 0.7.2 cosmetic rename (Gate → Advisory Signal) already shipped. `must_complete_before` slipped from 0.7.3 to 0.7.4.

### Version

- `packages/framework/package.json`: 0.7.2 → 0.7.3
- `cli-baseline-version.txt` fixture bumped

### Commits

- (this one) — chore(framework): 0.7.3 — C56 roadmap reconciliation + operator-blocked notes

## 0.7.2 — 2026-05-17 — AUDIT-001 writer-site + 5 small follow-ups

Patch release closing seven 0.7.1 follow-ups in one cohesive bundle, plus full
end-to-end completion of FRAMEWORK-AUDIT-001 (the 0.7.1 reader pipeline +
upgrade-path migrator now have a matching fresh-install writer). Spar-locked
scope per Codex round 1 (3 cedes + 1 hold-revised). Eight atomic commits.

### Highlights

- **AUDIT-001 writer-site migration (S1)** — `init.mjs` `applyInterviewAnswers`
  now routes `multi_model.cross_verification.tools` to `config.local.json`
  on fresh install (the 0.7.1 reader pipeline + upgrade migrator already
  worked; the fresh-install writer was the missing half). 5 AUDIT-001
  per-device fields stripped from `templates/config.json` so schema-migrate
  doesn't keep re-detecting them as drift. Reader-site SKILL.md prose
  (`apt-quick`, `apt-verify-proof`, `apt-setup` Batches 4+7) +
  `workflows/verify-proof.md` updated to instruct consumers to use the
  merged loader, not raw `JSON.parse(readFileSync('.aperant/config.json'))`.
- **BUG-011 → WONTFIX-PENDING-UPSTREAM (S2)** — `docs/frameworks/spec-gaps.md`
  flipped. RTL 16.3.x still requires `React.act`; upstream issues
  `testing-library/react-testing-library#1413` and `#1364` confirm no clean
  fix forthcoming. `packages/ui/vitest.config.ts` carries a breadcrumb
  comment.
- **Backend `pretypecheck` guard (S3)** — `packages/backend/package.json`
  gains `"pretypecheck": "pnpm -C ../core build"` so direct
  `pnpm -C packages/backend typecheck` no longer traps on stale
  `packages/core/dist/`. CI / pre-push already build first via turbo.
- **release-notes-e2e baseline-red → green (S4)** — 3 tests at
  `release-notes-e2e.test.ts:99, :199, :233` go green. Root cause:
  framework's `vitest.config.ts` was the only vitest workspace not merging
  `defineVitestShim()`. Pre-fix, dev shells with `NODE_ENV=production`
  silently ignored `GH_BIN` overrides because `publish.mjs:49-55`
  `ghBin()` gates on `NODE_ENV === 'test'`. Fix: merge the shared shim.
- **C57 roadmap flip + regression test (S5)** — C57 (pr-review old-gh
  graceful degradation, fixed in 0.7.1 commit `34ff033a`) flips
  `.aperant/roadmap/framework/milestones.json` c57 to `status: completed`.
  PATH-injected `gh` stub regression test asserts envelope
  `{status:'ok', authorship_mode:'unknown', authorship_signal:'authorAssociation=null'}`
  on simulated `Unknown JSON field`.
- **C58 hybrid — kilocode display + `--kilocode` alias (S6)** — Per spar
  ID-01 + ID-02: internal `kilo` runtime id, `--kilo` flag, and `.kilo/`
  installRoot all stay unchanged (zero migration). `--kilocode` is now a
  deprecated alias of `--kilo` at the CLI flag parser. RUNTIMES descriptor
  gains `displayLabel: 'Kilo Code'`; init interactive prompts render the
  branded label.
- **C52 cosmetic bounded-defer (S7)** — `.github/workflows/route-eval-gate.yml`
  top-level `name:` renamed from "Route Eval Precision Gate" → "Route Eval
  Precision Advisory Signal" so the prose matches the actual non-blocking
  advisory behavior. Workflow body unchanged. `must_complete_before: "0.7.3"`
  added to the c52 phase entry for the full re-enable (per-model baselines
  + ANTHROPIC_API_KEY + human re-labeling).

### Locked design decisions

1. **No `.kilo/` → `.kilocode/` rename** — Codex spar evidence at
   `init.mjs:374`, `runtime-detect.mjs:184`, `check-version.mjs:128`,
   `uninstall.mjs:108` showed the silent-orphaning failure mode.
2. **`--kilocode` is alias of `--kilo`** — surface-level only; internally
   resolves to runtime id `kilo`.
3. **Backend pretypecheck in scope** — 1-line, high ROI, no scope widening.
4. **C52 ships cosmetic-only** — full re-enable defers to 0.7.3 via the
   new `must_complete_before` field.

### Spec-gaps cross-reference

- BUG-011 flipped to `✖ WONTFIX-PENDING-UPSTREAM` with empirical upstream evidence.
- AUDIT-001 row stays `🟢 COMPLETE` (entry was already updated in 0.7.1;
  the writer-site gap closure ships in this release).

### Commits (atomic per-subtask)

- `dd998a75` S3 — backend pretypecheck
- `479ca6a8` S2 — BUG-011 wontfix doc + vitest.config.ts breadcrumb
- `efc9da3e` S5 — C57 roadmap flip + gh-REST regression test
- `a395c991` S7 — C52 cosmetic rename + 0.7.3 defer marker
- `6c8edf5c` S6 — kilocode display label + --kilocode CLI alias
- `1484e4bb` S4 — defineVitestShim() in framework vitest.config.ts
- `61ec0adc` S1 — AUDIT-001 writer migration + reader-site doc passes
- `<this-commit>` S8 — doc-sync + version bump 0.7.1 → 0.7.2

### Migration notes

- The 5 AUDIT-001 per-device fields are now ABSENT from `templates/config.json`.
  Fresh installs no longer write them to shared `config.json`. Existing users
  upgrading from 0.7.0 → 0.7.1 (or 0.7.0 → 0.7.2) still get migrated via the
  upgrade-path migrator (unchanged from 0.7.1).
- `--kilocode` CLI flag is accepted as a deprecated alias of `--kilo`. Existing
  `--kilo` callers keep working unchanged.
- Backend `pretypecheck` adds a `pnpm -C ../core build` step before
  `tsc --noEmit`. CI / pre-push are unaffected (turbo already builds first).

### Out of scope

- C52 full re-enable (per-model baselines + ANTHROPIC_API_KEY for all PRs +
  human re-labeling) defers to 0.7.3 per the new `must_complete_before` field.
- C56 artifact lifecycle (separately in-progress under fw-p6).
- `router.llm.providers` AUDIT-001 HELD field stays HELD.

## 0.7.1 — 2026-05-17 — ship/pr-review/ci-watch hardening + planner caveats + AUDIT-001 completion (14-bug bundle)

This release ships the 7.1 bug bundle: 14 framework defects surfaced over three weeks of `apt:pr-review`, `apt:ship`, `apt:watch-ci`, and feature-audit usage, plus completion of FRAMEWORK-AUDIT-001 (5 deferred fields migrated to `.aperant/config.local.json`). Spar-locked per-bug fix scopes via 4 family rounds (apt-researcher + Codex MCP gpt-5.3-codex high reasoning), user-approved 4 design decisions.

### Highlights

- **pr-review evidence/audit pipeline (Family A)** — `validate-evidence` now supports opt-in fuzzy matching behind `--allow-approx-evidence` (Jaccard ≥ 0.85 + ≥2 line anchors) (BUG-014), schema-aware parser accepts both specialist + validated.md shapes with always-populated `processed_files` (BUG-032), audit-fixer worktree resolution fixed (BUG-015), fixer agent now commits atomically per finding (BUG-016), strict-ordered `fixes-applied` lookup eliminates parallel-fixer false-positives (BUG-030).
- **Ship gate verdict normalization (Family B)** — `review-clean` delegates to `parseReview` (handles `status:"fixed"` strings + `deferred-*` variants) (BUG-009), `verify-approved` accepts `approved-with-notes` behind threshold config + normalizes case/format via `parseQaSignoff` (BUG-012/-013), `gitignore-in-sync` embeds unified-diff in reason + unifies root resolution between dispatch warning and ship gate (BUG-028).
- **ci-watch GC + classifier (Family C)** — 3-part ci-watch state file cleanup (pre-arm gh check, post-close stop, new `ci-watch sweep --orphaned`) (BUG-027), stop-matrix defensive wait when no failing checks (BUG-031).
- **Framework infrastructure (Family D)** — sync-store-vs-render-committed-ref caveat in planner + researcher (BUG-025), `features-audit --task-id` worktree redirect + pre-commit husky guard + health-check orphan walker (BUG-025b, renumbered from BUG-025), shared `vitest.shared.mjs` shim + `apt-tools vitest doctor` diagnostic (BUG-026), `Agent` tool transport-level retry with runtime-logger telemetry (BUG-029).
- **FRAMEWORK-AUDIT-001 completion** — 5 deferred fields (`verification.open_report`, `multi_model.cross_verification.tools`, `preferences.date_format`, `preferences.time_format`, `preferences.quick_task_post_verify`) now migrate from `config.json` → `config.local.json` on upgrade. `router.llm.providers` HELD.

### Locked design decisions (user-approved)

1. **BUG-014 fuzzy evidence:** literal substring stays DEFAULT, fuzzy opt-in via `--allow-approx-evidence` with anchor requirements + downgraded status.
2. **BUG-016 fixer contract:** require atomic per-finding COMMITS (NOT stage-only). NO new audit verdict.
3. **BUG-032 parser:** schema-aware parsing for both specialist + validated.md shapes (NOT pipeline rewire).
4. **BUG-029 telemetry:** runtime logger channel (NOT `.aperant/events/*.jsonl` — would couple core to framework filesystem).

### Spec-gaps cross-reference

All 14 entries at `docs/frameworks/spec-gaps.md` flipped to ✅ FIXED. AUDIT-001 marked 🟢 COMPLETE. BUG-025 (features-audit) renumbered to BUG-025b. BUG-028 root-cause prose corrected (researcher refuted "two sub-checks ANDed" hypothesis, Codex confirmed).

### Commits (atomic per-subtask)

- `a08c270e` BUG-014 + BUG-032 — opt-in fuzzy evidence + schema-aware parser
- `607ad77a` BUG-030 — strict-ordered fixes-applied lookup
- `7304a6ed` BUG-016 — fixer commit discipline
- `7ba04d28` BUG-015 — audit-fixer worktree resolution
- `98e6d927` BUG-009 — review-clean delegates to parseReview
- `7cd8517f` BUG-012 + BUG-013 — verify-approved normalization + threshold
- `45a4c17c` BUG-028 — gitignore-in-sync diff in reason + root unification
- `d3df1142` BUG-027 — ci-watch state GC (3-part)
- `4aa6290e` BUG-031 — stop-matrix defensive wait
- `d2dc4a11` BUG-025-planner — sync-store-vs-ref caveat
- `689c6048` BUG-025b — features-audit --task-id + worktree redirect
- `165d4bde` BUG-026 — shared vitest shim + vitest-doctor
- `a2dc12f5` BUG-029 — Agent tool transport retry
- `4d80cc05` AUDIT-001 — migrate 5 deferred fields to config.local.json

### Migration notes

- `apt-tools features-audit` now accepts `--task-id <id>` and `--no-worktree-redirect`. Executors invoking from non-worktree cwd should pass `--task-id $TASK_ID`. Layer-1 detection (cwd-is-worktree) still works without the flag, so existing direct-worktree invocations are unchanged.
- `.husky/pre-commit` adds a worktree-isolation guard. Refuses staged `.aperant/features/*.json` on main when non-terminal worktree-isolated tasks exist in `.aperant/state.json`. Bypass via `SKIP_APT_WORKTREE_GUARD=1`.
- `apt-pr-review-fixer` agent prompt now requires per-finding commits. Existing fixer runs that stage-only will trip the Pre-Return Self-Audit and report `VERIFICATION: FAIL`. Re-spawn the fixer or run `git commit` manually.
- `Agent` tool (`packages/core/src/tools/builtin/agent.ts`) now auto-retries on 5xx / stream-idle errors with 2 attempts and `[5s, 15s]` backoff. Callers wanting telemetry should inject a `Logger` into the ToolContext.
- AUDIT-001 migrator runs on UPGRADE paths only (when `configMigrated === true`). Existing projects that ran `apt-tools init` since 0.7.0 will see their 5 fields moved to `config.local.json` on the next upgrade init invocation.

### Out of scope

- BUG-011 (vitest `NODE_ENV='test'` workaround location) remains BLOCKED — candidate for separate wontfix decision.
- `router.llm.providers` migration HELD per AUDIT-001 brief.
- 3 pre-existing `release-notes-e2e` test failures from the PR #140 baseline are NOT part of this bundle (per 0.7.0 CHANGELOG note).

## 0.7.0 — 2026-05-17 — install/discovery/ship pipeline hardening (8-bug bundle)

This release ships the 7.0 bug bundle: 8 framework defects discovered during the 0.6.6→0.6.7 self-upgrade pipeline (FRAMEWORK-BUG-017 through -024). Each fix was independently verified by Codex/GPT-5 review before implementation. -021 and -022 bundled atomically per Codex recommendation.

### Highlights

- **`/apt:update` reliability** (-017): in-band drift detection in `apt-tools route` synthesizes the `update_check` chip when the workspace source is ahead of `.aperant/deps/`; new `requireMinVersion` helper for cmd preflight as a future-guard for new flags; `apt:update --from-source` skill flag for monorepo self-host case.
- **`/apt:ship` honesty** (-018): CI watcher banner now distinguishes state-recording from wake-registration. New four-way branch in §7.2 reads `scheduler_handle`; when null, emits explicit next-action instruction (`Register a wake-up: CronCreate every ~3min running '/apt:watch-ci --tick --pr {N}'`). Applied to both primary and ask-deferred paths.
- **`/apt:watch-ci` worktree safety** (-019): auto-merge unconditionally forbids `gh pr merge --delete-branch` from the worktree-rooted watcher (the flag tries to `git switch <base>` in the cwd, which fails when base is checked out in the primary worktree). Local branch + worktree cleanup remains `/apt:close-task`'s job via existing `computeWorktreeCleanup` (best-effort with named failure modes).
- **`task close-merged --narrate-only`** (-020): the flag was documented as SKILL-level but `parseFlags` parsed it generically and the cmd silently dropped it. Now wired as a CLI flag — read-only enumeration of `pending_narration[]` returning one entry per row in `closed[]` with `narration_pending: true`. SKILL.md §4 calls this before iterating per-row drain via the existing `narration-drain` primitive. At-least-once retry safe (verified narrator idempotence in `apt-team-docs-narrator`).
- **Install/discovery surface** (-021 + -022, bundled): `init` now detects + removes the legacy `.agents/skills/` codex tree per file against a shared `LEGACY_INSTALL_PATHS` constant (user-added skills preserved). Opt-out via `--keep-legacy`. New `legacy_cleanup` envelope field. **Health-check unified with check-version**: `readSkillInstallState` is now manifest-aware (`apt-file-manifest.json` precedence over `.apt-version` legacy fallback). Malformed manifests surface as stale (not silently healthy). `host` field stays `'claude-code'` for backward compat; new `runtime` field exposes descriptor id.
- **CLAUDE.md catalog generator** (-023): the hardcoded `APT_SECTION` constant in `copy.mjs` is gone. The section is built from `discoverSkills` (filesystem source of truth — same helper the router uses). Hand-curated sub-sections (Fast Path Guarantee, Discoverability, Feature Registry, Config) live in `templates/aperant-claude-md-appendix.md`. Foreign-row defensive check with `--force-overwrite-catalog` escape hatch (exit-2 on foreign rows). **Breaking for unattended upgrades:** unattended `init` flows on projects with custom CLAUDE.md rows must pass `--force-overwrite-catalog`.
- **`/apt:spar` partner transport ladder** (-024): replaced single-transport gate (`codex-companion.mjs` only) with 3-rung ladder: MCP tool → plugin shim → raw CLI. MCP-first per user preference with 60s timeout + automatic fallthrough to next rung on hang. Symmetric for codex/claude/gemini. `host-detect` extended additively with `host.partner_transports`. Raw codex invocation is `codex exec` (precedent from `apt-pr-review`).

### Spec-gaps cross-reference

All 8 entries at `docs/frameworks/spec-gaps.md#framework-bug-017-installed-deps-drift` through `#framework-bug-024-spar-partner-transport-too-narrow`. Each entry includes the Codex review verdict that drove the implementation choices.

### Commits

- `cac1db9c` BUG-017 — deps drift detection
- `3d9b1a2f` BUG-019 — forbid `gh pr merge --delete-branch`
- `cdd24c79` BUG-024 — apt:spar 3-rung partner transport ladder
- `a21c7d32` BUG-023 — CLAUDE.md APT_SECTION generator + appendix
- `bf51f438` BUG-023 — typecheck fix
- `42251bd2` BUG-021+022 — codex legacy cleanup + health-check manifest-aware
- `256edc11` BUG-018 — apt:ship CI watcher banner honesty
- `8a74b0f9` BUG-020 — task close-merged --narrate-only

### FRAMEWORK-BUG-023 — CLAUDE.md catalog generator (no more stale `APT_SECTION`)

`apt-tools init` previously regenerated the `## Aperant Framework` block
in `CLAUDE.md` / `AGENTS.md` from a hardcoded 19-row constant in
`src/cli/util/copy.mjs`, silently dropping the 18 skills added after the
constant was last hand-edited. The constant is gone; the section is now
built from `discoverSkills` (filesystem source of truth — same helper
the router uses at runtime), and the hand-curated sub-sections (Fast
Path Guarantee, Discoverability, Feature Registry, Config) live in a
template file at `templates/aperant-claude-md-appendix.md`.

- **NEW** `src/cli/util/aperant-section.mjs` — `buildAptSection({...})`
  emits the wrapped section. Skills are filtered to `user_invocable &&
  !internal`, sorted with the bare `apt` router first then ASCII by
  name, and pipe characters in descriptions are escaped (`apt:triage`
  is a live case).
- **NEW** `templates/aperant-claude-md-appendix.md` — hand-curated
  sub-sections, read verbatim by the generator. Editing this file is a
  normal docs PR with no `.mjs` rebuild.
- **NEW** `--force-overwrite-catalog` flag on `apt-tools init`.
  **Breaking for unattended upgrades:** if the on-disk CLAUDE.md /
  AGENTS.md contains row IDs the generator does NOT produce ("foreign
  rows"), init now exits 2 with the structured envelope
  `{ code: 'instruction-file.foreign-rows', foreignRowIds: [...] }`
  instead of silently clobbering. Re-run with
  `--force-overwrite-catalog` to regenerate anyway, or remove the
  foreign rows manually. Greenfield installs and brownfield installs
  with NO foreign rows are unaffected.



### persona-aware release-notes system (task persona-aware-changelog-and-release-note_16-05-26)

Two-skill split: `/apt:docs changelog` keeps the dev-flavored
`CHANGELOG.md` surface unchanged; a new opt-in `/apt:release-notes`
skill ships a persona-aware user-facing artifact. Schema bumps
`.aperant/config.json` v4 → v5 with a new `changelog.{dev,release_notes}`
block (default-OFF release-notes, dev-changelog stays default-ON).

- **New skill** `/apt:release-notes` (`stage: ops`, `intent: capture`,
  `task_context: none`). Three modes — `draft --pr <n>` for towncrier-
  style per-PR fragments, `compile <tag>` for tag-time narrative + the
  non-negotiable `## Needs Triage` bucket (spec ID-6: never silent-drop
  unmapped fragments), `publish <tag>` for `gh release create/edit`
  with a graceful `{status: 'skipped', reason: 'gh-not-found'}`
  envelope when `gh` is missing. DEEP-track-only — Fast Path Guarantee
  pinned by `apt-release-notes-quick-exempt.test.ts`.
- **New CLI verb** `apt-tools release-notes <draft|compile|publish>`.
  Deterministic I/O only — no LLM in the CLI, no prompt templates.
  Persona-voice tuning lives in the SKILL.md via the
  `appendices/persona-voice.md` posture loader.
- **Schema v4 → v5.** `packages/framework/templates/config.json` gains
  the `changelog` block; the existing
  `runMigrationsAgainstTemplate()` path silently auto-adds the new
  block to any v4 config via the `auto_added` bucket. No bespoke
  migration code, no destructive paths — TD-1 pins the safety net.
- **Setup integration.** `/apt:setup` gains Batch 9 (Changelog & Release
  Notes) with two top-level questions + an audience / persona-filter
  sub-picker under Q2=Yes. Per-project opt-in is explicit; presence of
  `.aperant/personas.json` does NOT auto-enable (spec ID-7).
- **Ship integration.** `/apt:ship` §2.6 runs only when opted in.
  Feature-registry diff → persona mapping → `Release note: …`
  auto-draft in the PR body. When `require_pr_field=true` (default)
  AND auto-draft fails, ship emits the exact error string `[APT] No
  release-note signal found — author manual note in PR body or label
  PR no-notes` and stops before `gh pr create`.
- **Close-task integration.** `/apt:close-task` §2.6 drops the
  per-PR fragment on merge via the same idempotent `apt-tools
  release-notes draft` verb. Failure is additive bookkeeping (appends
  to `pending_release_note[]` in state.json) — never blocks close.
- **Tests.** TD-1 schema migration, TD-2 fragment idempotency, TD-3
  compile + triage bucket, TD-4 ship-autodraft decision matrix, TD-5
  end-to-end opt-in flow (mocked `gh`), TD-6 quick-exempt regression.
  32 cases across 6 release-notes test files.

The framework's own `CHANGELOG.md` stays dev-flavored — Aperant ships
to developers, so this file is the right audience already. The
user-facing surface that this work introduces is for projects that
ship to non-developer humans.

## 0.6.7 — 2026-05-16 — /apt:update pipeline fixes (Codex spar) + framework-bug bundle

### /apt:update pipeline (Codex spar 2026-05-15)

Three line-cited defects from a 2026-05-15 Codex adversarial spar made
`/apt:update` silently a no-op on a real version bump (e.g. 0.6.5 →
0.6.7). All three are now fixed and proved by a new end-to-end test.

- **Bug 1: `check-version` was npm-blind.** `src/cli/commands/check-version.mjs:80-82`
  previously read `framework_version` from the local kernel's own
  `package.json` and hard-coded `update_available:false` per runtime
  (lines 59, 70). The skill's `<context>` block documented fields like
  `framework.latest_version` + `update_available` + `dev_install_ahead`
  that `check-version` never emitted — those fields lived only in the
  SessionStart worker's cache file (`hooks/apt-check-update-worker.js`
  at `~/.cache/aperant/apt-update-check.json`). **Fix:** `check-version`
  now reads that cache file when it exists and is fresh (< 1 h,
  `schema_version=1`) and merges `framework.latest_version`,
  `update_available`, `dev_install_ahead`, `not_published`,
  `stale_files_total`, `lookup_error` into its envelope. On a stale or
  missing cache the same field names are emitted with null/false/0
  sentinel values so the skill's parser stays single-shape (ID-03).
  Bails out on any non-1 `schema_version` (R2 mitigation against
  future cache schema drift).
- **Bug 2: `init` upgrade no-op ignored runtime-manifest drift.**
  `src/cli/commands/init.mjs:354-370` decided `upgrade_noop` solely on
  `.aperant/config.json` schema drift, and lines `1064-1078` returned
  `status:up_to_date` BEFORE `installRuntimes` ran. Reproduction:
  deleting `.claude/apt-file-manifest.json` and re-running init still
  returned `up_to_date` with the manifest unrestored. **Fix:** new
  `--force-runtimes` flag on `init`. When set AND `upgrade_noop` would
  fire, the cmdInit synthesizes a real `InstallAnswers` shape from the
  existing config (preserving visibility/autonomy/cross-verification)
  and falls through to `installRuntimes` + `runKernelInstall` +
  `finishInit` as on the upgrade-with-drift path. The flag is strictly
  additive — callers without it keep the existing no-op short-circuit
  (preserves `init-upgrade-drift.test.ts` case (a) — verified by 27
  passing tests across `init-upgrade-drift.test.ts` and
  `init-flag-parser.test.ts`).
- **Bug 3: skill read fields `check-version` didn't emit.**
  `skills/apt-update/SKILL.md:49-54` parsed `framework.installed_version`,
  `framework.latest_version`, `stale_files_total`, `update_available`,
  `dev_install_ahead`, `not_published` — none of which the CLI emitted.
  **Fix:** Bug 1's fix lights up the fields the skill was already
  reading; the SKILL.md prose is rewritten to also document a new
  Step 1.5 (`npm view @aperant/framework version` + `npm install
  --prefix .aperant/deps @aperant/framework@<latest>` when newer, with
  10 s npm timeout per ID-04) and to pass `--force-runtimes` in Step 3.
  Added a `TODO(followup)` comment near the hardcoded
  `.aperant/deps/...` path so the PATH-only / npx-only install case is
  tracked but explicitly deferred (ID-05).

- **Schema status.** Envelope changes are strictly additive — the new
  `framework`, `update_available`, `dev_install_ahead`, `not_published`,
  `stale_files_total`, `lookup_error` fields are all nullable; existing
  callers ignore unknown fields. `--force-runtimes` is opt-in. No
  `schema_version` bump required.
- **Decision source.** Codex spar 2026-05-15.
- **Tests.** New: `check-version-cache-merge.test.ts` (6 cases),
  `init-force-runtimes.test.ts` (3 cases),
  `update-pipeline-bump-e2e.test.ts` (2 cases). Existing:
  `init-upgrade-drift.test.ts` case (a) still green —
  `--force-runtimes` is opt-in, not a behavior change. Manual smoke
  test (kernel binary upgrades in place via `npm install --prefix`) is
  deferred to release-time validation; the e2e test scopes to
  cache-merge + force-runtimes composition.

`task: fix-apt-update-pipeline-3-bugs_15-05-26`

### Other framework-bug fixes shipped in this release (rolled up from previous unreleased)

- **fix:** ship gate `review-clean` and `parse-review` now accept `fixed:true`
  alongside `resolved:true` as resolution markers
  ([FRAMEWORK-BUG-009](../../docs/frameworks/spec-gaps.md#framework-bug-009-review-clean-fixed-key)).
  The reviewer skill writes `fixed:true` paired with `fix_commit` — the gate
  and consistency normalizer previously only recognized `resolved:true`,
  causing false-positive blocks when a review fixed findings in-loop.
- **fix:** `apt-tools ci-watch tick --payload-file <path>` lets the
  orchestrator skill pass pre-captured `gh pr checks` + `gh pr view`
  payloads directly to the cmd, closing the production-path gap
  ([FRAMEWORK-BUG-010](../../docs/frameworks/spec-gaps.md#framework-bug-010-ci-watch-payload-file)).
  The cmd still does NOT shell out to gh — that boundary stays with the
  orchestrator.
- **feat:** new `loadMergedProjectConfig` deep-merge reader in
  `src/cli/config/load.mjs` for per-user overrides via
  `.aperant/config.local.json` (gitignored). `diagram.mcpConsent` migrated
  to `config.local.json` to close a per-user consent-leak surface
  ([FRAMEWORK-AUDIT-001](../../docs/frameworks/spec-gaps.md#framework-audit-001-config-local-json-migration)).
  Five additional per-user candidates documented and deferred per a recorded
  Codex spar round (cede-partial outcome).
- **blocked:** FRAMEWORK-BUG-011 (vitest `NODE_ENV='test'` workaround
  location). Spec proposed moving the env override from `vitest.config.ts`
  to `vitest.setup.ts`; empirical regression in `use-analytics.test.ts`
  (`ERR_UNKNOWN_BUILTIN_MODULE: No such built-in module: node:`) disproved
  the hypothesis. See
  [FRAMEWORK-BUG-011](../../docs/frameworks/spec-gaps.md#framework-bug-011-vitest-node-env-workaround)
  for follow-up options.

### apt:setup onboarding-marker (rolled up from PR #137)

- **fix:** fresh installs now walk the full Batch 1-8 onboarding flow
  via an explicit onboarding marker, replacing the previous heuristic
  that short-circuited on the presence of any `.aperant/` content.

## 0.6.5 — 2026-05-14 — Drop aggregate envelope cap

Replaces the aggregate `ENVELOPE_MAX_BYTES` cap in
`src/cli/route/envelope.mjs` with a per-skill description budget. The
aggregate cap was bumped 9 times in 3 weeks (8192 → 11264) and was
theater on 1M-token hosts (30 skills × 256 bytes = 0.28 % of context).
It was a proxy for two unrelated constraints — classifier precision
(already measured by route-eval) and per-skill description quality
(never actually enforced) — and shouted at the wrong author each time
a new skill landed.

- **New constant.** `MAX_DESCRIPTION_BYTES = 256` is exported from
  `envelope.mjs`. The budget binds visible AND internal skills, so an
  internal SKILL.md cannot ship a 4 KB description through the
  side-channel.
- **New helper.** `enforceDescriptionBudget(skills, budgetBytes)`
  replaces `truncateLongestFirst`. It walks each skill once: passes
  through if the wrapped description fits the budget; else tries
  `truncateDescriptionToFirstSentence`; if the trim succeeds and now
  fits, marks `truncated:true` and increments `truncated_count`; if the
  trim still doesn't fit (or trim was a no-op), records the offender
  (`{ name, bytes, description }`) and stops. The builder throws with
  the offender named so the next fix lands on the right author the
  first time.
- **Error code rename.** The `cmdRoute` catch branch now emits
  `error_code: 'skill_description_too_long'` (was `'envelope_overflow'`).
- **Decision source.** Codex spar 2026-05-14.
- **Tests.** `envelope-budget.test.ts`, `envelope-truncation.test.ts`,
  and `route-envelope-builder.test.ts` rewritten for the per-skill
  pathway. Aggregate-cap pins and the 400-byte headroom invariant are
  gone.

Schema unchanged (still v4). The router envelope JSON shape is
unchanged for consumers — the only externally observable difference is
the renamed error_code in the over-budget path.

`task: 14-05-26_drop-aggregate-envelope-max-bytes-cap-fr-03rb`

## 0.6.4 — 2026-05-13 — Upgrade `--yes` infers visibility from git tracking

Patch on top of 0.6.3 closing the last surface of the 0.6.0/0.6.1
visibility-flip dogfood incident.

- **`init --yes` upgrade path now infers `share.visibility` from git
  tracking** when the existing config is pre-visibility (v3 schema) and
  no explicit `--solo`/`--team` was passed. Heuristic: if `git ls-files`
  reports any tracked file under `.aperant/` or any runtime install root
  (`.claude/`, `.codex/`, …), assume `team` — these repos were already
  sharing framework state via git. Otherwise fall back to `solo`. The
  recorded `applied_defaults` entry tags the inferred case with
  `source: "--yes (inferred from git tracking)"` so unattended installs
  remain auditable. Net effect: a team repo that runs `--yes` no longer
  has `.gitignore` silently rewritten to solo allow-list rules.
- **`/apt:update` skill hardened.** Step 3 now explicitly forbids adding
  `--yes` or any Class A flag. If `init` returns a `requires_input`
  envelope, the skill surfaces it verbatim and stops — the user must
  pick team vs solo themselves. The 0.6.4 framework inference is a
  defense-in-depth backstop, not the primary boundary.

No schema bump (still v4). No breaking changes.

## 0.6.3 — 2026-05-12 — README license + tarball slim

Follow-up patch on top of the 0.6.2 first-public release. Three fixes:

- **License consistency.** README badge + footer corrected from MIT to
  AGPL-3.0-only to match `package.json` and the actual `LICENSE` file.
  No license change — only the README claim was wrong.
- **README docs references removed.** The framework's `docs/` directory
  is not currently shared; links pointing into it (workflows.md,
  plugin-distribution.md, api.md, the aperant-framework-spec deep link)
  have been dropped from the README.
- **Tarball slimmed.** Added negation globs to `files` to exclude
  `__tests__/`, `*.test.*`, `*.spec.*`, and the test sourcemap variants
  from the published tarball. Dropped the framework's own `docs/` and
  `CONTRIBUTING.md` from `files`; added `CHANGELOG.md`. Result: 2.5 MB
  → 1.8 MB tarball, 11.8 MB → 7.9 MB unpacked, 3498 → 2040 files (~42%
  reduction).

## 0.6.2 — 2026-05-12 — First public npm release + router envelope cap bump

First publish to the public npm registry (`registry.npmjs.org`). Prior
versions (0.1.0 – 0.6.1) were only published to a local Verdaccio proxy
during dogfood development; they are not retrievable from public npm.
Public history starts here.

Also lands PR #92: `ENVELOPE_MAX_BYTES` bumped 9216 → 9472 in
`src/cli/route/envelope.mjs`. The compact JSON routing payload crossed
9234 bytes after recent skill additions; the old 9216 cap rejected
otherwise-valid inputs with `envelope_overflow`. Same 256-byte step as
prior bumps (C47/C48/C49/C56); ~240 bytes of fresh headroom matches the
"≥1 more small skill" target documented in the cap-bump history.

## 0.6.1 — 2026-05-01 — Agent-aware non-TTY install + schema-drift detection

Closes the dogfood incident where `apt-tools init . --claude --codex` from
inside a Claude Code session silently defaulted to `visibility:'solo'`,
which made `injectProjectGitignore` write a blanket `.aperant/` ignore line
that clobbered the team-mode allow-list. Class A settings (visibility,
runtimes, scanNow) now fail loudly in non-TTY mode unless the user opts in.

Also adds first-class detection of schema drift between the user's
`.aperant/config.json` and the shipped `templates/config.json` — new keys
no longer accumulate as `undefined` in the user's config without a path to
review them.

### Highlights

- New flags on `apt-tools init`: `--team` / `--solo` (mutually exclusive
  visibility), `--autonomy <0..3>` (default autonomy level),
  `--accept-new-defaults` (apply Class A template defaults during a
  schema migration), `--yes` (CI escape hatch — accept all Class A
  defaults silently, recorded as `source:"--yes"` in `applied_defaults`).
- New envelope variant: `{"status":"requires_input", ...}` returned at
  exit code 2 when a Class A setting is missing in non-TTY. Includes
  `requires_explicit`, `applied_defaults`, `agent_runtime`, and a
  copy-pasteable `rerun_with` command. Mirrors the
  `config/upgrade-layout.mjs` `exitWith(envelope, 2)` convention.
- New envelope variant: `{"status":"up_to_date", ...}` returned when an
  existing config matches the template schema (no drift). Skips the
  runtime install — `/apt:update` is the right surface for refreshing
  skills on a no-drift run.
- `init` envelope on success grows four fields: `applied_defaults`,
  `explicit_inputs`, `agent_runtime`, `migration` (the last summarizes
  schema drift handled). `configMigrated:bool` joins `configCreated`.
- TTY upgrade gate: when `init` runs interactively against an existing
  config with drift, clack prompts ONLY for the new Class A keys
  (existing-key widgets are NOT re-rendered). No drift → "Already up to
  date" note + outro, install skipped. Class B / auto_added only →
  summary note + silent apply.

### Migration notes

- **Breaking for unattended `apt-tools init` callers** (CI, scripted
  installs). A non-TTY `init` with no flags and no existing config now
  exits 2 with a `requires_input` envelope instead of silently defaulting
  to `visibility:'solo'`. To restore the old behavior:
  - **Pass the explicit Class A flags**: `apt-tools init . --team --claude --codex`
  - **Or pass `--yes`** to accept all Class A defaults: `apt-tools init . --yes`
- **Existing configs at `version < 4`** will trip the schema-migration
  gate on the next `init` call. Pass `--accept-new-defaults` (Class B
  fills) or `--yes` (Class A + Class B fills) to clear it
  non-interactively, or run interactively to be prompted.
- The dogfood incident the new gate prevents: `apt-tools init . --claude --codex`
  from a Claude Code session, which silently defaulted to
  `visibility:'solo'` and wrote a blanket `.aperant/` ignore line that
  clobbered the team-mode `.aperant/.gitignore` allow-list. After this
  release that command exits 2 until visibility is named explicitly.

### Internals

- New: `packages/framework/src/cli/install/setting-class.mjs` — single
  source of truth for Class A vs Class B classification + flag mapping +
  per-key `classify()` helpers.
- New: `packages/framework/src/cli/config/schema-migrate.mjs` — pure
  function that walks the template's leaf paths, buckets each into
  `applied_defaults` (Class B) / `requires_explicit` (Class A or invalid)
  / `auto_added` (template-internal additions). Bumps `migrated.version`
  only when `requires_explicit` is empty.
- New: `packages/framework/src/cli/util/agent-runtime.mjs` —
  `detectAgentRuntime()` env-var sniff returning
  `'claude-code' | 'cursor' | 'aider' | 'unknown'` with deterministic
  precedence. Messaging-only (never affects which codepath runs).
- Modified: `packages/framework/src/cli/commands/init.mjs` —
  `parseInstallFlags` extended with the new flag surface;
  `nonInteractiveAnswers` rewritten as a fork between fresh-install and
  upgrade paths; `interactiveInterview` gains an upgrade gate at the top
  while staying byte-identical for fresh installs; `cmdInit` handles the
  three new sentinels (`requiresInput`, `upgrade_noop`, plus the existing
  `cancelled`).
- Tests: `setting-class.test.ts` (19), `agent-runtime.test.ts` (6),
  `schema-migrate.test.ts` (9), `init-non-tty-class-a.test.ts` (6,
  including 2 env-parity cases for spec R4),
  `init-upgrade-drift.test.ts` (4 — the R3 matrix). Existing
  `init-flag-parser.test.ts` extended from 12 to 22 cases.

## 0.6.0 — 2026-04-27 — CLI layering refactor (envelope/exitCode shape)

Internal refactor of `apt-tools` into three explicit layers — Layer 1 cmd
bodies, Layer 2 wrappers, Layer 3 dispatcher — closing the
"`process.exit()` scattered across cmd files" footgun that prevented
in-process unit testing of cmd branches.

### Highlights

- Every Layer 1 cmd in `src/cli/commands/` now returns a `CommandResult`
  tuple (`{ envelope, exitCode }` for JSON output OR `{ rawText, exitCode }`
  for non-JSON output) instead of calling `process.exit()` directly.
  Verified: `grep -rn 'process\.exit' packages/framework/src/cli/commands/`
  returns 0 lines (excluding comments).
- New `src/__tests__/cmd-pure/<cmd>.test.ts` set covers every migrated cmd
  in-process via the D-05 `runCmd` harness with `strictExit:true` default —
  any forgotten `process.exit` trips the harness immediately.
- Slim subprocess gate (`src/__tests__/cli-contract-<cmd>.test.ts`)
  preserves byte-identical CLI contract end-to-end. Replaces the legacy
  `smoke-*.test.ts` set (now empty).
- 3 new e2e-pipeline lifecycle chain tests (`task-lifecycle`,
  `pr-review-lifecycle`, `roadmap-lifecycle`) pin cross-cmd state-flow
  contracts in-process.
- D-03 closure: `process.argv[1]` is read in exactly one place
  (`src/cli/util/bin-path.mjs:resolveAptToolsBin`). All other consumers
  (worktree, worktree-install, task) go through that helper.

### Wall-time impact

_Final wall-time numbers measured in S26 — TBD; numbers will be filled in
after the wall-time + final coverage gate runs._ The expected outcome is
the framework test suite p99 ≤ 2 min on CI (vs ~10 min pre-refactor)
because cmd branch coverage no longer requires per-test subprocess
spawn-and-tear-down.

### Migration notes

- External callers parsing apt-tools stdout: zero break. The envelope
  shape is byte-identical pre-refactor; the JSON contract is the public
  surface. Internal layering changes are transparent to consumers.
- Internal callers that previously imported `cmdX` and expected stdout
  side-effects: they now get the `CommandResult` tuple back. The
  `PipelineHarness` (the only known internal consumer outside the test
  suite) was already on the new shape via `core/src/pipeline/harness.ts`'s
  `runCli` shim and stays green throughout the refactor (verified by
  `pipeline-harness-cli-spawn.test.ts` running on every commit).
- The `process.argv[1]` deprecation in cmd bodies is a soft API change —
  any external script that imported a cmd and depended on `process.argv[1]`
  reflecting the apt-tools entrypoint must now pass `projectDir` to
  `resolveAptToolsBin` directly.

## 0.5.0 — 2026-04-24 — C56 artifact lifecycle closeout (mechanistic + systemic; absorbs C59)

Minor release closing the post-ship artifact leaks surfaced after PR #62
merged on 2026-04-23. Combines what was originally planned as two phases
(C56 mechanistic + C59 systemic) into one PR because they share ~5
source files and C59 depends on C56's classification contract. Six
subsystem changes across four altitudes — CLI primitives, skill
contracts, lifecycle state machine, and documentation surface.

### Root cause eliminated

The framework split **producers** (apt:plan / execute / review / ship /
pr-review — write artifacts while an agent is present) from **closers**
(roadmap set-phase-status / task close / workflow destroy — should run
post-merge when no agent is present). No skill walked the closer side
because merge happens on GitHub hours/days after `/apt:ship` exits. C56
introduces the missing closer (`/apt:close-task`), the state machine
waypoint it transitions through (`shipped-pending-merge`), and the
per-subtask drift audit that prevents leaks from accumulating during
execution.

### New: classification contract (B4)

`packages/framework/src/cli/artifacts/classification.mjs` exports the
single authoritative committable-vs-ephemeral table for every `.aperant/`
subpath. See [`docs/artifact-lifecycle.md`](docs/artifact-lifecycle.md)
for the full matrix. Self-staging helpers, the audit CLI, pr-review
close-round cleanup, and the per-subtask drift check all consume this
contract — one source of truth, extension = one file.

### New: `apt-tools audit pending-artifacts` (B2)

Enumerates uncommitted changes under `.aperant/` and splits them into
`committable[]` / `ephemeral[]` / `unknown[]` via the classification
contract. `--stage` auto-`git add`s committable paths. `unknown[]` is
the planner-gap signal: a path touched by the subtask that the contract
doesn't recognize yet.

### New: `apt-tools task prune --older-than <duration>` (A4)

Retention for `.aperant/tasks/{id}/` directories. Accepts durations like
`30d`, `6w`, `1y`; refuses to prune tasks still in `active_tasks[*]`.
Gitignored by design, so safe default is user-opt-in.

### New: `apt-tools workflow destroy --archive` (A1 reframed)

Investigation found `workflow destroy` already unlinks the file — the
real bug was that **nobody called destroy post-merge**. `--archive`
moves the workflow state JSON into `.aperant/workflows/archive/` with
retention. B6 below wires the caller into `pr-review close-round`.

### New: `/apt:close-task` skill (B1)

Post-merge closer. Enumerates `shipped-pending-merge` tasks, polls
`gh pr view` for PR merge state, and performs closeout on `MERGED`:
phase flip, authorship override strip, worktree GC, task archive,
`task.closed.merged` event. Offline / rate-limited invocations are
tolerated (tasks skipped with reason, user re-runs). Supports
`--all` / `--task <id>`. Spawns the ROADMAP.md narrator (moved from
`/apt:ship`) when a closed task has a phase_id.

### New: `shipped-pending-merge` lifecycle phase (B5)

Additive enum between `reviewing` and `completed`. `/apt:ship` flips
tasks to this waypoint on PR open; `/apt:close-task` flips to
`completed` on confirmed merge. Tasks stay in `active_tasks[*]` until
`/apt:close-task` runs — addresses the "shipped == landed" lie that
produced the original drift.

### Changed: pr-review authorship overrides (A3 migration)

Authorship overrides now write to per-PR `.aperant/pr-reviews/pr-{N}/pr-state.json`
instead of global `.aperant/config.json`. Precedence: `pr-state.json` →
legacy `config.authorship_overrides['pr-{N}']` (deprecated, removed
next release) → global `config.authorship_override`. `init-pr` promotes
legacy entries on first call. Closes the stale-per-PR-config class of
drift.

### Changed: `apt-tools task close` auto-phase-flip (A2)

Opt-in via `task_isolation.auto_close_phase: true`. When the task has
`milestone_id` + `phase_id` linkage AND the linked PR is merged AND the
phase is `in-progress`, close auto-flips the phase to `completed` with a
`chore(aperant): roadmap sync` commit. Off by default for safety.

### Changed: `apt-tools pr-review close-round` post-merge cleanup (B6)

When `gh pr view` reports the PR is `MERGED` / `CLOSED`, close-round
best-effort unlinks matching `.aperant/workflows/pr-<N>-*.json` files
and strips `config.pr_review.authorship_overrides['pr-<N>']`. Uses
`APT_GH_FIXTURE_JSON` for deterministic test runs. Offline degrades to
`cleanup: {skipped: true}`, never fails close-round itself.

### Changed: `/apt:ship` SKILL.md (B2/B5)

- New §0.5 Pre-PR Committable Drift Check (runs audit pending-artifacts
  --stage before PR creation; hard-stops on unknown[]).
- §6 records `pr_url` on the task record via `task update --pr-url`
  after `gh pr create` succeeds.
- §8 replaces `task close --verdict approved` with `task update
  --lifecycle-phase shipped-pending-merge`.
- §9 narrator spawn removed; narration moves to `/apt:close-task`.

### Changed: `/apt:execute` SKILL.md (B3)

- New §3e-drift runs audit pending-artifacts --stage after every atomic
  subtask commit. Committable drift is amended in; `unknown[]` routes to
  `{task_dir}/execution-decisions.md` as a `classification-gap`.

### Self-staging (B4, internal)

State-mutating CLI commands (`roadmap add-milestone` /
`add-phase` / `set-phase-status` / `delete-phase` / `delete-milestone`
/ `render`, `pr-review init-pr` / `detect-authorship` /
`record-comment` / `close-round`) now `git add -f` their committable
file writes automatically. Caller's next commit picks them up — no more
"I forgot to stage the roadmap sync" drift class.

### Fixed

- `apt-close-task/SKILL.md` frontmatter used `stage: close` + `intent:
  close-task`, both outside the allowed enums. Every `apt-tools init`
  was hard-failing. Changed to `stage: ship` + `intent: x-close-task`
  (custom x- prefixed intent, schema-valid). Existing installs should
  run `/apt:update` to pick up the fix.

### Documentation

- New: [`docs/artifact-lifecycle.md`](docs/artifact-lifecycle.md) —
  authoritative reference for the classification contract, the
  `shipped-pending-merge` lifecycle waypoint, self-staging contract,
  authorship-override migration window, and retention model.
- Updated: [`docs/docs-coherence.md`](docs/docs-coherence.md) links the
  artifact-lifecycle reference from the "How the planner enforces this"
  section.
- Updated: `CLAUDE.md` "Multi-Agent Workflow" section gains the
  shipped-pending-merge note + `/apt:close-task` entry in the skills
  table + one-paragraph self-staging-contract subsection.

---

## 0.4.1 — 2026-04-23 — C53 warning silencer + C54 language-agnostic worktree bootstrap + C55 multi-language ecosystems cheatsheet

Patch release closing out the fw-p6-product-context cleanup bundle on PR #61
(post-fw-p6). All 0.4.0 behavior is preserved; 0.4.1 adds:

- **C53 — YAML warning silencer on install.** Silences the "Keys with
  collection values" warning from `yaml` during worktree-install YAML parsing
  (manifest-scanner + toolchain-detect). Cosmetic-only; no behavior change.

- **C54 — language-agnostic worktree bootstrap.** New opt-in
  `task_isolation.bootstrap_worktree: true` config turns `apt-tools worktree
  create --task <id>` into a language-agnostic bootstrap that detects every
  ecosystem at the worktree path (pnpm/npm/yarn/yarn-berry/bun/uv/poetry/
  pip-venv/cargo/go/bundler/composer/gradle) and spawns a detached install
  child per ecosystem under a per-task env isolation envelope (PATH/LANG/
  LC_ALL/TMPDIR only; HOME stubbed to `.aperant/tasks/<id>/install/home/`
  with skeleton configs referencing only allowlisted registries). A URL
  allowlist + lockfile scanner rejects `git+ssh://`, `git@`, `git+http(s)://`,
  tarball URLs outside the registry allowlist, `file:` / `link:` / TOML
  `path=` locals, and multi-lockfile coexistence BEFORE spawning any install.
  New commands: `apt-tools worktree-install <dir> --task <id>` and
  `apt-tools detect-ecosystem <dir>` (path-scoped). New events:
  `install.started` / `install.ok` / `install.failed`. New statusline chip
  `installing [<eco>] <Ns>` (or `install failed` in red on any task with a
  failed install). Escape hatches: `--no-bootstrap` flag,
  `task_isolation.skip_bootstrap_for: [globs]` config,
  `task_isolation.bootstrap_cmd: [script]` config. Cargo uses
  `cargo fetch --locked` only (never `cargo build`); Gradle always uses
  `--no-daemon` to avoid cross-worktree daemon deadlocks.

- **C55 — multi-language worktree ecosystems cheatsheet.** New doc at
  `packages/framework/docs/worktree-ecosystems.md` covers all 12 v1
  ecosystems with anti-patterns (never symlink `node_modules`, never copy
  `venv/`, never share `target/` across branches), a frozen-install flag
  matrix, an offline-prefer flag matrix, and a lifecycle-script-exec matrix.
  `CLAUDE.md` gets a new "Multi-language worktree workflow" section pointing
  at the cheatsheet.

### Zero-regression guarantee

With `task_isolation.bootstrap_worktree: false` (the default), `apt-tools
worktree create` behavior is byte-identical to pre-0.4.1; `state.active_tasks
[<id>]` keys are unchanged. C54 is strictly opt-in.

## 0.4.0 — 2026-04-22 — fw-p6 production release + C51 discuss --auto

Minor version bump closing out the fw-p6-product-context milestone (C47–C51)
and the associated infrastructure fixes shipped via PR #60. All 0.3.x
behaviour from the 0.3.x entry below remains present; 0.4.0 adds:

- **`apt:discuss --auto` + `--review-auto`** (C51) — joint-reasoning batch
  mode that resolves all gray areas in one LLM call instead of per-decision,
  with optional single pause-at-end confirmation. DEEP tasks auto-run
  advanced elicitation. Triggered by `--auto`, parent `--yolo`, or
  `autonomy=3` config (precedence: flag > parent > config).
- **`--runtime <id>`** override on `apt-tools route` — completes the D-02
  unknown-host hint that previously pointed at a non-existent flag. Validates
  against `supported_hosts`, overrides `detectHost().cli`.
- **Route-eval OAuth token support** — the precision-smoke script
  `run-route-eval.mjs` now speaks Anthropic's API directly (via `fetch`) with
  auto-detected auth: OAuth tokens (`sk-ant-oat*`) use `Authorization: Bearer`
  + `anthropic-beta` header; API keys (`sk-ant-api*`) use `x-api-key`. Long-
  lived OAuth tokens from `claude setup-token` work end-to-end in CI.
- **`--no-fail` flag on run-route-eval.mjs** — downgrades the D-01 kill
  criterion from blocking exit to advisory warning. CI uses this pending C52
  paraphrase-set re-baseline (labels are too opinionated about plan-first-vs-
  full-auto; both opus and sonnet route "build a feature" → `apt:plan` not
  `apt:run`).
- **Review-loop quality fixes** — 21 findings from `/apt:pr-review` on PR #60
  landed including path-traversal containment on `create-docs --output-dir`,
  TOCTOU guard on `canonical_source`, r7-persona-alignment rule rename to
  convention, and CLAUDE.md skills-table coverage for the three new skills.

### Known limitations

- **C52 follow-up** — `.github/workflows/route-eval-gate.yml` is advisory-only
  until the paraphrase set is re-labeled with multi-reviewer agreement.
  Re-enables as blocking when baselines stabilize.

## 0.3.x — 2026-04-22 — Product-context ecosystem (C47–C50)

Adds the four-skill product-context chain shipped in fw-p6. All new content is
additive; no existing behaviour changed.

### New skills

- **`apt:bootstrap`** (C47) — generate or refresh `PROJECT.md` (the
  product-context source of truth). Supports greenfield (7-section interview)
  and brownfield (recon-and-infer) postures, controlled by the D-03 compound
  heuristic. Derives `.aperant/product-context.json` sidecar on every write.
  CLI surface: `apt-tools bootstrap <detect|scaffold|derive-sidecar|run>`.
- **`apt:personas`** (C48) — discover 3-5 product personas grounded in code
  signal (deterministic role-scan) and strategic intent from PROJECT.md. Writes
  `.aperant/personas.json` with `team_id: 'framework-local'` sentinel; respects
  D-10 canonical-source guard (refuses `--fresh` when the app wrote
  `canonical_source: 'cloud'`). CLI surface: `apt-tools personas
  <scan|rubric|read|write|record-run>`. Custom intent: `x-personas`.
- **`apt:create-docs`** (C49) — generate persona-first product docs at
  `docs/product/` from PROJECT.md + personas.json + the features registry.
  Deterministic `StructurePlan` phase (CLI) + per-feature prose (apt-docs-author
  agent). D-05 section-level diff-and-confirm on re-invocation. CLI surface:
  `apt-tools create-docs <plan|render-nav|read|run>`. Custom intent:
  `x-document`.

### New gate

- **`r-persona-alignment`** (C50) — G10 gate checking that a planned task's
  `declared_persona_tier` aligns with its milestone's `focus_tier` list. Fires
  at `stage=execute` and `stage=ship`. Skips when `personas.json` is absent
  (D-06) or the task predates C50 (D-07). D-05 severity is `warn` by default;
  escalates to `fail` when the milestone sets `focus_tier_enforcement:
  "blocking"`. Structured D-08 rejection picker (Defer / Reassign / Promote /
  Force) carried in `evidence.rejection_options[]` for UX consumption.

### New consistency rule

- **`r7-persona-alignment`** — plan ↔ personas.json internal consistency
  rule (cross-artifact check that `declared_personas[]` IDs resolve to real
  personas and declared tier strings are valid). Severity: `warn`. Stages:
  plan, execute, ship.

### New custom intents registered

`x-personas` (`apt:personas`) and `x-document` (`apt:create-docs`) are the
first `x-*` dogfood custom intents validating the router-0.3.0 extension path.
Both are listed in `docs/intents.md`.

### `--runtime` flag (all skills)

`apt-tools route` now accepts `--runtime <host>` to override the detected
runtime. `SUPPORTED_HOSTS` is the exhaustive allowlist; unknown values exit 1
with a structured error envelope.

---

## 0.3.0 — 2026-04-22 — Breaking: router envelope redesign

Router rewrite from a code-first classifier (hardcoded skill tables,
regex keyword matching) to an LLM-first context provider (filesystem
scan → thin envelope → host LLM picks a skill inline). Upgrade path:
run `/apt:update` after pulling; the framework installer
(`apt-tools init`) now refuses to install when any canonical SKILL.md
fails schema validation, so the upgrade is safe even on multi-runtime
trees.

### Envelope fields removed

- `mode: "task"` — free-text input used to produce a `mode: "task"`
  envelope with `track`, `primary_skill`, `options`, and friends. The
  deterministic flag-override path (`--quick` / `--deep` / `--debug`)
  is now the only way a route call returns a resolved track; free-text
  input lands on `mode: "classify"` with the host LLM picking.
- `mode: "question"` — the regex-based question heuristic is gone.
  Host LLMs route inquiry-shaped prompts to `apt:discuss` via the
  envelope, so questions now get a real investigation loop instead of
  a bare "answer inline" handler.
- `KNOWN_SKILLS` / `AGENT_SKILLS` / `INLINE_SKILLS` / `AGENT_NAME_MAP` /
  `QUESTION_PATTERNS` — these duplicates of per-skill metadata were
  deleted outright. `src/cli/commands/route.mjs` no longer carries any
  hardcoded skill set.

### Envelope fields added

- `mode: "classify"` — the new default envelope shape for free-text
  input. Carries `skills[]`, `classification_meta`, `host_capabilities`,
  `task_isolation`, `update_check`, `task_description`,
  `autonomy_override`, `execution_mode`, `needs_autonomy_prompt`, and
  `classification.source` (`"flag-override"` or `"host-model-deferred"`).
- `classification_meta.trust_notice` — instruction string telling the
  host LLM that skill descriptions are untrusted user-supplied data
  wrapped in `«...»` delimiters. Mitigates prompt injection in skill
  descriptions (red-team finding on third-party skills).
- `classification_meta.discovered_roots` — the list of trusted
  `trusted_skill_sources` roots that were scanned. Host LLMs can lazy-
  load a full SKILL.md at `discovered_roots[skill.root]/<skill.rel>`.
- Per-skill `stage`, `intent`, `spawns_agent`, `agent_name`,
  `default_execution_mode`, `execution_modes`, `gates` — read straight
  from the skill's fat frontmatter instead of from a central lookup.

### New commands + APIs

- `apt-tools check [<project-dir>] [--skills-root <path>]` — validates
  every canonical `packages/framework/skills/apt-<slug>/SKILL.md` against
  the new `SkillFrontmatterSchema` and cross-references every
  `gates: [...]` entry against the live gate registry. Exits 1 on any
  error. `apt-tools init` runs the same check before every install as
  a pre-flight guard.
- `trusted_skill_sources` in `.aperant/config.json` — optional allowlist
  of absolute or project-relative paths the router's skill scan will
  accept. Omit the field to let `getTrustedSkillSources()` compute safe
  defaults (framework repo skills, monorepo dev path, consumer deps
  install, host-CLI's install root). Set it to narrow the discovery
  surface (e.g. only the vendored `@aperant/framework` copy). See
  [`docs/intents.md`](docs/intents.md) for when to narrow.
- `CORE_INTENTS` + `CUSTOM_INTENT_REGEX` exported from
  `src/cli/skill-author/contract.mjs`. Core intents:
  `implement | capture | discuss | investigate | design | ship | configure | debug`.
  Custom intents use the `x-*` prefix (e.g. `x-marketing-draft`) and
  graduate to the core enum after appearing in ≥3 shipping skills. See
  [`docs/intents.md`](docs/intents.md) for the promotion path.
- `detectHost()` (`src/cli/host/detect.mjs`) recognizes all 14 installed
  runtimes by their env vars: existing (claude-code, codex, gemini-cli,
  opencode, pi) plus 10 new (cursor, windsurf, kilo, copilot,
  antigravity, augment, trae, qwen, cline, codebuddy). `cli: 'unknown'`
  combined with free-text input now emits a structured error envelope
  (`error_code: "unknown_host"`) listing the supported hosts.
- `src/cli/install/cli-to-install-root.mjs` — shared `cliToInstallRoot(cli)`
  mapping that both the router (skill scan) and the install pipeline
  consume via a new `cliId` field on every RUNTIMES entry.

### Frontmatter contract

Every `apt-<slug>/SKILL.md` frontmatter must now validate against
`packages/framework/schemas/skill-frontmatter.json` (JSON Schema
Draft-07, mirrored by `SkillFrontmatterSchema` in
`src/cli/skill-author/contract.mjs`). The 14 required keys:

- `name` (regex `^apt(?::[a-z][a-z0-9-]*)?$` — bare `apt` or `apt:<slug>`)
- `description` (non-empty one-line string; wrapped in `«...»` in the
  envelope)
- `stage` — enum of 14 lifecycle stages
- `intent` — core enum or `x-*` custom
- `when_to_use` — one-sentence trigger heuristic
- `user_invocable` / `internal` / `spawns_agent` — state flags
- `agent_name` — required non-null when `spawns_agent: true`
- `allowed-tools` / `argument-hint` — GSD-style
- `gates` — gate IDs only; cross-referenced at install time
- `default_execution_mode` / `execution_modes` — 5-mode set:
  auto | step | plan-mode | plan-only | research

Legacy keys (`triggers`, `color`, `apt-skill-version`, `user-invocable`)
stay as optional pass-throughs. Install transforms preserve them.

### Upgrade step

In every installed project:

```bash
/apt:update          # refresh skills/agents/hooks across every runtime
```

The `/apt:update` flow reads `~/.cache/aperant/apt-update-check.json`
(written by the SessionStart worker), runs `apt-tools init . --<installed-runtimes>`,
and clears the update chip from the statusline. No manual migration.

### Statusline chip behavior

Unchanged. The update chip surfaces via the SessionStart hook's cache
file; the chip clears automatically after `/apt:update`. C34/C35
infrastructure shipped ahead of this release for exactly this upgrade.

### Host-model classification precision baseline

The 0.3.0 release is gated on `packages/framework/scripts/run-route-eval.mjs`
producing `precision_overall >= 0.85` against claude-opus-4-7 on a
40-line paraphrase eval set. The baseline result file is committed
under `.aperant/benchmarks/route-eval/`. If the number drops below
0.85 in a future release, D-01's kill criterion fires and routing
reverts to a subprocess classifier fallback.

**Known limitation — track precision (non-blocking).** The 0.3.0
baseline posts `precision_skill = 0.872` (release gate — MET) but
`precision_track = 0.660` on the same eval set. Track inference
(QUICK / STANDARD / DEEP / DEBUG) is fuzzier than skill pick because
a single skill often spans multiple tracks depending on prompt scope
— e.g. "open a PR for the OAuth work" (`apt:ship`) can legitimately
be QUICK or STANDARD depending on whether there's already a draft to
finalize. The release gate (AC-RC-3) is on skill precision only; the
lower track number is captured as a follow-up to either (a) tighten
SKILL.md `when_to_use` phrasing to disambiguate tracks, or (b) move
track to a second-pass classification separate from the skill pick.
Runner output carries both numbers (`precision_skill`, `precision_track`)
so the follow-up has a reproducible baseline to improve against.

---

## Earlier versions

Prior history lives in commit messages on `main`. The router rewrite in
0.3.0 is the first entry in this CHANGELOG because it's the first break
significant enough to need one. Earlier semver bumps were additive
(`/apt:design`, `/apt:mockup`, `/apt:verify-proof`, etc.) and are
documented in `VERSIONING.md`'s pre-1.0 stability section.
