ktg-plugin-marketplace/plugins/ultraplan-local/docs/HANDOVER-CONTRACTS.md
Kjell Tore Guttormsen cc38155fa6 feat(ultraplan-local): Spor 2 — HANDOVER-CONTRACTS.md + PreCompact-hook (P0 progress.json drift fix)
Reconciles divergence after parallel-session race: includes both Spor 1 wiring (validators inn i 4 commands + 1 agent) og Spor 2 (HANDOVER-CONTRACTS.md + PreCompact-hook).

Spor 1 wiring (re-applied etter rebase):
- /ultrabrief-local Phase 4g — brief-validator post-write
- /ultraplan-local Phase 1 — brief-validator --soft + research-validator --dir + architecture-discovery
- planning-orchestrator Phase 5.5 — plan-validator --strict erstatter 3 grep -cE-kall
- /ultraexecute-local Phase 2.3 (--validate) — plan-validator + progress-validator
- YAML-parser-utvidelse: list-of-dicts (must_contain), støtter v1.7 template-format

Spor 2 NEW:
- docs/HANDOVER-CONTRACTS.md (~310 linjer) — single source of truth for de 5 pipeline-handover-formatene m/ faste sub-headinger (Producer / Consumer / Path / Frontmatter schema / Body invariants / Validation strategy / Versioning / Failure modes)
- hooks/scripts/pre-compact-flush.mjs (NY) — fikser dokumentert P0 i docs/ultraexecute-v2-observations-from-config-audit-v4.md:
  * Fyrer på PreCompact-event (CC v2.1.105+)
  * Lokaliserer progress.json under .claude/projects/*/
  * Sammenligner stored current_step mot git log {session_start_sha}..HEAD
  * Atomisk write (tmp + rename), monoton — current_step kan aldri reduseres
  * Aldri blokkerer compaction (exit 0)
- hooks/hooks.json registrerer PreCompact-hooken

Resultat: /ultraexecute-local --resume virker nå etter context compaction selv ved skill-driven execution.

Docs:
- README.md (plugin): "Quality infrastructure", "Handover contracts", "PreCompact resume integrity"
- CLAUDE.md (plugin): peker til HANDOVER-CONTRACTS.md + dokumenterer pre-compact-flush
- README.md (marketplace root): bullet-liste over Spor 2-deliverables (resolved merge-konflikt fra parallell-sesjon)

Tester: 109 grønn (ingen regresjon).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 06:07:01 +02:00

15 KiB

Handover Contracts (ultra-suite local pipeline)

This document is the single source of truth for the file formats that pass between the four commands of the ultraplan-local 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: ultraresearch-brief) unversioned
plan.md plan_version (frontmatter) 1.7
progress.json schema_version (top-level) "1"

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

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: /ultrabrief-local Phase 4g (after brief-reviewer stop-gate passes or iteration cap is hit).

Consumer: /ultraresearch-local 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 ultrabrief 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 === "ultrabrief"
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: /ultraresearch-local Phase 7 (synthesis + brief writer).

Consumer: /ultraplan-local 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/ultraresearch-{date}-{slug}.md.

Frontmatter schema:

Field Type Required Allowed values
type string yes ultraresearch-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 /ultra-cc-architect-local from the separate ultra-cc-architect plugin (v0.1.0+). When that plugin is not installed, this handover is absent — and that is fine.

Producer: /ultra-cc-architect-local (external plugin).

Consumer: /ultraplan-local 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 — ultraplan-local 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 sister plugin's territory.

Versioning: the producer (ultra-cc-architect) 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: /ultraexecute-local Phase 2 (plan parsing) + --validate mode.

Path conventions:

  • Project-dir: {project_dir}/plan.md
  • Legacy: .claude/plans/ultraplan-{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 /ultraexecute-local 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: /ultraexecute-local per-step (after Verify + Manifest audit + Checkpoint).

Consumer: /ultraexecute-local --resume (re-entry) + pre-compact-flush hook (drift detection before context compaction).

Path conventions:

  • Project-dir: {project_dir}/progress.json
  • Legacy: {plan-dir}/.ultraexecute-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. /ultraexecute-local --validate (alongside plan-validator)
  2. /ultraexecute-local --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/ultraexecute-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

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 (ultra-cc-architect) 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+)

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.