---
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 <dir> [--from-file <path>] [--target {brief|plan|review|auto}] [--reason <text>] [--profile <name>] [--gates <mode>]"
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 `<!-- voyage:anchor ... -->` 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 "<text>"`.

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 <dir>` | valued | Required. Path to the trekplan project folder containing the target artifact. |
| `--from-file <path>` | 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 <text>` | valued | Optional. Required when revision is non-additive (see Phase 4). |
| `--profile <name>` | valued | Optional. Model profile (`economy`, `balanced`, `premium`, custom). Default: `balanced`. |
| `--gates <mode>` | valued | Optional. Autonomy mode (`open`, `closed`, `adaptive`). Default: `adaptive`. |

Resolution:

1. If `--project` is missing, print usage and stop:
   ```
   Error: --project <dir> is required.
   Usage: /trekrevise --project <dir> [--from-file <path>] [--target {brief|plan|review|auto}] [--reason <text>]
   ```
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 <path>` 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 <path> 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 `## <required-section>` 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 "<short explanation>" 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 = <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 <name>` where `<name>` 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.** `<!-- voyage:anchor id="ANN-NNNN" target="..."
  line="N" [snippet="..."] [intent="fix|change|question|block"] -->`
  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.
