Compare commits

...

3 commits

Author SHA1 Message Date
2728a43656 docs(readme): replace dead disclosure link with inline text
The `[Full disclosure →](../../README.md#ai-generated-code-disclosure)`
link was inherited from the monorepo layout. In a standalone repo `../../`
resolves outside the repository (404), and the anchor never existed in any
README. Replaced with the inline sentence the polyrepo migration intended.

Reported by catalog (coord 20260803T191159Z-4541213484), confirmed by
repo-standard v0.2.0 gate (LINK-OUTSIDE-REPO, README.md:11).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRYUCVByhmzWBMCCFSYK2j
2026-08-04 12:36:39 +02:00
79044624db docs(readme): align first screen with the org repo standard
repo-standard gate (plugin class) flagged 6 ERROR. Fixed all six:

- README-DESC: H1 is now `# voyage` (was `# trekplan — …`, stale from the
  rename) and the opening line is byte-identical to the forge description,
  so description == catalog == README on all three surfaces.
- INSTALL-NO-CLI + HEADING-MISSING: new first-screen `## Install` with the
  CLI install command (`claude plugin install voyage@ktg-plugin-marketplace`)
  beside `marketplace add`; the `enabledPlugins` JSON is kept as the second
  form. Retires the duplicate `## Installation` section further down (its
  architect paragraph already exists verbatim under Quick start) and drops
  the now-redundant install lines from the Quick start block.
- HEADING-MISSING: `## Non-goals` (promotes the existing "What it doesn't
  solve" list) and `## Changelog` (release notes moved off the first screen,
  per the standard, now a link + one line per recent version — keeps the
  doc-consistency pin on framing / brief_version 2.2).

Remaining 3 gate ERROR are a classifier defect in the gate, not this repo:
directory link targets (`agents/`, `examples/observability/`) are checked
against `git ls-files`, which never contains directories. Links are correct
and left as-is by operator decision; reported to repo-standard via coord.

Suite: 832 tests, 829 pass, 1 fail, 2 skipped — the failure is the
load-dependent otel tail-latency NFR (passes 3/3 in isolation), unrelated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRYUCVByhmzWBMCCFSYK2j
2026-08-03 21:57:51 +02:00
8d39e1d4a5 fix(trekendsession): release v5.9.1 - eager-exec blocks crashed command load
Phase 3 (atomic-write) and Phase 4 (validator) used !`...` eager-exec
with unresolved runtime placeholders; the harness executes those at
command LOAD time, so zsh parsed <project-dir> as input redirection and
/trekendsession aborted before the model saw a single instruction.
Both blocks are now plain runtime Bash fences with {curly} placeholders
(shell-inert, trekplan.md convention) and absolute ${CLAUDE_PLUGIN_ROOT}
paths (cwd-relative plugin paths were a latent ERR_MODULE_NOT_FOUND in
any user repo). Phase 1 discovery block keeps its legitimate eager-exec.

Regression guard: new tests/commands/trekendsession.test.mjs flags any
!`-block in commands/*.md containing <angle>/{curly} placeholders, and
pins Phase 3/4 as runtime Bash. Suite 828 -> 832 (830/0/2). E2E: fixed
blocks run with real values write both state files, validator valid:true;
trekcontinue.md:147 runtime-verified self-contained.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013NXzF3F2zAM8S7FCBXCqAb
2026-07-03 01:11:02 +02:00
7 changed files with 220 additions and 50 deletions

View file

@ -1,12 +1,23 @@
{
"name": "voyage",
"description": "Voyage — brief, research, plan, execute, review, continue. Contract-driven Claude Code pipeline. /trekbrief, /trekplan, and /trekreview each end by building a self-contained operator-annotation HTML (scripts/annotate.mjs, modelled on claude-code-100x): select text or click any element, pick intent (Fiks/Endre/Spørsmål), write comment, copy structured prompt, paste back, Claude revises the .md.",
"version": "5.9.0",
"version": "5.9.1",
"author": {
"name": "Kjell Tore Guttormsen"
},
"homepage": "https://git.fromaitochitta.com/open/ktg-plugin-marketplace/src/branch/main/plugins/voyage",
"repository": "https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git",
"license": "MIT",
"keywords": ["voyage", "trek", "planning", "implementation", "research", "context-engineering", "agents", "adversarial-review", "headless", "execution"]
"keywords": [
"voyage",
"trek",
"planning",
"implementation",
"research",
"context-engineering",
"agents",
"adversarial-review",
"headless",
"execution"
]
}

View file

@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## v5.9.1 — 2026-07-03 — Fix /trekendsession load-time crash (eager-exec placeholders)
Patch, no functional additions.
### Fixed
- `/trekendsession` was unusable in every invocation: two of its three `` !`...` `` eager-exec blocks (Phase 3 atomic-write, Phase 4 validator call) contained unresolved runtime placeholders (`<project-dir>` etc.). The harness executes eager-exec blocks at command LOAD time, so zsh parsed `<project-dir>` as input redirection and the command aborted before the model saw a single instruction. Both blocks are now plain runtime Bash fences with the `{curly}` placeholder convention (shell-inert), matching `trekplan.md`/`trekresearch.md`. The Phase 1 discovery block (self-contained) keeps its legitimate eager-exec prefix; `trekcontinue.md`'s discovery block was runtime-verified unaffected.
- Latent secondary bug in the same blocks: cwd-relative plugin paths (`lib/validators/...`, `./lib/util/atomic-write.mjs`) would have failed with `ERR_MODULE_NOT_FOUND` even after substitution, since the Bash cwd is the user's repo. Both now use absolute `${CLAUDE_PLUGIN_ROOT}` paths per the existing command convention (Node ESM accepts absolute-path import specifiers — verified on Node 18+).
### Added
- Regression guard `tests/commands/trekendsession.test.mjs`: scans every `` !` ``-block in `commands/*.md` for unresolved `<angle>`/`{curly}` placeholders (this bug class is silent until first invocation), plus structure tests pinning Phase 3/4 as runtime Bash with `${CLAUDE_PLUGIN_ROOT}` paths and exactly one surviving eager block. Suite baseline 828 → 832 (830 pass / 0 fail / 2 skip).
## v5.9.0 — 2026-07-02 — Fable model tier + deep-research engine
Additive, plus one behavior alignment: profile `phase_models` now reach sub-agent spawn sites (previously documented but never wired), and the seven command orchestrators no longer pin `model: opus` — frontmatter omits `model:`, so the orchestrator follows the session model.

View file

@ -1,17 +1,17 @@
# trekplan — Brief, Research, Plan, Execute, Review, Continue
# voyage
![Version](https://img.shields.io/badge/version-5.9.0-blue)
Contract-driven Claude Code pipeline: brief, research, plan, execute, review. Agent swarms, research triangulation, adversarial review, multi-session resumption.
![Version](https://img.shields.io/badge/version-5.9.1-blue)
![License](https://img.shields.io/badge/license-MIT-green)
![Platform](https://img.shields.io/badge/platform-Claude%20Code-purple)
> **Solo-maintained, fork-and-own.** This plugin is a starting point, not a vendor product. Issues are welcome as signals; pull requests are not accepted. See [GOVERNANCE.md](GOVERNANCE.md) for the full model and what upstream provides.
*AI-generated: all code produced by Claude Code through dialog-driven development. [Full disclosure →](../../README.md#ai-generated-code-disclosure)*
*AI-generated: all code produced by Claude Code through dialog-driven development. Every change is human-directed, reviewed, and validated before commit.*
A [Claude Code](https://docs.anthropic.com/en/docs/claude-code) plugin for deep implementation planning, multi-source research, autonomous execution, independent post-hoc review, and zero-friction multi-session resumption. Six commands, one pipeline:
> **What's new — v5.8.0: offline gold-scored output eval (SKAL-1·4b).** The review-coordinator self-eval gains a scoring run: `lib/review/gold-scorer.mjs` grades a committed agent-run fixture against the golden corpus at `(file, rule_key)` granularity (precision/recall/f1 + verdict match), and the suite census gains a third category (`goldEval`) so a scoring run is counted apart from behavior coverage and doc-pins. Offline + deterministic — committed reviewer payloads, no live agent spawn (the LLM-in-the-loop tier is the separate 4c). Internal eval infrastructure; no command/agent/Handover change. **v5.7.1:** leaner always-loaded agent listing — `<example>` blocks relocated to agent bodies (~3,180 tok/turn, no behavior change). **v5.7.0:** opt-in per-session token/cost metering (SKAL-2) + eval foundation (SKAL-1·4a). **v5.6.1:** one-line `description:` for the four reference/dormant agents (~700 tok). **v5.5.0:** brief **framing** enforcement (`brief_version 2.2`) + a `/trekreview` reviewer-schema contract. Additive — no breaking changes. **Full version history → [CHANGELOG.md](CHANGELOG.md).**
| Command | What it does |
|---------|-------------|
| **`/trekbrief`** | Brief — interactive interview produces a task brief with explicit research plan |
@ -21,6 +21,23 @@ A [Claude Code](https://docs.anthropic.com/en/docs/claude-code) plugin for deep
| **`/trekreview`** | Review — independent post-hoc review of delivered code against the brief, severity-tagged findings |
| **`/trekcontinue`** | Continue — read `.session-state.local.json` and resume the next session in a multi-session project |
## Install
```bash
claude plugin marketplace add https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git
claude plugin install voyage@ktg-plugin-marketplace
```
Or enable directly in `~/.claude/settings.json`:
```json
{
"enabledPlugins": {
"voyage@ktg-plugin-marketplace": true
}
}
```
`/trekbrief`, `/trekplan`, and `/trekreview` each end by running `scripts/annotate.mjs` against the just-written artifact and printing the resulting `file://<abs path>` link. The operator opens the HTML in a browser, clicks any line of the document, writes their own note in the inline textarea, watches a sidebar of all notes (editable, deletable, persisted in browser `localStorage`), and clicks "Copy Prompt" to get one structured prompt that they paste back into Claude — Claude then revises the `.md` from the notes. **The operator drives every annotation.** See [Reviewing and annotating artifacts](#reviewing-and-annotating-artifacts-v502).
Every artifact lives in one project directory: `.claude/projects/{YYYY-MM-DD}-{slug}/` contains `brief.md`, `research/NN-*.md`, `plan.md`, `sessions/`, `progress.json`, and `review.md`.
@ -68,9 +85,6 @@ Under the hood, `lib/util/autonomy-gate.mjs` runs a small state machine (`idle
## Quick start
```bash
# Install the marketplace, then browse and enable plugins with /plugin
claude plugin marketplace add https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git
# Capture intent (interactive)
/trekbrief Add user authentication with JWT tokens
# → .claude/projects/2026-04-18-jwt-auth/brief.md
@ -127,7 +141,10 @@ Concrete capabilities, observable in the code — not aspirations.
**Virksomhet / regulated environment.** Defense-in-depth security across four layers (plugin hooks, prompt-level denylist, pre-execution plan scan, scoped tool access). `disableSkillShellExecution: true` recommendation for fork-ers handling untrusted briefs. No cloud dependency, no GitHub requirement. Validators are plain-Node CLIs — invocable from CI, custom hooks, or external tools, not just from voyage commands.
**What it doesn't solve:**
## Non-goals
What this pipeline does **not** solve — read this before adopting it:
- LLM output truthfulness. Validators check shape, not facts. A plan with hallucinated paths passes schema but fails in execute. Plan-critic catches some, not all.
- Multi-user concurrency on a single project directory. Two simultaneous executors will clobber `progress.json`.
- Cost management. Opus on the orchestrator layer is expensive; documented in [Cost profile](#cost-profile), no automatic model downgrade.
@ -757,26 +774,6 @@ The `pre-compact-flush.mjs` hook directly fixes the documented P0 in `docs/treke
**Annotation HTML requires a desktop browser.** `scripts/annotate.mjs` produces a single self-contained `.html` file you open with `file://` in any modern browser (Chrome / Safari / Firefox / Edge — last two versions). No CDN, no server, no npm runtime deps. State persists in `localStorage` so closing and re-opening the tab keeps your work, but it's local to one browser on one machine — not synced anywhere. If you want to annotate without a browser, paste the `.md` into Claude with "comments inline below" and write notes in chat — same end result, just without the visual surface.
## Installation
Add the marketplace and browse plugins with `/plugin`:
```bash
claude plugin marketplace add https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git
```
Or enable directly in `~/.claude/settings.json`:
```json
{
"enabledPlugins": {
"voyage@ktg-plugin-marketplace": true
}
}
```
An optional architect step between research and plan was previously available via a separate plugin; that architect plugin is no longer publicly distributed. The `architecture/overview.md` filesystem slot remains supported by `/trekplan` for any compatible producer.
## Profile system (v4.1.0)
Four built-in model profiles plus operator-defined `<custom>.yaml` (drop in `lib/profiles/`). Each profile pins `phase_models` for the six pipeline phases. The active profile is recorded in plan.md frontmatter as `profile: <name>` and emitted to JSONL stats for cost-attribution.
@ -921,6 +918,18 @@ suppress this, leave the `architecture/` directory absent from your
project directory. Discovery is additive — missing file is fine, no
error.
## Changelog
Full version history → [CHANGELOG.md](CHANGELOG.md).
Recent, in one line each:
- **v5.8.0** — offline gold-scored output eval (SKAL-1·4b): `lib/review/gold-scorer.mjs` grades a committed agent-run fixture against the golden corpus at `(file, rule_key)` granularity; suite census gains a `goldEval` category. Offline + deterministic, no live agent spawn.
- **v5.7.1** — leaner always-loaded agent listing (`<example>` blocks moved into agent bodies, ~3,180 tok/turn, no behavior change).
- **v5.7.0** — opt-in per-session token/cost metering (SKAL-2) + eval foundation (SKAL-1·4a).
- **v5.6.1** — one-line `description:` for the four reference/dormant agents (~700 tok).
- **v5.5.0** — brief **framing** enforcement (`brief_version 2.2`) + a `/trekreview` reviewer-schema contract. Additive, no breaking changes.
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).

View file

@ -90,16 +90,16 @@ want an interactive flow, use `/trekcontinue --help` to see the full pipeline.
## Phase 3 — Atomically write `.session-state.local.json` + sibling NEXT-SESSION-PROMPT.local.md
Write `<project-dir>/.session-state.local.json` with the schema-v1 object:
Write `{project_dir}/.session-state.local.json` with the schema-v1 object:
```json
{
"schema_version": 1,
"project": "<project-dir>",
"next_session_brief_path": "<arg 1>",
"next_session_label": "<arg 2>",
"project": "{project_dir}",
"next_session_brief_path": "{arg 1}",
"next_session_label": "{arg 2}",
"status": "in_progress",
"updated_at": "<now, ISO-8601>"
"updated_at": "{now, ISO-8601}"
}
```
@ -114,14 +114,22 @@ Under `node --input-type=module -e "<script>" arg1 arg2 arg3`, Node sets
This phase ALSO writes a sibling `NEXT-SESSION-PROMPT.local.md` in the
project directory with YAML frontmatter (`produced_by: trekendsession`,
`produced_at: <ISO-8601>`, `project: <project-dir>`). Both files are written
in a single ESM block so the writes succeed or fail together:
`produced_at: {ISO-8601}`, `project: {project_dir}`). Both files are written
in a single ESM block so the writes succeed or fail together.
Run the block below via the Bash tool at runtime, substituting the resolved
values for the `{curly}` placeholders (Phase 1 gives `{project_dir}`, Phase 2
gives `{next_brief_path}` and `{next_label}`). This is NOT an eager-exec
block — the values do not exist at command-load time. The import path must
stay absolute via `${CLAUDE_PLUGIN_ROOT}` — your Bash cwd is the user's
repo, not the plugin root, so a cwd-relative import throws
`ERR_MODULE_NOT_FOUND`:
```bash
!`node --input-type=module -e "
node --input-type=module -e "
import path from 'node:path';
import { writeFileSync } from 'node:fs';
import { atomicWriteJson } from './lib/util/atomic-write.mjs';
import { atomicWriteJson } from '${CLAUDE_PLUGIN_ROOT}/lib/util/atomic-write.mjs';
const [, dir, brief, label] = process.argv;
const now = new Date().toISOString();
const stateObj = { schema_version: 1, project: dir, next_session_brief_path: brief, next_session_label: label, status: 'in_progress', updated_at: now };
@ -132,26 +140,28 @@ const promptBody = '---\\nproduced_by: trekendsession\\nproduced_at: ' + now + '
writeFileSync(promptFile, promptBody);
console.log(stateFile);
console.log(promptFile);
" '<project-dir>' '<next-brief-path>' '<next-label>'`
" '{project_dir}' '{next_brief_path}' '{next_label}'
```
## Phase 4 — Validate + narrate
Validate the freshly-written state file:
Validate the freshly-written state file via the Bash tool at runtime,
substituting the resolved `{project_dir}` (NOT eager-exec — the file does
not exist at command-load time):
```bash
!`node lib/validators/session-state-validator.mjs --json <project-dir>/.session-state.local.json`
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/session-state-validator.mjs --json {project_dir}/.session-state.local.json
```
If `valid: true`, print the success block matching `/trekcontinue` Phase 3
narration (SC-8 cross-project consistency — same template both sides):
```
Session state written: <project-dir>/.session-state.local.json
Session state written: {project_dir}/.session-state.local.json
Project: <project-dir>
Next session: <next-label>
Brief: <next-brief-path>
Project: {project_dir}
Next session: {next_label}
Brief: {next_brief_path}
In a fresh Claude session, run /trekcontinue to resume.
```

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "voyage",
"version": "5.9.0",
"version": "5.9.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "voyage",
"version": "5.9.0",
"version": "5.9.1",
"license": "MIT",
"engines": {
"node": ">=18"

View file

@ -1,6 +1,6 @@
{
"name": "voyage",
"version": "5.9.0",
"version": "5.9.1",
"description": "Voyage — brief, research, plan, execute, review, continue. Contract-driven Claude Code pipeline. /trekbrief, /trekplan, and /trekreview each end by building a self-contained operator-annotation HTML (scripts/annotate.mjs, modelled on claude-code-100x): select text or click any heading/paragraph/list-item, pick intent (Fiks/Endre/Spørsmål), write comment, copy structured prompt, paste back, Claude revises the .md.",
"type": "module",
"engines": {

View file

@ -0,0 +1,127 @@
// tests/commands/trekendsession.test.mjs
// Regression tests for /trekendsession (commands/trekendsession.md).
//
// Bug (2026-07-03): two of the three !`...` eager-exec blocks contained
// unresolved placeholders (<project-dir> etc.). The harness executes
// eager-exec blocks at command LOAD time, so zsh parsed <project-dir> as
// input redirection and the command aborted before the model saw a single
// instruction. Eager-exec is only valid for self-contained commands.
//
// Pattern D (markdown structure) — assertions against command prose.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync, readdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..', '..');
const COMMANDS_DIR = join(ROOT, 'commands');
const COMMAND_FILE = join(COMMANDS_DIR, 'trekendsession.md');
function readCommand() {
return readFileSync(COMMAND_FILE, 'utf8');
}
function extractPhase(commandText, phaseHeader) {
const startIdx = commandText.indexOf(phaseHeader);
if (startIdx === -1) return '';
const rest = commandText.slice(startIdx);
const nextPhase = rest.search(/\n## (?:Phase |Hard )/);
if (nextPhase === -1) return rest;
return rest.slice(0, nextPhase);
}
// Extract all eager-exec blocks (!`...`) from a command/skill file,
// including multi-line blocks. Returns [{ content, line }].
function extractEagerBlocks(text) {
const blocks = [];
const re = /!`([^`]+)`/g;
let m;
while ((m = re.exec(text)) !== null) {
const line = text.slice(0, m.index).split('\n').length;
blocks.push({ content: m[1], line });
}
return blocks;
}
// ---------------------------------------------------------------
// Marketplace-wide regression guard: eager-exec blocks must be
// self-contained. An unresolved placeholder (<angle> or {curly}) in an
// eager block is executed verbatim by the shell at load time — <x> is
// parsed as input redirection and aborts the whole command load.
// ---------------------------------------------------------------
test('eager-exec guard — no !`-block in commands/ contains an unresolved placeholder', () => {
const offenders = [];
for (const file of readdirSync(COMMANDS_DIR).filter((f) => f.endsWith('.md'))) {
const text = readFileSync(join(COMMANDS_DIR, file), 'utf8');
for (const { content, line } of extractEagerBlocks(text)) {
// Placeholder conventions: <angle-word> or {curly_word}. Curly must
// contain a separator (- or _) so JS destructuring like {join} in a
// legitimate self-contained script does not false-positive; angle
// placeholders are unambiguous (shell would parse them as redirects).
if (/<[a-z][a-z0-9_-]*>/.test(content) || /\{[a-z][a-z0-9]*([_-][a-z0-9]+)+\}/.test(content)) {
offenders.push(`${file}:${line}`);
}
}
}
assert.deepEqual(
offenders,
[],
`eager-exec !\`-blocks run at command LOAD time and must be self-contained; ` +
`placeholder found in: ${offenders.join(', ')}`,
);
});
// ---------------------------------------------------------------
// trekendsession-specific: exactly one eager block (Phase 1 project
// discovery — self-contained, legitimate); Phases 3 and 4 are runtime
// Bash-tool commands with model-substituted values, never eager.
// ---------------------------------------------------------------
test('trekendsession — exactly one eager-exec block remains (Phase 1 discovery)', () => {
const cmd = readCommand();
const blocks = extractEagerBlocks(cmd);
assert.equal(
blocks.length,
1,
`expected exactly 1 eager-exec block (Phase 1 discovery), got ${blocks.length} at line(s) ${blocks.map((b) => b.line).join(', ')}`,
);
assert.match(
blocks[0].content,
/readdirSync\(root\)/,
'the surviving eager block must be the self-contained Phase 1 discovery script',
);
});
test('trekendsession Phase 3 — atomic-write block is runtime Bash (no eager prefix) with plugin-root import', () => {
const phase3 = extractPhase(readCommand(), '## Phase 3 ');
assert.doesNotMatch(phase3, /!`/, 'Phase 3 must not use eager-exec — values exist only at runtime');
assert.match(
phase3,
/\$\{CLAUDE_PLUGIN_ROOT\}\/lib\/util\/atomic-write\.mjs/,
'Phase 3 import must use the absolute ${CLAUDE_PLUGIN_ROOT} path — cwd is the user repo, not the plugin root',
);
assert.doesNotMatch(
phase3,
/['"]\.\/lib\/util\/atomic-write\.mjs['"]/,
'Phase 3 must not import atomic-write.mjs via a cwd-relative path',
);
});
test('trekendsession Phase 4 — validator call is runtime Bash (no eager prefix) with plugin-root path', () => {
const phase4 = extractPhase(readCommand(), '## Phase 4 ');
assert.doesNotMatch(phase4, /!`/, 'Phase 4 must not use eager-exec — the state-file path exists only at runtime');
assert.match(
phase4,
/\$\{CLAUDE_PLUGIN_ROOT\}\/lib\/validators\/session-state-validator\.mjs/,
'Phase 4 validator path must use the absolute ${CLAUDE_PLUGIN_ROOT} convention',
);
assert.doesNotMatch(
phase4,
/<[a-z][a-z0-9_-]*>/,
'Phase 4 must not use <angle> placeholders in commands — zsh parses <x> as input redirection',
);
});