fix(trekplan): explorers and reviewers read only in the repo and get the brief

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-12 19:45:40 +02:00
commit e6755bd886
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
2 changed files with 128 additions and 12 deletions

View file

@ -446,7 +446,8 @@ below (operator decision 2026-10-08).
Launch the **brief-reviewer** agent:
Prompt: "Review this task brief for quality: {brief_path}. Check completeness,
consistency, testability, scope clarity, and research-plan validity."
consistency, testability, scope clarity, and research-plan validity.
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names)."
Handle the verdict:
- **PROCEED** — continue to Phase 5.
@ -502,40 +503,50 @@ medium: default, large: default) instead of dropping agents.
| `research-scout` | Conditional | Conditional | Conditional | External docs (only when unfamiliar tech detected AND no research brief covers it) |
| `convention-scanner` | No | Yes | Yes | Coding conventions, naming, style, test patterns |
research-scout's web search is not a local read; the line above bounds its local reads only. The session decomposer (`--decompose`) is not an exploring or reviewing agent and does not carry the line.
### Always launch (all codebase sizes):
**architecture-mapper** — full codebase structure, tech stack, patterns, anti-patterns.
Prompt: "Analyze the architecture of this codebase. The task being planned is: {task}"
Prompt: "Analyze the architecture of this codebase. The task being planned is: {task}.
Brief file: {brief_path} — read it first.
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names)."
**dependency-tracer** — module connections, data flow, side effects for task-relevant code.
Prompt: "Trace dependencies and data flow relevant to this task: {task}. Focus on modules
that will be affected by the implementation."
that will be affected by the implementation. Brief file: {brief_path} — read it first.
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names)."
**risk-assessor** — risks, edge cases, failure modes, technical debt near task area.
Prompt: "Assess risks and failure modes for implementing this task: {task}. Check for
complexity hotspots, security boundaries, and technical debt in the relevant code."
complexity hotspots, security boundaries, and technical debt in the relevant code.
Brief file: {brief_path} — read it first.
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names)."
**task-finder** — all files, functions, types, and interfaces directly related to the task.
Prompt: "Find all code relevant to this task: {task}. Include existing implementations
that solve similar problems, API boundaries, database models, configuration files.
Report file paths and line numbers for every finding."
Report file paths and line numbers for every finding. Brief file: {brief_path} — read it first.
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names)."
**test-strategist** — existing test patterns, coverage gaps, test strategy.
Prompt: "Analyze the test infrastructure and design a test strategy for this task: {task}.
Discover existing patterns and identify coverage gaps."
Discover existing patterns and identify coverage gaps. Brief file: {brief_path} — read it first.
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names)."
**git-historian** — recent changes, code ownership, hot files, active branches.
Prompt: "Analyze git history relevant to this task: {task}. Brief file:
{brief_path} — read its Constraints and Non-Goals for files it keeps unchanged,
and never recommend changing one. Report recent changes, ownership, hot files,
and active branches that may affect planning. Name the remote as `git remote -v`
shows it."
shows it.
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names)."
### Launch for medium+ codebases (50+ files):
**Convention Scanner** — use the `convention-scanner` plugin agent (model: "opus")
for medium+ codebases only.
Provide concrete examples from the codebase, not generic advice."
Prompt: "Discover the coding conventions relevant to this task: {task}. Brief file: {brief_path} — read it first. Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names). Provide concrete examples from the codebase, not generic advice."
### Conditional: External research
@ -553,7 +564,8 @@ If yes (and not covered by attached briefs): launch **research-scout** in
parallel with the other agents.
Prompt: "Research the following technologies for this task: {task}.
Specific questions: {list specific questions about the technology}.
Technologies to research: {list}."
Technologies to research: {list}. Brief file: {brief_path} — read it first.
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names)."
If no external technology is involved or all topics are covered by briefs:
skip research-scout and note:
@ -571,7 +583,10 @@ Common reasons for deep-dives:
- A risk was flagged but the actual impact needs verification
For each significant gap, spawn a targeted deep-dive agent (model: "opus",
subagent_type: "Explore") with a narrow, specific brief.
subagent_type: "Explore") with a narrow, specific brief. Each deep-dive prompt
carries these two lines:
Brief file: {brief_path} — read it first.
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names).
Launch up to 3 deep-dive agents in parallel. If no gaps exist, skip this phase
and note: "Initial exploration was sufficient — no deep-dives needed."
@ -920,7 +935,8 @@ wrong ordering, fragile assumptions, missing error handling, scope creep,
underspecified steps. Rate each finding as blocker, major, or minor.
End your response with the REQUIRED machine-readable `json` findings block
(schema in `agents/plan-critic.md`) so the orchestrator can pipe it to the
dedup helper. You have no Write tool — return the block inline, do not write a file."
dedup helper. You have no Write tool — return the block inline, do not write a file.
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names)."
**scope-guardian** — scope alignment check.
Prompt: "Check this implementation plan against the brief.
@ -930,7 +946,8 @@ Find scope creep (plan does more than the brief requires) and scope gaps
exist. Verify that every Success Criterion in the brief is covered by the
plan's Verification section. End your response with the REQUIRED machine-readable
`json` findings block (schema in `agents/scope-guardian.md`). You have no Write
tool — return the block inline, do not write a file."
tool — return the block inline, do not write a file.
Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names)."
After both complete, **extract each reviewer's trailing `json` findings block**
and pipe both into the dedup helper via **stdin** — the reviewers are read-only

View file

@ -0,0 +1,99 @@
// tests/commands/trekplan-explorer-prompts.test.mjs
// Prose pins: every agent /trekplan sends to explore or review is told to read
// local files only inside the repo under exploration (plus the paths its prompt
// gives it), and every exploring agent gets the brief path.
// Blocks are found by a named list; a block that cannot be found fails, so an
// empty scan is never green.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { findSection } from '../../lib/parsers/plan-schema.mjs';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const PLAN_CMD = readFileSync(join(ROOT, 'commands', 'trekplan.md'), 'utf8');
const STAY_LINE =
'Read local files only inside the repo under exploration, plus paths this prompt gives you (the brief, the plan, and templates it names).';
/** The text of a level-2 section, up to the next `## ` heading outside a fence. */
function section(heading) {
const s = findSection(PLAN_CMD, heading.replace(/^## /, ''), 2);
assert.ok(s, `missing ${heading}`);
return s.text;
}
/** Collapse line wraps so a sentence wrapped across lines still matches. */
const flat = (s) => s.replace(/\s+/g, ' ');
/**
* The quoted prompt that follows `marker` inside `text`: from `Prompt: "` to
* the next closing `"`. Fails when the marker or the prompt is missing, or
* when the first `Prompt: "` after the marker sits in a later paragraph.
*/
function promptAfter(text, marker, label) {
const at = text.indexOf(marker);
assert.ok(at !== -1, `${label}: marker ${JSON.stringify(marker)} not found`);
const open = text.indexOf('Prompt: "', at);
assert.ok(open !== -1, `${label}: no Prompt: after the marker`);
assert.ok(!text.slice(at, open).includes('\n\n'),
`${label}: the next Prompt: lies in another paragraph, not in this block`);
const start = open + 'Prompt: "'.length;
const close = text.indexOf('"', start);
assert.ok(close !== -1, `${label}: prompt never closes`);
return text.slice(start, close);
}
const P4B = section('## Phase 4b — Brief review');
const P5 = section('## Phase 5 — Parallel exploration (specialized agents + research)');
const P6 = section('## Phase 6 — Targeted deep-dives');
const P9 = section('## Phase 9 — Adversarial review (bounded critic loop)');
function phase6Rule() {
const start = P6.indexOf('spawn a targeted deep-dive agent');
const end = P6.indexOf('Launch up to 3 deep-dive agents');
assert.ok(start !== -1 && end > start, 'Phase 6 deep-dive rule not found');
return P6.slice(start, end);
}
// [label, block text getter, carries {brief_path}?]
const BLOCKS = [
['architecture-mapper', () => promptAfter(P5, '**architecture-mapper**', 'architecture-mapper'), true],
['dependency-tracer', () => promptAfter(P5, '**dependency-tracer**', 'dependency-tracer'), true],
['risk-assessor', () => promptAfter(P5, '**risk-assessor**', 'risk-assessor'), true],
['task-finder', () => promptAfter(P5, '**task-finder**', 'task-finder'), true],
['test-strategist', () => promptAfter(P5, '**test-strategist**', 'test-strategist'), true],
['git-historian', () => promptAfter(P5, '**git-historian**', 'git-historian'), true],
['convention-scanner', () => promptAfter(P5, '**Convention Scanner**', 'convention-scanner'), true],
['research-scout', () => promptAfter(P5, 'launch **research-scout**', 'research-scout'), true],
['Phase 6 deep-dive rule', phase6Rule, true],
['brief-reviewer', () => promptAfter(P4B, '**brief-reviewer**', 'brief-reviewer'), false],
['plan-critic', () => promptAfter(P9, '**plan-critic**', 'plan-critic'), false],
['scope-guardian', () => promptAfter(P9, '**scope-guardian**', 'scope-guardian'), false],
];
test('all 12 named blocks are found (an empty scan fails)', () => {
assert.equal(BLOCKS.length, 12);
let found = 0;
for (const [label, get] of BLOCKS) {
const text = get();
assert.ok(text.trim().length > 0, `${label}: empty block`);
found += 1;
}
assert.equal(found, 12);
});
for (const [label, get] of BLOCKS) {
test(`${label} carries the stay-in-repo line`, () => {
assert.ok(flat(get()).includes(STAY_LINE), `${label} does not carry: ${STAY_LINE}`);
});
}
for (const [label, get, needsBrief] of BLOCKS) {
if (!needsBrief) continue;
test(`${label} is given the brief path`, () => {
assert.ok(get().includes('{brief_path}'), `${label} is not given {brief_path}`);
});
}