From 8cbb33e1fd965259df6f280c6eb9d929b6d7afe9 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Wed, 13 May 2026 20:31:58 +0200 Subject: [PATCH] =?UTF-8?q?docs(voyage):=20pin=20operator-UX=20contract=20?= =?UTF-8?q?=E2=80=94=20always=20emit=20file://=20link=20+=20open=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operator runs Ghostty (also iTerm2, modern Terminal.app) — all support cmd+click on file:// URLs. Producing commands (/trekbrief, /trekplan, /trekreview) already emit both forms but the contract was implicit. This commit makes it explicit: 1. CLAUDE.md gains an "Operator-UX guarantee" paragraph stating both forms must always appear in the final report: (a) plain file:// URL with absolute path (for cmd+click), (b) copy-pasteable `open file://` command (for terminals without cmd+click). 2. tests/lib/doc-consistency.test.mjs gains a pin asserting both patterns appear in all three producing commands' final report blocks. Drift catches at test time. Non-functional change to the commands themselves — they already emit both forms (verified at trekbrief.md L510/L519, trekplan.md L798/L802, trekreview.md L299/L317). Operator request 2026-05-13: "Noter ned i Voyage at jeg ALLTID får en slik direkte file:// lenke." --- plugins/voyage/CLAUDE.md | 2 ++ .../voyage/tests/lib/doc-consistency.test.mjs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/plugins/voyage/CLAUDE.md b/plugins/voyage/CLAUDE.md index bf1aab4..75ab8df 100644 --- a/plugins/voyage/CLAUDE.md +++ b/plugins/voyage/CLAUDE.md @@ -234,6 +234,8 @@ Local Docker Compose stack: `examples/observability/`. Operator docs: `docs/obse **Continue:** `/trekcontinue` reads `{dir}/.session-state.local.json` (Handover 7), validates schema-v1 via `session-state-validator`, narrates a 3-line summary (project / next-session-label / brief-path), and immediately begins executing the next session. Auto-discovers active project state files under `.claude/projects/*/.session-state.local.json` if no explicit `` argument. Operator-invoked only — never auto-loaded via SessionStart. The `/trekendsession` helper is the informal-flow producer: writes the same state file for ad-hoc multi-session handovers that don't run through `/trekexecute`. +**Operator-UX guarantee (since v5.0.2):** `/trekbrief`, `/trekplan`, and `/trekreview` MUST always emit (a) a plain `file://` URL AND (b) a copy-pasteable `open file://` command in the final report block. The file:// URL must use an ABSOLUTE path (not relative or `~/`-prefixed) so terminals with cmd+click support (Ghostty, iTerm2, modern Terminal.app) can resolve it without shell interpretation. This is a non-negotiable operator-UX contract — the doc-consistency test pins both forms in all three commands' final report blocks. + **Operator-annotation HTML (v5.0.3):** the last step of `/trekbrief`, `/trekplan`, and `/trekreview` runs `scripts/annotate.mjs` against the just-written `.md` and prints the resulting `file://` link. The HTML is self-contained (zero npm deps, zero external network, design-system-styled, light + dark + print) and modelled on `~/repos/claude-code-100x/claude-code-100x/build-site.js` (lines 1431–2255). The operator opens the file, the document renders as a proper article (headings / paragraphs / lists / tables / code / quotes — every element gets a stable `data-anchor-id`). In annotation mode (default ON, pencil-toggle in topbar), the operator can **select any text or click any element** → a form popover opens at the cursor with: section context auto-detected from nearest h1/h2, the anchored snippet (selection if any, else element text), **three intent buttons (Fiks / Endre / Spørsmål)**, comment textarea, Save/Cancel. The sidebar (Show annotations button) lists every annotation grouped by section with intent badge + snippet + comment + delete; clicking a card scrolls to and flashes the source element. **Copy Prompt** assembles a structured markdown (`### N. [Intent] Section: <…>` + `Quote: «…»` + `Comment: …`) and copies to clipboard. Persistence: `localStorage` keyed on absolute artifact path (`voyage-annotate:v2:`). v5.0.0 removed the v4.2/v4.3 bespoke playground SPA + `/trekrevise` + Handover 8; v5.0.1 pointed at `/playground document-critique` (Claude-leads, wrong direction); v5.0.2 was operator-led but too thin (line-click + freeform note, no intents); v5.0.3 matches the claude-code-100x reference the operator first pointed at, with pencil-toggle / selection capture / intent categories / popover form / structured export. See [CHANGELOG.md](CHANGELOG.md) § v5.0.3. **Security:** 4-layer defense-in-depth: plugin hooks (pre-bash-executor, pre-write-executor), prompt-level denylist (works in headless sessions), pre-execution plan scan (Phase 2.4), scoped `--allowedTools` replacing `--dangerously-skip-permissions`. Hard Rules 14-16 enforce verify command security, repo-boundary writes, and sensitive path protection. diff --git a/plugins/voyage/tests/lib/doc-consistency.test.mjs b/plugins/voyage/tests/lib/doc-consistency.test.mjs index 5c7bef8..d01a728 100644 --- a/plugins/voyage/tests/lib/doc-consistency.test.mjs +++ b/plugins/voyage/tests/lib/doc-consistency.test.mjs @@ -485,6 +485,24 @@ test('producing commands tell the operator the flow is THEIR own annotations', ( } }); +test('producing commands emit file:// link in final report (operator-UX contract, 2026-05-13)', () => { + // Operator runs Ghostty / iTerm2 / modern Terminal.app — all support cmd+click + // on file:// URLs. Producing commands MUST emit both forms: (a) plain file:// + // line in the report block, (b) `open file://...` copy-pasteable command. + // Both must reference $ANNOT_HTML (absolute path from scripts/annotate.mjs). + for (const f of ['trekbrief.md', 'trekplan.md', 'trekreview.md']) { + const text = read(`commands/${f}`); + assert.ok( + /file:\/\/\{\$ANNOT_HTML\}/.test(text), + `commands/${f} must include "file://{$ANNOT_HTML}" plain URL in the final report block`, + ); + assert.ok( + /open file:\/\/\{\$ANNOT_HTML\}/.test(text), + `commands/${f} must include "open file://{$ANNOT_HTML}" copy-pasteable command in the final report block`, + ); + } +}); + test('package.json still has no "npm run render" script (removed in v5.0.1)', () => { const pkg = JSON.parse(read('package.json')); assert.equal(