--- name: trekrevise description: | Apply operator-annotated brief/plan/review back into the source artifact with audit trail. Reads anchored comments from pasted (or `--from-file`) content, validates anchor placement, computes a canonical annotation_digest, and writes the revision in-place with rollback-on-validator-fail. Implements Handover 8 (annotation → revision). argument-hint: "--project [--from-file ] [--target {brief|plan|review|auto}] [--reason ] [--profile ] [--gates ]" model: opus allowed-tools: Read, Write, Edit, Bash, Grep, Glob --- # Ultrarevise Local v1.0 Apply operator annotations from the voyage playground back into the source artifact (`brief.md`, `plan.md`, or `review.md`) with audit-trail in frontmatter. Implements **Handover 8 (annotation → revision)**. Pipeline position: ``` /trekbrief → brief.md /trekresearch → research/*.md /trekplan → plan.md /trekexecute → progress.json (+ commits) /trekreview → review.md /trekrevise → in-place revision of brief|plan|review (this command) ``` The annotation source is the **operator-edited markdown** that the voyage playground exports. The playground reads `{target}.md`, lets the operator add anchored comments via three creation gestures, and exports a markdown blob carrying `` block-level HTML comments inline plus the operator's revised body content. `/trekrevise` consumes that blob, validates the anchor placement, computes a canonical `annotation_digest`, and replaces the source artifact in-place. A pre-revision backup (`{target}.md.local.bak`) is created and used to roll back if the post-write validator rejects the new content. The revision is **additive by default**: applying the same annotation set twice is idempotent (same digest, same body). Non-additive revisions (structural step reorder, section removal, frontmatter `*_version` changes) require an explicit `--reason ""`. See `docs/HANDOVER-CONTRACTS.md § Handover 8` for the schema contract. ## Phase 1 — Parse mode and validate input Parse `$ARGUMENTS` via the shared arg-parser: ```bash node ${CLAUDE_PLUGIN_ROOT}/lib/parsers/arg-parser.mjs --command trekrevise "$@" ``` The parser recognizes these flags (see `lib/parsers/arg-parser.mjs` FLAG_SCHEMA `trekrevise` entry): | Flag | Type | Purpose | |------|------|---------| | `--project ` | valued | Required. Path to the trekplan project folder containing the target artifact. | | `--from-file ` | valued | Optional. Read annotated content from a file instead of stdin/pasted prompt. | | `--target {brief\|plan\|review\|auto}` | valued | Optional. Which artifact to revise. `auto` (default) infers from frontmatter `type:` field of the annotated input. | | `--reason ` | valued | Optional. Required when revision is non-additive (see Phase 4). | | `--profile ` | valued | Optional. Model profile (`economy`, `balanced`, `premium`, custom). Default: `balanced`. | | `--gates ` | valued | Optional. Autonomy mode (`open`, `closed`, `adaptive`). Default: `adaptive`. | Resolution: 1. If `--project` is missing, print usage and stop: ``` Error: --project is required. Usage: /trekrevise --project [--from-file ] [--target {brief|plan|review|auto}] [--reason ] ``` 2. Trim trailing slash from `{dir}`. Set: - `project_dir = {dir}` - `brief_path = {dir}/brief.md` - `plan_path = {dir}/plan.md` - `review_path = {dir}/review.md` 3. If `{dir}` does not exist: ``` Error: project directory missing: {dir} Run /trekbrief first. ``` 4. Determine the **annotated input source**: - If `--from-file ` is set, read that file. If the file is missing or unreadable, stop with `Error: --from-file path not readable: {path}`. - Otherwise, the annotated content MUST appear in the operator's prompt (pasted directly into the chat). Capture it from the prompt text. - If neither source yields content, stop with: ``` Error: no annotated content provided. Pass --from-file or paste the content directly into your prompt. ``` 5. **Reject multi-artifact bundle.** Scan the annotated input for two or more lines matching `^---$` at line-start that introduce frontmatter blocks (i.e. more than one `---\n...\n---\n` pair). If detected: ``` Error: MULTI_ARTIFACT_NOT_SUPPORTED — annotated input contains more than one artifact (frontmatter blocks detected: {N}). /trekrevise revises one artifact per invocation. Split the bundle and re-run for each target. ``` Stop. No partial revisions. 6. Resolve `target`: - If `--target` is `brief`, `plan`, or `review`: use it explicitly. - If `--target` is `auto` (or missing): parse the annotated input's frontmatter via `parseDocument`. Read the `type:` field — expect `brief`, `plan`, or `review`. If absent, fall back to the artifact whose schema validates against the input (try in order: brief, plan, review). If none match, stop with: ``` Error: cannot infer --target from annotated input frontmatter. Pass --target {brief|plan|review} explicitly. ``` 7. Resolve the target file path: `target_path = {project_dir}/{target}.md`. If it does not exist, stop: ``` Error: target artifact missing: {target_path} Run /trek{brief|plan|review} first to produce it. ``` Set: - `mode = revise` (the only mode currently) - `profile`, `gates`, `reason` per flags Report: ``` Mode: revise Project: {project_dir} Target: {target} → {target_path} Source: {--from-file path | pasted} Profile: {profile} ``` ## Phase 2 — Read source artifact + check rollback hygiene Stale-backup precheck: ```bash test -f "{target_path}.local.bak" ``` If a `.local.bak` exists from a previously aborted run, abort: ``` Error: stale rollback backup found at {target_path}.local.bak Inspect it (it represents the pre-revision state of a prior aborted run), then either restore it manually with: cp "{target_path}.local.bak" "{target_path}" rm "{target_path}.local.bak" or delete it if you want to keep the current target_path: rm "{target_path}.local.bak" After resolving, re-run /trekrevise. ``` Stop. The revision is not applied. Read the source artifact: ```js import { parseDocument } from '${CLAUDE_PLUGIN_ROOT}/lib/util/frontmatter.mjs'; const source = parseDocument(readFileSync(target_path, 'utf-8')); ``` If `source.valid === false`: ``` Error: source artifact has malformed frontmatter: {target_path} Fix it manually before annotating. ``` Capture: - `existing_frontmatter = source.parsed.frontmatter` - `existing_body = source.parsed.body` - `existing_revision = existing_frontmatter.revision || 0` ## Phase 3 — Parse anchors + validate placement Parse anchors from the **annotated input** (not the source artifact): ```js import { parseAnchors, validateAnchorPlacement, stripAnchors } from '${CLAUDE_PLUGIN_ROOT}/lib/parsers/anchor-parser.mjs'; const annotated = parseDocument(annotated_input_text); const annotated_body = annotated.parsed.body; const anchors_result = parseAnchors(annotated_body); ``` If `anchors_result.valid === false`: ``` Error: anchor parse failed in annotated input. {for each error: file:line:rule_key — message} ``` Stop. **No partial revisions** — abort BEFORE any write. Run placement validation: ```js const placement_result = validateAnchorPlacement(annotated_body, anchors_result.parsed); ``` If `placement_result.valid === false`: ``` Error: anchor placement violations detected. {for each error: line N: rule_key — message} ``` Stop. Capture `anchors = anchors_result.parsed` (an array of anchor objects with `{id, target, line, snippet?, intent?}`). If `anchors.length === 0`, the operator submitted an annotation-free revision. Continue — empty-anchor round-trip is a valid case (used by SC2). The body diff will still be applied; the audit fields will record zero anchors plus an all-zeros digest baseline. ## Phase 4 — Compute revision diff + digest Compute the canonical digest from the annotation set: ```js import { computeAnnotationDigest } from '${CLAUDE_PLUGIN_ROOT}/lib/parsers/annotation-digest.mjs'; const annotation_digest = computeAnnotationDigest(anchors); ``` Determine the new revision counter: ```js const new_revision = (existing_revision | 0) + 1; ``` **Detect non-additive revision.** A revision is non-additive when ANY of: - The body removes a `### Step N:` heading that previously existed. - The body removes any `## ` heading per the target's validator (e.g. plan: "Implementation Plan", "Verification"; brief: "Goal", "Success Criteria"; review: "Executive Summary", "Coverage"). - The frontmatter changes any `*_version` field (e.g. `plan_version`, `brief_version`, `review_version`). - A `### Step N:` heading is reordered (different N sequence than before). Compute these by comparing `existing_body` heading sequence with the `annotated_body` (with anchors stripped via `stripAnchors`). The heading-sequence comparison is sufficient — content edits inside an existing step are always additive. If non-additive AND `--reason` was NOT supplied: ``` Error: non-additive revision detected: - {bullet list of detected non-additive changes} Re-run with --reason "" to acknowledge the structural change and record it in revision_reason for the audit trail. ``` Stop. The revision is not applied. If non-additive AND `--reason` is supplied: capture `revision_reason = `. If additive, leave `revision_reason` unset (omitted from frontmatter). ## Phase 5 — Apply revisions in-place Strip anchors from the annotated body to produce the new artifact body: ```js const new_body = stripAnchors(annotated_body); ``` Build the new frontmatter object by merging the existing fields with the revision audit fields: ```js const new_frontmatter = { ...existing_frontmatter, revision: new_revision, source_annotations: anchors.map(a => ({ id: a.id, target_artifact: target, target_anchor: a.target, line: a.line, intent: a.intent || 'change', snippet: a.snippet || '', timestamp: new Date().toISOString(), })), annotation_digest, ...(revision_reason ? { revision_reason } : {}), }; ``` Apply the revision via `revisionGuard` from `lib/util/revision-guard.mjs`, which performs the full **backup → mutate → atomic write → validate → rollback-on-fail** orchestration: ```js import { revisionGuard } from '${CLAUDE_PLUGIN_ROOT}/lib/util/revision-guard.mjs'; import { validateBrief, validatePlan, validateReview } from '${CLAUDE_PLUGIN_ROOT}/lib/validators/...'; const validator = ({ brief: validateBrief, plan: validatePlan, review: validateReview, })[target]; const result = revisionGuard( target_path, ({ frontmatter, body }) => ({ frontmatter: new_frontmatter, body: new_body, }), validator, ); ``` `revisionGuard` returns `{outcome, validator_result, sha256_before, sha256_after, error?}`. Outcomes: - `applied` — write succeeded, validator passed, `.local.bak` deleted. - `rolled-back` — write succeeded, validator FAILED, file restored from `.local.bak`, byte-identical to pre-revision state. - `mutator-failed` — pre-existing backup, mutator threw, or read failed. ## Phase 6 — Validate revision Branch on the `revisionGuard` outcome: ### outcome === 'applied' The revision is in place. Surface validator warnings (if any) to the operator, but do not roll back. Continue to Phase 7. ### outcome === 'rolled-back' The post-write validator rejected the new content. The file is byte-identical to its pre-revision state. ``` Validator REJECTED the revision. Rolled back to pre-revision state. sha256_before === sha256_after (byte-identical): {true|false} Validator errors: - {code} {file}:{line} — {message} ... To inspect the proposed (rejected) revision, retry with --from-file pointing at a fixed annotated input, or fix the annotation set in the playground and re-export. ``` Stop. The audit-trail fields are NOT recorded; revision counter is unchanged. ### outcome === 'mutator-failed' ``` Error: revision could not be applied: {revisionGuard.error} If the error references a pre-existing backup, follow Phase 2's remediation steps. ``` Stop. ## Phase 7 — Optional review-gate (plan/review targets only) Only runs when `target === 'plan'` AND `{review_path}` exists, OR `target === 'review'` AND `{plan_path}` exists. Otherwise skip to Phase 8. Run the existing review (validate-only mode) against the revised plan, or re-validate the revised review: ```bash node ${CLAUDE_PLUGIN_ROOT}/lib/validators/review-validator.mjs --json "{review_path}" ``` If the validator returns BLOCK or FAIL: ``` Review-gate WARN: revised {target}.md is in place, but the review at {review_path} contains BLOCKER findings against the now-revised content. Verdict: BLOCK Top findings: - {list} The revised file is NOT auto-rolled-back. You may: - Re-annotate to address the BLOCKER findings, then /trekrevise again. - Accept the gap and run /trekreview to refresh review.md. - Manually restore via: cp "{target_path}.local.bak" "{target_path}" (Note: the .local.bak was deleted on Phase 6 success. To re-rollback after Phase 7, use git: `git checkout HEAD -- "{target_path}"` if you have not yet committed the revision.) ``` The revised file remains as written. Continue to Phase 8. If the gate passes (verdict ALLOW or WARN-only): continue silently. ## Phase 8 — Stats + report Append a stats line to `${CLAUDE_PLUGIN_DATA}/trekrevise-stats.jsonl` (create the file if it does not exist): ```json {"ts":"{ISO-8601}","target":"{brief|plan|review}","project_dir":"{dir}","revision":{N},"anchor_count":{N},"digest":"{16-char-hex}","validator_verdict":"{pass|fail-rolled-back}","outcome":"{applied|rolled-back|mutator-failed}","profile_used":"{profile}"} ``` Use inline `appendFileSync` (mirrors the jsonl-append pattern in `hooks/scripts/post-bash-stats.mjs:50`). If `${CLAUDE_PLUGIN_DATA}` is unset or not writable, skip stats silently. Never let stats failures block the main workflow. Emit the human-readable summary: ``` ## Ultrarevise Complete **Project:** {project_dir} **Target:** {target} ({target_path}) **Revision:** {existing_revision} → {new_revision} **Anchors applied:** {N} **annotation_digest:** {16-char-hex} **Outcome:** {applied | rolled-back | mutator-failed} {if revision_reason}: **revision_reason:** {revision_reason} ### Audit fields written - revision: {N} - source_annotations: {N entries} - annotation_digest: {hex} {if revision_reason}: - revision_reason: "{reason}" {if Phase 7 surfaced findings}: ### Review-gate WARN - Verdict: {BLOCK|WARN} - Top findings: - {list} You can: - Inspect the revised file at {target_path} - Run /trekreview --project {project_dir} to refresh review.md - Run /trekplan --project {project_dir} to extend the plan - Re-annotate via the playground and run /trekrevise again - Roll back manually via: git checkout HEAD -- "{target_path}" ``` ## Profile (v4.1) Accepts `--profile ` where `` is `economy`, `balanced`, `premium`, or a custom profile under `voyage-profiles/`. Default: `balanced`. Resolution order (per `lib/profiles/resolver.mjs`): 1. `--profile` flag (source: `flag`) 2. `VOYAGE_PROFILE` env-var (source: `env`) 3. `balanced` default (source: `default`) The selected profile drives `phase_models.revise`. `/trekrevise` is mostly deterministic (parsing + validating + atomic writes), so all built-in profiles use sonnet for any sub-agent invocation. The operator-facing synthesis in Phase 6/7 stays in the main thread regardless of profile. Examples: ``` /trekrevise --profile balanced --project .claude/projects/2026-05-09-foo VOYAGE_PROFILE=premium /trekrevise --project ... --target plan ``` Stats records emit `profile_used` (and `profile_source` when available). ## Hard rules - **No partial revisions.** If anchor parse or placement validation fails in Phase 3, abort BEFORE any write. The source artifact is never left in a half-revised state. - **One artifact per invocation.** Multi-artifact bundles return `MULTI_ARTIFACT_NOT_SUPPORTED`. Split into per-artifact runs. - **revision_reason is required for non-additive changes.** Step reorder, section removal, and `*_version` bumps require an explicit reason recorded in frontmatter for audit. - **Backup hygiene.** Pre-existing `.local.bak` blocks the run. Operator must inspect or delete it; the executor never auto-overwrites it. - **Validator is the gate.** A revision that writes successfully but fails post-write validation is rolled back to byte-identical pre-revision state. Audit fields are NOT recorded on rollback. - **Idempotent digest.** Re-applying the same annotation set yields the same `annotation_digest`. The digest is canonical SHA-256 over a field-sorted, id-sorted, pipe-separated, line-joined serialization (16-char hex prefix). - **Forward-compat fields.** `revision`, `source_annotations`, `annotation_digest`, and `revision_reason` are additive frontmatter fields. Validators that predate v4.2 ignore them. Artifacts without `revision:` are treated as `revision: 0`. - **Anchor format.** `` block-level only; placement disipline enforced (not in list-items, not inside fenced code blocks, not at line-start collisions with frontmatter delimiter, manifest:, plan_version:, ### Step N:, ## required sections, or 40-char hex finding-IDs). - **No production code.** This command never runs production code, never writes to anything outside `{project_dir}` and `${CLAUDE_PLUGIN_DATA}`. - **Operator has final say.** The review-gate in Phase 7 is advisory — it never auto-rolls-back a successful revision. The operator decides whether to re-annotate, refresh the review, or accept the gap.