ktg-plugin-marketplace/plugins/voyage/docs/HANDOVER-CONTRACTS.md

30 KiB
Raw Permalink Blame History

Handover Contracts (voyage-suite local pipeline)

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.

Versioning policy

Each artifact carries an explicit version field. Schema bumps are coordinated:

Artifact Field Current
brief.md brief_version (frontmatter) 2.1
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)

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

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.


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 <file>.

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
phase_signals list optional (v5.1+) list of {phase, effort?, model?} entries Per-phase effort + model commitment from Phase 3.5. Mutually exclusive with phase_signals_partial.
phase_signals_partial bool optional (v5.1+) true Force-stop record from Phase 3.5. Mutually exclusive with phase_signals.

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"
v5.1 sequencing gate every read brief_version ≥ 2.1 requires phase_signals (list) OR phase_signals_partial: true — error BRIEF_V51_MISSING_SIGNALS on miss. Validator-only enforcement; commands surface, don't re-enforce.
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.1 (v5.1 — adds optional phase_signals + phase_signals_partial). The forward-compat policy in brief-validator.mjs header still applies: unknown frontmatter keys flow through silently, so a 2.1 brief still validates against pre-v5.1 consumers. The version bump exists because v2.1 activates the version-conditional sequencing gate (above) — the only check in the validator that triggers on brief_version rather than field-presence. There are no live 1.x briefs; remove legacy paths in next major. v5.4 may promote phase_signals from optional to required (breaking change → 3.0).

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
  • BRIEF_V51_MISSING_SIGNALS → strict halt (v5.1+ sequencing gate); soft-mode warning. Commands surface a friendly hint pointing back to /trekbrief (Phase 3.5).
  • BRIEF_INVALID_PHASE_SIGNALS → strict halt; phase_signals must be a list of {phase, effort?, model?} entries.
  • BRIEF_INVALID_PHASE_SIGNAL_PHASE → strict halt; phase ∉ [research, plan, execute, review].
  • BRIEF_INVALID_EFFORT → strict halt; effort ∉ [low, standard, high].
  • BRIEF_INVALID_MODEL → strict halt; model ∉ BASE_ALLOWED_MODELS (currently [sonnet, opus]).
  • BRIEF_SIGNALS_MUTUALLY_EXCLUSIVE → strict halt; cannot set both phase_signals and phase_signals_partial: true.

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: <title> 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_RANGEcurrent_step outside [0, total_steps]; resume halts
  • PROGRESS_ALREADY_DONEstatus === 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 14 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_TYPEtype !== "trekreview"; halt
  • REVIEW_MISSING_FIELD → strict halt; soft-mode warning
  • REVIEW_BAD_FINDINGS_TYPEfindings 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.parseSESSION_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 1SESSION_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.parseSESSION_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 12 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.


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

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.