chore(voyage): release v5.0.0 — remove bespoke playground + /trekrevise + Handover 8; render produced artifacts to HTML + link, annotate via /playground

The v4.2/v4.3 bespoke playground SPA (~388 KB), the /trekrevise command,
Handover 8 (annotation → revision), the supporting lib/ modules
(anchor-parser, annotation-digest, markdown-write, revision-guard), the
Playwright e2e suite, and the @playwright/test / @axe-core/playwright
devDeps are removed. A browser walkthrough found the playground borderline
unusable, and it duplicated the official /playground plugin's
document-critique / diff-review templates.

In their place: scripts/render-artifact.mjs — a small, zero-dependency
renderer that turns a brief/plan/review .md into a self-contained,
design-system-styled, zero-network .html (frontmatter folded into a
<details> block). /trekbrief, /trekplan, and /trekreview call it on their
last step and print the file:// link; to annotate, run /playground
(document-critique) on the .md and paste the generated prompt back.

Resolves the v4.3.1-deferred findings as moot (their target files are
deleted). npm test green: 509 tests, 507 pass, 0 fail, 2 skipped.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-12 14:05:07 +02:00
commit 916d30f63e
96 changed files with 620 additions and 14716 deletions

View file

@ -1,6 +1,6 @@
# Handover Contracts (voyage-suite local pipeline)
This document is the single source of truth for the file formats that pass between the four commands of the `trekplan` pipeline. When you fork the plugin or extend a stage, the contracts below tell you what every producer must write and what every consumer is allowed to assume.
This document is the single source of truth for the file formats that pass between the commands of the `trekplan` pipeline. There are seven handovers. When you fork the plugin or extend a stage, the contracts below tell you what every producer must write and what every consumer is allowed to assume.
For each handover, the same headings appear in the same order: **Producer**, **Consumer**, **Path conventions**, **Frontmatter schema**, **Body invariants**, **Validation strategy**, **Versioning**, **Failure modes**.
@ -16,7 +16,6 @@ Each artifact carries an explicit version field. Schema bumps are coordinated:
| `progress.json` | `schema_version` (top-level) | `"1"` |
| `review.md` | `review_version` (frontmatter) | `1.0` |
| `.session-state.local.json` | `schema_version` (top-level) | `1` (number) |
| `brief.md` / `plan.md` / `review.md` (annotated) | `revision` (frontmatter) | `0` (implicit), `1+` after `/trekrevise` |
## Breaking-change protocol
@ -37,7 +36,6 @@ Each artifact carries an explicit version field. Schema bumps are coordinated:
| 5. progress.json (resume) | `lib/validators/progress-validator.mjs` |
| 6. review → plan | `lib/validators/review-validator.mjs` |
| 7. session-state (multi-session resume) | `lib/validators/session-state-validator.mjs` |
| 8. annotation → revision | `lib/parsers/anchor-parser.mjs` + `lib/parsers/annotation-digest.mjs` (parsing) and the existing brief / plan / review validators (forward-compat — additive fields tolerated) |
Every validator exposes a CLI: `node lib/validators/<name>.mjs --json <path>` returns `{valid, errors[], warnings[], parsed}`. Errors and warnings have stable `code` fields for downstream tooling.
@ -440,96 +438,6 @@ The `next-session-prompt-validator` (`lib/validators/next-session-prompt-validat
---
## Handover 8 — annotation → revision
**Handover 8 closes the operator-feedback loop.** Where Handovers 14 flow forward (brief → research → plan → execute), Handover 5 makes execute resumable, Handover 6 routes review findings back into planning, and Handover 7 makes multi-session work survivable across fresh chats, **Handover 8 lets a human operator annotate an artifact (brief, plan, or review) inside the playground and feed those annotations back into a revision cycle without losing the original artifact's byte-for-byte content**. The pipeline becomes round-trippable: a single artifact can be annotated, revised, and re-rendered repeatedly, each revision recorded with a deterministic digest in frontmatter.
**Producer:**
- The operator (manual step) — open the artifact in `playground/voyage-playground.html`, drag-select or hover-to-anchor, fill comment + intent in the modal, click "Eksporter batch" to copy the `/trekrevise` invocation to clipboard.
- `/trekrevise --project <dir>` (Phase 3 — apply) — consumes the pasted batch, writes anchor comments back into the target artifact, increments `revision:`, appends entries to `source_annotations:`, and computes a fresh `annotation_digest`.
**Consumer:**
- Subsequent `/trekplan --project <dir>` if the revised brief or plan needs further re-planning.
- Subsequent `/trekexecute --project <dir>` if the revised plan is ready for execution.
- All existing validators (brief, plan, review) — they tolerate the additive frontmatter fields without version bumps.
**Path conventions:**
- The annotated artifact is the same canonical file in `{project_dir}/` (`brief.md`, `plan.md`, `review.md`). Revisions are *in-place* — no `plan-revN.md` shadow files. Audit trail lives in git commits + the `revision:` counter + `source_annotations:` list inside frontmatter.
- The playground itself lives at `playground/voyage-playground.html` (single self-contained file with vendored `markdown-it` + `highlight.js` under `playground/lib/`).
**Frontmatter schema (additive — applies to brief.md, plan.md, review.md):**
| Field | Type | Required | Allowed values | Notes |
|---|---|---|---|---|
| `revision` | number | optional | `0`, `1`, `2`, … | Absent = `0` (forward-compat). Incremented by `/trekrevise` on each apply |
| `source_annotations` | list | optional | block-style YAML list of dicts | Absent = empty. Audit trail of every annotation that has been folded into this artifact |
| `annotation_digest` | string | optional | first 16 hex chars of canonical SHA-256 over the sorted `source_annotations` array | Absent = no annotations applied yet. Deterministic — re-applying the same set yields the same digest |
| `revision_reason` | string | optional | free-form text | **Required only** when the revision is non-additive (e.g. removed scope, replaced an SC). Operators are encouraged to fill it for any revision |
Each entry in `source_annotations` is a YAML dict:
```yaml
source_annotations:
- id: ANN-0001
target_artifact: plan.md
target_anchor: step-3
intent: change # change | add | remove | clarify | risk
comment: "consider rolling back if cache_creation jumps >10%"
line: 412
```
**Anchor format (block-level HTML comments inside the body):**
```html
<!-- voyage:anchor id="ANN-0001" target="plan.md#step-3" line="412" -->
```
Anchors are placed **only at block boundaries** — not in list items, not mid-paragraph, not at line-start collision points where a markdown parser might fold them into surrounding content. The playground enforces this discipline via `validateAnchorPlacement()` in `lib/parsers/anchor-parser.mjs`. Anchors survive markdown rendering as comments (no visible artifact) and round-trip through `parseAnchors → addAnchors → stripAnchors` byte-identically (SC2 contract).
**Body invariants:**
- The artifact's existing required sections remain untouched. `/trekrevise` writes anchor comments only at block boundaries declared by the operator's annotations.
- After each apply: byte-identical content outside anchor blocks. Stripping all anchors with `stripAnchors()` MUST yield the artifact's original body modulo the operator's deliberate prose edits.
**Validation strategy:**
| Layer | When | What |
|---|---|---|
| `revision` shape | every read | Number (or absent → treat as `0`). Negative or non-integer rejected by validator |
| `source_annotations` shape | every read | Array of dicts; each dict must include `id`, `target_artifact`, `target_anchor`, `intent`. Other fields tolerated (drift-WARN) |
| `annotation_digest` shape | every read | 16-char lowercase hex; presence requires `source_annotations` non-empty |
| Anchor placement | `/trekrevise` Phase 2 (validate) | `validateAnchorPlacement()` rejects in-list / mid-paragraph / line-start anchors before write |
| Round-trip integrity | `/trekrevise` Phase 4 (post-write) | `stripAnchors()` of the just-written file MUST equal the pre-write body |
The parsers (`lib/parsers/anchor-parser.mjs`, `lib/parsers/annotation-digest.mjs`) are pure functions — no I/O, fully unit-tested. The existing brief / plan / review validators in `lib/validators/` already tolerate the new optional fields (forward-compat — see Step 12 manifest pins).
**Forward-compat — additive principle:** Artifacts without any of the four new fields validate as `revision: 0` with empty annotations. This means every brief / plan / review written before v4.2 remains valid without migration. Producers (operators using `/trekrevise`) opt into the schema by writing the fields; consumers tolerate their presence or absence equally.
**Idempotence:** Re-applying the same annotation batch on the same artifact (same anchors, same intents, same comments) yields the same `annotation_digest` and no body diff outside anchor refresh. This is enforced by `computeAnnotationDigest()` canonicalizing the sorted `source_annotations` array before hashing — operators can replay a batch safely, and the test suite pins this with `tests/parsers/annotation-digest.test.mjs`.
**Versioning:** No `*_version` bump for v4.2 — the four new fields are additive. A future schema break (e.g. removing `target_anchor` in favor of structured pointer) would bump `brief_version` / `plan_version` / `review_version` per the breaking-change protocol.
**Failure modes:**
- `ANNOTATION_PLACEMENT_INVALID``/trekrevise` Phase 2 halts; operator re-anchors via playground
- `ANNOTATION_DIGEST_DRIFT` → digest computed at apply-time differs from operator's expected digest in the pasted batch; halt with mismatch report (suggests the batch was hand-edited after export)
- `ANNOTATION_ROUNDTRIP_FAIL` → post-write `stripAnchors()` does not yield the original body; rollback restores the byte-identical pre-write file from `*.local.bak`
- Parser failures from `parseAnchors()` produce `{valid: false, errors: [...]}` and `/trekrevise` halts before any write. The atomic-write pattern (tmp-file + rename) guarantees the canonical file is never partially updated.
### § Lifecycle
The annotation cycle has three stages — no persistent state file (unlike Handover 7), only the artifact frontmatter and git history record what happened:
| Stage | Owner | Action |
|---|---|---|
| Annotate | Operator + playground UI | Open artifact in `playground/voyage-playground.html`, anchor + comment, click "Eksporter batch" → clipboard contains a `/trekrevise --project <dir> --apply '{...JSON...}'` command |
| Apply | `/trekrevise` | Phase 1 parse the pasted batch, Phase 2 validate placement, Phase 3 atomically write anchors + frontmatter (`revision: N+1`, append to `source_annotations`, recompute `annotation_digest`), Phase 4 round-trip integrity check, Phase 5 commit |
| Re-render | Playground (next open) | The freshly-revised artifact loads with anchors visible as inline comment markers; operator can iterate or call `/trekplan` / `/trekexecute` on the revised file |
**Single-iteration MVP (v4.2 scope):** Each operator annotation batch produces one `revision:` increment. Multi-iteration loops (e.g. revise → re-review → revise again without operator intervention) are deferred indefinitely — the brief's SC4 wording is single-revision, and operator validation that multi-iteration is desired has not been collected (see plan Alternatives table). The single-iteration MVP keeps the audit trail unambiguous: one `revision:` bump per `/trekrevise` invocation.
**Stale-anchor principle:** Unlike `.session-state.local.json`, anchors are durable and intentionally retained — they document *why* a revision happened. There is no `--cleanup` for anchors. Removing them is a manual operator decision, executed via the playground "Strip all anchors" affordance or hand-editing the source. `/trekrevise` does not remove anchors automatically.
---
## Stability summary
| Handover | Validation strength | Owner | Risk |
@ -541,6 +449,5 @@ The annotation cycle has three stages — no persistent state file (unlike Hando
| 5. progress.json | shape + resume readiness | this plugin | medium — drift during compaction handled by pre-compact-flush hook (CC v2.1.105+) |
| 6. review → plan | strict at write, soft at read | this plugin | low — additive feedback loop; consumer falls back gracefully when source_findings is absent |
| 7. session-state (multi-session resume) | required-fields + status enum + drift-WARN extras | this plugin | low — readers tolerate unknown keys; writers are owned by trekexecute Phase 8 + helper command |
| 8. annotation → revision | parser-strict at write (placement + round-trip), validator-soft at read (additive fields) | this plugin | low — additive frontmatter; existing artifacts validate as `revision: 0` without migration |
When extending the plugin or adding a new pipeline stage, follow the same pattern: produce an artifact with a versioned frontmatter (or `schema_version` for JSON), write a validator under `lib/validators/`, add fixtures under `tests/fixtures/`, and add an entry to this document.