# 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. For each handover, the same headings appear in the same order: **Producer**, **Consumer**, **Path conventions**, **Frontmatter schema**, **Body invariants**, **Validation strategy**, **Versioning**, **Failure modes**. ## Versioning policy Each artifact carries an explicit version field. Schema bumps are coordinated: | Artifact | Field | Current | |---|---|---| | `brief.md` | `brief_version` (frontmatter) | `2.0` | | `research/*.md` | (implicit; tracked via `type: trekresearch-brief`) | unversioned | | `plan.md` | `plan_version` (frontmatter) | `1.7` | | `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 1. Bump the artifact's version field. 2. Update the matching validator in `lib/validators/`. 3. Add a fixture under `tests/fixtures/` covering both old and new shapes. 4. Document the change in `MIGRATION.md` with at least an N-1 compatibility window in the validator (read both shapes; warn on old, fail only after one minor version of warning). 5. Bump the plugin version in `package.json` and `.claude-plugin/plugin.json`. ## Validator → handover map | Handover | Validator | |---|---| | 1. brief → research | `lib/validators/brief-validator.mjs` | | 2. research → plan | `lib/validators/research-validator.mjs` | | 3. architecture → plan | `lib/validators/architecture-discovery.mjs` | | 4. plan → execute | `lib/validators/plan-validator.mjs` | | 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/.mjs --json ` returns `{valid, errors[], warnings[], parsed}`. Errors and warnings have stable `code` fields for downstream tooling. --- ## Handover 1 — `brief.md` → research/ **Producer:** `/trekbrief` Phase 4g (after `brief-reviewer` stop-gate passes or iteration cap is hit). **Consumer:** `/trekresearch` Phase 1 (mode parse + brief validation). **Path conventions:** - Project-dir mode (recommended): `.claude/projects/{YYYY-MM-DD}-{slug}/brief.md`. - Legacy / loose mode: any path passed via `--brief `. **Frontmatter schema:** | Field | Type | Required | Allowed values | Notes | |---|---|---|---|---| | `type` | string | yes | `trekbrief` | Hard-coded discriminator | | `brief_version` | string | yes | `"2.0"` (current) | Bump on schema change | | `created` | date | yes | YYYY-MM-DD | | | `task` | string | yes | one-line description | | | `slug` | string | yes | URL-safe slug | Used in project_dir | | `project_dir` | string | yes | `.claude/projects/{date}-{slug}/` | | | `research_topics` | number | yes | ≥ 0 | | | `research_status` | string | yes | `pending \| in_progress \| complete \| skipped` | State machine — see below | | `auto_research` | bool | optional | `true \| false` | | | `interview_turns` | number | optional | ≥ 0 | | | `source` | string | optional | `interview \| manual` | | | `brief_quality` | string | optional | `complete \| partial` | Set when iteration cap is hit | **Body invariants:** required sections (validator runs in strict mode at write-time, soft mode at read-time): - `## Intent` - `## Goal` - `## Success Criteria` Optional but standard sections: `## Non-Goals`, `## Constraints`, `## Preferences`, `## Non-Functional Requirements`, `## Research Plan`. **Validation strategy:** | Layer | When | What | |---|---|---| | Frontmatter parse | every read | YAML subset; reject nested dicts | | Required fields | every read | All `BRIEF_REQUIRED_FRONTMATTER` present | | Type discriminator | every read | `type === "trekbrief"` | | Status enum | every read | `research_status ∈ allowed values` | | **State machine** | every read | `research_topics > 0 && research_status === "skipped"` requires `brief_quality === "partial"` | | Body sections | strict only | All `BRIEF_BODY_SECTIONS` present | **State machine** detail: a brief that says it has research topics but skipped them must explicitly admit it (via `brief_quality: partial`). This is the most common failure mode the validator catches. **Versioning:** current is `2.0`. There are no live `1.x` briefs; remove legacy paths in next major. **Failure modes:** - `BRIEF_NOT_FOUND` → consumer halts with a usage message - `FM_MISSING` → file has no frontmatter; halt - `BRIEF_WRONG_TYPE` → file is not a brief; halt - `BRIEF_MISSING_FIELD` → strict halt; soft-mode warning - `BRIEF_STATE_INCOHERENT` → strict halt; soft-mode warning (incoherence will haunt downstream agents) - `BRIEF_MISSING_SECTION` → strict halt; soft-mode warning --- ## Handover 2 — research/*.md → plan **Producer:** `/trekresearch` Phase 7 (synthesis + brief writer). **Consumer:** `/trekplan` Phase 1 (project-dir auto-discovery) + `planning-orchestrator` (consumes findings as context). **Path conventions:** - Project-dir mode: `.claude/projects/{YYYY-MM-DD}-{slug}/research/{NN}-{topic-slug}.md` (sorted by filename). - Legacy: `.claude/research/trekresearch-{date}-{slug}.md`. **Frontmatter schema:** | Field | Type | Required | Allowed values | |---|---|---|---| | `type` | string | yes | `trekresearch-brief` | | `created` | date | yes | YYYY-MM-DD | | `question` | string | yes | the research question | | `confidence` | number | optional | `[0.0, 1.0]` — strongly recommended | | `dimensions` | number | optional | ≥ 1 | | `mcp_servers_used` | list | optional | server names | | `local_agents_used` | list | optional | agent names | | `external_agents_used` | list | optional | agent names | Missing `confidence` is a warning, not an error — but downstream planning has no signal to weight findings. **Body invariants:** required sections (strict mode): - `## Executive Summary` - `## Dimensions` Optional: `## Local Context`, `## External Knowledge`, `## Triangulation`, `## Sources`, `## Recommendations`. **Validation strategy:** schema parse + body-section check. Per-file by `validateResearch`; whole-directory by `validateResearchDir`. Anchoring back to brief topics is currently best-effort, not enforced (planned for a future minor). **Versioning:** unversioned — research briefs are write-once read-once; no migration concern. If schema changes, change `type` discriminator or add `research_brief_version`. **Failure modes:** all same shape as brief (`RESEARCH_*` codes). Default soft mode in plan Phase 1 — research drift does not block planning, but warnings surface in the user-visible summary. --- ## Handover 3 — architecture/ → plan (EXTERNAL CONTRACT) **This is the only handover where the producer is in a *different plugin*.** The `architecture/overview.md` (and optional `gaps.md`) are produced by an external opt-in architect plugin (no longer publicly distributed; the filesystem slot remains available for any compatible producer). When no producer is installed, this handover is absent — and that is fine. **Producer:** external opt-in architect plugin (no longer publicly distributed). **Consumer:** `/trekplan` Phase 1 (architecture-discovery) + `planning-orchestrator` Phase 7 (cross-reference architecture-note as priors during synthesis). **Path conventions:** - Canonical: `{project_dir}/architecture/overview.md` - Optional: `{project_dir}/architecture/gaps.md` - Tolerated alternatives (with warning): `architecture-overview.md`, `overview.markdown`, `README.md` **Frontmatter schema:** **unenforced.** This is the external contract — `trekplan` does not validate the format. We sniff only the first H1 heading. **Body invariants:** **unenforced.** We never read body content beyond the first heading. **Validation strategy:** **drift-WARN, never drift-FAIL.** | Detection | Result | |---|---| | File at canonical path | `found: true`, no warnings | | File at known alternative path | `found: true`, warning `ARCH_NON_CANONICAL_OVERVIEW` | | Loose `*.md` files in `architecture/` not in known set | warning `ARCH_LOOSE_FILES` | | No `architecture/` dir | `found: false`, no warnings | The validator (`lib/validators/architecture-discovery.mjs`) is intentionally minimal. It is unit-tested to assert it does NOT read body content beyond the first heading — guarding against scope creep into the producer's territory. **Versioning:** the external producer owns its schema. We do not version this handover from our side. **Failure modes:** none. Discovery always succeeds (returns `found: false` if absent). The handover is additive. --- ## Handover 4 — `plan.md` → execute **Producer:** `planning-orchestrator` Phase 5 (plan synthesis) + Phase 5.5 (schema self-check via `plan-validator --strict`). **Consumer:** `/trekexecute` Phase 2 (plan parsing) + `--validate` mode. **Path conventions:** - Project-dir: `{project_dir}/plan.md` - Legacy: `.claude/plans/trekplan-{date}-{slug}.md` **Frontmatter schema:** | Field | Type | Required | Allowed | |---|---|---|---| | `plan_version` | string | yes | `"1.7"` (current) | **Body invariants (strict, v1.7):** 1. Top-level structure: - `## Implementation Plan` heading present - One or more `### Step N: ` headings, numbered 1..N contiguously - `### Step N: ` is the literal canonical form — colon + space 2. Forbidden narrative-drift heading forms (Opus 4.7 regression guard): - `## Fase N` (Norwegian) - `### Phase N` - `### Stage N` - `### Steg N` (Norwegian variant) 3. Per-step Manifest block — **required for every step**: - Indented fenced YAML: ` ```yaml\n manifest:\n ...\n ``` ` - Required keys: `expected_paths` (list), `min_file_count` (number), `commit_message_pattern` (string compilable to RegExp), `bash_syntax_check` (list), `forbidden_paths` (list), `must_contain` (list of `{path, pattern}` dicts or empty list) 4. Step count == manifest count **Validation strategy:** The strongest validation in the entire pipeline. Phase 5.5 (planning-orchestrator) **must** run `plan-validator --strict` before handing the plan to plan-critic. `--validate` mode of `/trekexecute` runs the same check + `progress-validator`. | Code | Meaning | Recovery | |---|---|---| | `PLAN_FORBIDDEN_HEADING` | Narrative drift detected | Rewrite using literal Phase 5 template | | `PLAN_NO_STEPS` | No `### Step N:` headings | Plan is empty; restart | | `PLAN_STEP_NUMBERING` | Steps skip a number | Renumber sequentially | | `PLAN_MANIFEST_COUNT_MISMATCH` | Some step lost its manifest | Add missing manifest | | `MANIFEST_MISSING` | Specific step has no manifest YAML | Add Manifest block | | `MANIFEST_MISSING_KEY` | Manifest is missing a required key | Add the key | | `MANIFEST_PATTERN_INVALID` | `commit_message_pattern` does not compile | Check escaping (`\\(` not `\(` in YAML double-quoted strings) | | `PLAN_VERSION_MISMATCH` | Older `plan_version` | Warning only; planner should bump | **Versioning:** v1.7 has been stable since v1.8.0 of the plugin (when literal-template + Phase 5.5 self-check were added to fix Opus 4.7 schema drift). v1.6 → v1.7 added the Manifest block (mandatory). Before bumping to v1.8, write the new validator branch + fixtures first. **Failure modes:** strict mode is the default for both producer and consumer. There is no soft mode here — a malformed plan is a hard failure for execute. --- ## Handover 5 — `progress.json` (resume contract) **Producer:** `/trekexecute` per-step (after Verify + Manifest audit + Checkpoint). **Consumer:** `/trekexecute --resume` (re-entry) + `pre-compact-flush` hook (drift detection before context compaction). **Path conventions:** - Project-dir: `{project_dir}/progress.json` - Legacy: `{plan-dir}/.trekexecute-progress-{slug}.json` **Schema (top-level):** | Field | Type | Required | Notes | |---|---|---|---| | `schema_version` | string | yes | `"1"` (current) | | `plan` | string | yes | Path to the plan being executed | | `plan_type` | string | optional | `plan \| session-spec` | | `plan_version` | string | yes | Mirrors plan's frontmatter | | `started_at` | ISO string | yes | | | `updated_at` | ISO string | yes | Bumped on every write | | `completed_at` | ISO string | optional | Set when status flips to completed | | `mode` | string | yes | `execute \| dry-run \| validate` | | `total_steps` | number | yes | | | `current_step` | number | yes | 0..total_steps | | `status` | string | yes | `pending \| in_progress \| completed \| failed \| partial` | | `session_start_sha` | string | optional | git sha at execute start | | `session_end_sha` | string | optional | git sha at execute end | | `steps` | object | yes | Map of step number → step record | **Per-step record:** | Field | Type | Notes | |---|---|---| | `status` | `completed \| in_progress \| failed \| pending \| deferred \| skipped` | | | `attempts` | number | 1..N | | `error` | string \| null | | | `completed_at` | ISO string \| null | | | `commit` | string \| null | git sha after Checkpoint | | `manifest_audit` | string | `pass \| fail \| pass-with-note \| n/a` | | `note` | string | optional human-readable annotation | **Validation strategy:** `progress-validator.mjs` runs at: 1. `/trekexecute --validate` (alongside plan-validator) 2. `/trekexecute --resume` entry (must pass `checkResumeReadiness`) 3. `pre-compact-flush` hook (drift check before compaction; never blocks) **Drift detection:** the `pre-compact-flush` hook compares `progress.steps[N].commit` against `git log --oneline {session_start_sha}..HEAD`. If git reality has progressed past the recorded `current_step`, the hook updates progress.json atomically (`tmp + rename`, monotonic only) before allowing compaction. This guards against the documented P0 drift in `docs/trekexecute-v2-observations-from-config-audit-v4.md`. **Versioning:** `schema_version: "1"` is current. Future bump (e.g. `"2"`) should add a backward-compat read path that downgrades unknown fields to warnings. **Failure modes:** - `PROGRESS_PARSE_ERROR` → JSON corruption; resume halts - `PROGRESS_SCHEMA_MISMATCH` → unknown schema version; resume halts - `PROGRESS_MISSING_FIELD` → required top-level field absent; resume halts - `PROGRESS_STEP_RANGE` → `current_step` outside `[0, total_steps]`; resume halts - `PROGRESS_ALREADY_DONE` → `status === completed`; nothing to resume - `PROGRESS_STEP_COUNT_MISMATCH` → warning; not a blocker --- ## Handover 6 — `review.md` → plan **Handover 6 closes the iteration loop.** Where Handovers 1–4 flow forward (brief → research → plan → execute) and Handover 5 makes execute resumable, Handover 6 routes review findings *back* into planning so a remediation plan can be produced with full traceability via `source_findings`. **Producer:** `/trekreview` Phase 7 (write `review.md` after coordinator dedup + verdict). **Consumer:** `/trekplan` Phase 1 when `--brief review.md` is supplied and the consumer detects `type: trekreview` in frontmatter. The plan command branches into a remediation-plan path: BLOCKER + MAJOR findings become plan goals, the produced `plan.md` carries a `source_findings: [<id>, ...]` frontmatter list as the audit trail back to the consumed findings. MINOR + SUGGESTION are skipped for v1.0 plan-input. **Path conventions:** - Project-dir mode (recommended): `{project_dir}/review.md` (one per review iteration; subsequent runs overwrite atomically). - Multiple review iterations are allowed in the same project; each overwrites the canonical path. Audit trail lives in git history. **Frontmatter schema:** | Field | Type | Required | Allowed values | Notes | |---|---|---|---|---| | `type` | string | yes | `trekreview` | Hard-coded discriminator | | `review_version` | string | yes | `"1.0"` (current) | Bump on schema change | | `task` | string | yes | one-line description | Mirrors brief task | | `slug` | string | yes | URL-safe slug | Used in project_dir | | `project_dir` | string | yes | `.claude/projects/{date}-{slug}/` | | | `brief_path` | string | yes | path to consumed `brief.md` | Audit trail back to brief | | `scope_sha_end` | string | yes | git sha of HEAD at review time | Defines "after" boundary | | `reviewed_files_count` | number | yes | ≥ 0 | From triage gate Coverage | | `findings` | list | yes | block-style YAML list of 40-char hex IDs | Flat array; full objects in body | | `created` | date | optional | YYYY-MM-DD | | | `scope_sha_start` | string | optional | git sha at review start | `null` if mtime fallback used | | `verdict` | string | optional | `BLOCK \| WARN \| ALLOW` | Coordinator output | `findings:` is a flat array of finding-IDs (40-char hex from `lib/parsers/finding-id.mjs`). The full finding objects (severity, location, message, evidence, fix) live in the body as `### <id>` subsections under per-severity `## Findings (...)` headings — same pattern as brief-reviewer to avoid frontmatter-parser fragility on lists of dicts. **Body invariants:** required sections (validator runs in strict mode at write-time, soft mode at read-time): - `## Executive Summary` - `## Coverage` - `## Remediation Summary` Optional but standard sections: `## Findings (BLOCKER)`, `## Findings (MAJOR)`, `## Findings (MINOR)`, `## Findings (SUGGESTION)`. The `## Coverage` section enumerates which files were deep-reviewed, summary-only, or skipped (with reason) — this is how the triage gate stays honest and avoids Copilot-style silent skips. **Validation strategy:** | Layer | When | What | |---|---|---| | Frontmatter parse | every read | YAML subset; reject nested dicts | | Required fields | every read | All `REVIEW_REQUIRED_FRONTMATTER` present | | Type discriminator | every read | `type === "trekreview"` | | Findings shape | every read | Array of strings, each matching `^[0-9a-f]{40}$` | | Body sections | strict only | `Executive Summary`, `Coverage`, `Remediation Summary` | | Version format | every read | `review_version` matches `N.M`; warning otherwise | The validator (`lib/validators/review-validator.mjs`) exposes the same CLI as the others: `node lib/validators/review-validator.mjs --json <review.md>`. Strict mode is the default; `--soft` downgrades section-missing errors to warnings. `/trekreview` Phase 8 runs `--strict`. `/trekplan` Phase 1 (when consuming `--brief review.md`) runs `--soft` so a partially-valid review can still seed a plan. **Versioning:** current is `1.0`. There are no live `0.x` reviews. Future schema changes follow the breaking-change protocol above. **Failure modes:** - `REVIEW_NOT_FOUND` → consumer halts with usage message - `REVIEW_READ_ERROR` → I/O failure; halt - `FM_MISSING` → file has no frontmatter; halt - `REVIEW_WRONG_TYPE` → `type !== "trekreview"`; halt - `REVIEW_MISSING_FIELD` → strict halt; soft-mode warning - `REVIEW_BAD_FINDINGS_TYPE` → `findings` is not an array; halt (covers the YAML flow-style trap) - `REVIEW_BAD_FINDING_ID` → an ID is not 40-char hex; halt - `REVIEW_MISSING_SECTION` → strict halt; soft-mode warning - `REVIEW_VERSION_FORMAT` → warning only; review_version not in `N.M` form --- ## Handover 7 — `.session-state.local.json` **Handover 7 enables zero-friction multi-session resumption.** Where Handover 5 (`progress.json`) makes a single execute run resumable after a crash inside that session, Handover 7 makes a *multi-session* plan resumable across fresh Claude Code chats. The state file is the contract; any session-end mechanism may write it; `/trekcontinue` only reads. **Producer:** - `/trekexecute` Phase 8 (canonical convergence — every completed/failed/stopped/partial run that reaches the final report) - `/trekexecute` Phase 2.55 (Check 1 — dirty-tree pre-flight stop) - `/trekexecute` Phase 4 (entry-condition stop) - `/trekendsession` (informal multi-session helper — Step 9 of v3.3.0) - *Future:* `graceful-handoff` v2.2 may dual-write here as part of its session-rescue artifact (additive — extra fields tolerated, see Body invariants). - `hooks/scripts/pre-compact-flush.mjs` *refreshes* `updated_at` on existing state files (status `in_progress` or `partial` only). Never creates the file; never changes status or owned fields. **Consumer:** `/trekcontinue` (read-only). Reads the file, validates it, narrates a 3-line summary, then begins executing the next session by reading `next_session_brief_path`. **Path conventions:** - Per-project: `.claude/projects/{YYYY-MM-DD}-{slug}/.session-state.local.json` — one file per project directory. - Gitignored at the plugin level via `*.local.json` (added in v3.3.0). State files MUST NOT be committed — they may contain absolute project paths and label strings that vary per-machine. - For `--session N` parallel multi-session runs the parent's Phase 8 aggregate write IS the canonical state. Child session writes (inside their worktrees) are ephemeral; the worktree is cleaned up after merge so child state is intentionally discarded. **Frontmatter schema:** N/A — file is JSON, not Markdown. Top-level keys: | Field | Type | Required | Allowed values | Notes | |---|---|---|---|---| | `schema_version` | number | yes | `1` (current) | Bump on breaking changes only | | `project` | string | yes | absolute or repo-relative path | Project directory containing brief/plan | | `next_session_brief_path` | string | yes | path to a brief or session-spec | Validator soft-checks file existence (warning, not error) | | `next_session_label` | string | yes | human-readable label | e.g. "Session 2b" or "Continue" | | `status` | string | yes | `in_progress \| partial \| failed \| stopped \| completed` | Mirrors progress.json status. `completed` triggers SESSION_STATE_NOT_RESUMABLE warning (valid:true) | | `updated_at` | string | yes | ISO-8601 timestamp | Refreshed by pre-compact-flush on resumable statuses | **Body invariants:** N/A (JSON). **Forward-compat — drift-WARN principle:** Unknown top-level keys are **silently tolerated**. The validator does not warn on extras. This is a load-bearing decision: it lets future writers (graceful-handoff v2.2, custom plugin extensions) add metadata fields without breaking `/trekcontinue`. Mirrors Handover 3's discovery-only, drift-WARN posture. **Validation strategy:** | Layer | When | What | |---|---|---| | JSON parse | every read | `JSON.parse` → `SESSION_STATE_PARSE_ERROR` on failure | | Required fields | every read | All six top-level keys present → `SESSION_STATE_MISSING_FIELD` on absence | | Schema version | every read | Numeric `1` → `SESSION_STATE_SCHEMA_MISMATCH` otherwise | | Status enum | every read | Must be one of the five values → `SESSION_STATE_INVALID_STATUS` otherwise | | Resumability | every read | `completed` emits `SESSION_STATE_NOT_RESUMABLE` warning but valid:true | | Path shape | every read | `next_session_brief_path` must be non-empty string → `SESSION_STATE_INVALID_PATH` otherwise | | Timestamp shape | every read | `updated_at` parses via `Date.parse` → `SESSION_STATE_INVALID_TIMESTAMP` otherwise | | Unknown keys | every read | Tolerated silently (drift-WARN forward-compat) | The validator (`lib/validators/session-state-validator.mjs`) exposes the standard CLI: `node lib/validators/session-state-validator.mjs --json <path>`. Returns `{valid, errors[], warnings[], parsed}`. Exit code 0 on valid, 1 on invalid, 2 on usage error. **Versioning:** Current is `1` (number). Schema is **additive only** — new optional fields land without bumping schema_version (forward-compat tolerates them). A breaking change (renaming a field, narrowing the status enum) requires bumping schema_version to `2`, adding migration support in the validator, and following the breaking-change protocol above. **Failure modes:** - `SESSION_STATE_NOT_FOUND` → `/trekcontinue` exits with cold-start message ("no active multi-session project here; start with `/trekbrief` or `/trekplan`") - `SESSION_STATE_PARSE_ERROR` → halt with structured error; user fixes JSON - `SESSION_STATE_MISSING_FIELD` → halt; suggests running validator directly - `SESSION_STATE_SCHEMA_MISMATCH` → halt; future `1` → `2` migration path will warn instead - `SESSION_STATE_INVALID_STATUS` → halt; protects against typo'd writers - `SESSION_STATE_NOT_RESUMABLE` → warning; `/trekcontinue` exits cleanly with "no further sessions to resume; project complete" - Validator failures during writer Phase 8 emit a stderr warning but DO NOT block the session-end report. `progress.json` remains the authoritative record of what was attempted. ### § Lifecycle The state file follows a producer/consumer separation that keeps responsibilities narrow and the contract observable. **Producer/consumer arbeidsdeling:** | Role | Owners | Phase / location | |---|---|---| | Producer (writes the state file) | `/trekexecute` | Phase 8 (canonical), Phase 2.55 (dirty-tree pre-flight stop), Phase 4 (entry-condition stop) | | Producer (informal multi-session helper) | `/trekendsession` | Phase 3 — writes the same schema for ad-hoc handovers that don't run through executor | | Refresher (touch only) | `hooks/scripts/pre-compact-flush.mjs` | Updates `updated_at` only; never creates the file; never changes `status` or any owned field; only acts when `status` is `in_progress` or `partial` | | Consumer | `/trekcontinue` | Phase 2 — reads, validates, narrates a 3-line summary, then begins executing the next session | **Stale-file principle (SC-5):** When `status === 'completed'`, the state file and its sibling `NEXT-SESSION-PROMPT.local.md` represent finished work and SHOULD be removed. Removal is **operator-invoked** via `/trekcontinue --cleanup --confirm <project-dir>`; the plugin does NOT auto-cleanup. Stale state is actively harmful — it can mislead a fresh `/trekcontinue` into resuming a project that's already shipped. The `--cleanup` gate refuses to act unless `validateSessionState({...}).valid === true && parsed.status === 'completed'`. There is no force flag. **Frontmatter contract for `NEXT-SESSION-PROMPT.local.md`:** Producers MUST write a YAML frontmatter block on the prompt file with at minimum: - `produced_by:` — string identifying the producer (e.g. `trekplan-A4-session`, `trekexecute-phase-8`, `trekendsession`) - `produced_at:` — ISO-8601 timestamp of when the file was written The `next-session-prompt-validator` (`lib/validators/next-session-prompt-validator.mjs`) cross-checks `produced_at` against the sibling state file's `updated_at` and emits a `NEXT_SESSION_PROMPT_INCONSISTENT` error when the prompt is older than the state — that means the prompt has not been refreshed for the current session and is stale. Files **without** any frontmatter are tolerated (warning, not error) for backwards compatibility with v3.3.x and earlier hand-rolled prompt files; this is consistent with Handover 3's drift-WARN posture. **Idempotency:** `--cleanup --confirm` is safe to re-run. If only one of the two files (state file, prompt file) was previously deleted, the second run reports the partial state ("state file: not found, prompt file: removed") but does not auto-recover or re-create. There is no rollback. Operators choosing to re-create a project after `--cleanup` should re-run `/trekbrief` from scratch. --- ## Handover 8 — annotation → revision **Handover 8 closes the operator-feedback loop.** Where Handovers 1–4 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 | |---|---|---|---| | 1. brief → research | strict at write, soft at read | this plugin | low | | 2. research → plan | soft, drift-warn | this plugin | low | | 3. architecture → plan | discovery-only, drift-WARN | **external** (opt-in architect plugin, not bundled) | low — by design we tolerate drift | | 4. plan → execute | **strict, both ends** | this plugin | medium — Opus 4.7 narrative drift requires constant vigilance | | 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.