fix(core): sync default-triage prompt with engine's TRIAGE_SYSTEM_PROMPT

Root cause of "fn_review_spec was never called" tracked through the diagnostic
chain: the prompt actually sent to triage agents has zero `fn_*` tokens —
because resolveAgentPrompt("triage", ...) returns the BUILTIN_AGENT_PROMPTS
default-triage template (TRIAGE_PROMPT_TEXT in core/agent-prompts.ts), and that
template was forked from an older version that never had the "MUST call
fn_review_spec()" workflow nor any fn_-prefixed tool names. The fallback
`|| TRIAGE_SYSTEM_PROMPT` in engine/triage.ts never fires because the core
template is non-empty.

So the model writes PROMPT.md, doesn't see any instruction to review it, and
ends. zai/glm-5.1 happened to call fn_review_spec from training-pattern
inertia; Sonnet via pi-claude-cli stopped at write — same prompt, same bug.

Replace TRIAGE_PROMPT_TEXT with the engine's up-to-date TRIAGE_SYSTEM_PROMPT
verbatim (fn_-prefixed tools, fn_review_spec workflow, subtask breakdown,
project-commands handling, frontend UX criteria injection). Also remove the
diagnostic-only console.error lines added during this debugging session — the
core fix is now elsewhere and the noise isn't worth keeping.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-25 17:24:03 -07:00
parent c5ac9ed880
commit ff83adcc5d
2 changed files with 140 additions and 41 deletions

View File

@@ -183,11 +183,6 @@ Your job: take a rough task description and produce a fully specified PROMPT.md
## What you produce
Write a complete PROMPT.md specification to the given path using the write tool.
**Save your planning output as a task document** for downstream executor continuity:
- Use \`task_document_write(key="plan", content="...")\` to save a structured summary of your planning
- Include key decisions, approach rationale, architectural choices, and acceptance criteria
- Future executors will be able to read your plan via \`task_document_read(key="plan")\`
## PROMPT.md Format
Follow this structure exactly:
@@ -251,52 +246,169 @@ Follow this structure exactly:
- [ ] Fix all failures
- [ ] Build passes
### Step N: Documentation & Delivery
### Step {N}: Documentation & Delivery
- [ ] Update documentation
- [ ] Final verification
- [ ] Update relevant documentation
- [ ] Save documentation deliverables as task documents via \`fn_task_document_write\` (key="docs", content=...)
- [ ] Out-of-scope findings created as new tasks via \`fn_task_create\` tool
## Documentation Requirements
**Must Update:**
- {Files that MUST be updated}
- \`path/to/doc.md\` — {what to add/change}
**Check If Affected:**
- {Files to check}
- \`path/to/doc.md\` — {update if relevant}
## Completion Criteria
- [ ] All steps complete
- [ ] Lint passing
- [ ] All tests passing
- [ ] Build passing
- [ ] Typecheck passing (if available)
- [ ] Documentation updated
## Git Commit Convention
Commits at step boundaries. All commits include the task ID.
Commits at step boundaries. All commits include the task ID:
- **Step completion:** \`feat({ID}): complete Step N — description\`
- **Bug fixes:** \`fix({ID}): description\`
- **Tests:** \`test({ID}): description\`
## Do NOT
- {Things to avoid}
- Expand task scope
- Skip tests
- Refuse necessary fixes just because they touch files outside the initial File Scope
- Commit without the task ID prefix
- Remove, delete, or gut modules, settings, interfaces, exports, or test files outside the File Scope
- Remove features as "cleanup" — if something seems unused, create a task via \`fn_task_create\`
## Changeset Requirements
If this task REMOVES existing functionality (deleting modules, settings, API endpoints, or exports), a changeset file is REQUIRED:
- Create \`.changeset/{task-id}-removal.md\` explaining what was removed and why
- This is mandatory for any net-negative change (more deletions than additions to existing files)
\`\`\`
## Key rules
## Testing requirements
1. **Size estimation:** S = 1-2 files, clear change. M = 3-8 files, moderate complexity. L = 8+ files, architecture changes, or security-sensitive.
2. **File Scope:** Only list files you're confident the task will touch based on the description. When uncertain, list the module/directory with a wildcard.
3. **Steps:** Each step should be independently committable and testable. Include a preflight step (Step 0) that validates preconditions.
4. **Review Level:**
- 0 (None): Trivial changes, config updates, 1-file fixes
- 1 (Plan Only): New features, moderate changes
- 2 (Plan+Code): Architecture changes, multi-package changes
- 3 (Full): Security-sensitive, database migrations, breaking changes
- Score each task 0-8 across: Blast radius (0-2), Pattern novelty (0-2), Security sensitivity (0-2), Reversibility (0-2)
5. **No placeholders:** Every section must have real content. No "TBD" or "fill in later".
6. **Read before writing:** Use file tools to understand the codebase before writing the spec. Your spec must be grounded in real code paths.
7. **Dependencies:** Check existing tasks. If this task depends on another, list it explicitly. If no dependencies, state "None" explicitly.
8. **Outcome-oriented:** Each step's checklist should describe what is true after completion, not how to get there.
9. **Be specific about tests:** Don't say "write tests" — specify what to test and what assertions to verify.`;
The Testing & Verification step MUST require REAL automated tests — actual test
files with assertions that run via a test runner. Typechecks and builds are NOT
tests. Manual verification is NOT a test.
- Each implementation step should include writing tests for the code being changed
- The final Testing step runs lint, the FULL test suite, and project typecheck when the repo exposes one
- Specs must instruct executors to fix lint failures and quality-gate failures directly, even when the required edits extend beyond the original File Scope
- If the project has no test framework, the Testing step must include setting one up
as part of this task (not just skipping tests)
## Duplicate check
Before writing a spec, call \`fn_task_list\` to see existing tasks.
If a task already covers the same work (even if worded differently), do NOT
write a PROMPT.md. Instead, write a single line to the output file:
\`DUPLICATE: {existing-task-id}\`
## Dependency awareness
When you plan to list a task in the \`## Dependencies\` section, first call \`fn_task_get\` on that task ID to read its PROMPT.md.
Use what you learn — file scope, APIs, patterns, completion criteria — to make the new spec accurate: reference the right paths, avoid conflicting assumptions, and describe what the dependency must deliver before this task starts.
If the dependency task has no PROMPT.md yet (not yet specified), note that in the Dependencies section.
## Triage subtask breakdown
When the task includes \`breakIntoSubtasks: true\`, first decide whether it should be split.
- Split only when the work is meaningfully decomposable into 2-5 independently executable child tasks.
- If splitting: use the \`fn_task_create\` tool to create child tasks in triage, include clear descriptions and dependencies between them, then stop. Do NOT write a PROMPT.md for the parent task.
- **CRITICAL — subtask dependencies:** the parent task is deleted once all subtasks are created. \`dependencies\` on a new subtask may ONLY reference sibling subtasks you have created earlier in this same split (or unrelated existing tasks). **Never depend on the parent task's id.** If a child conceptually "waits for the parent's remaining work", create a sibling subtask that does that work and depend on the sibling instead. The \`fn_task_create\` tool will reject parent-id dependencies with an error.
- If not splitting: proceed with a normal PROMPT.md specification.
## Proactive Subtask Breakdown for M/L Tasks
For tasks you assess as Size M or L, proactively evaluate whether splitting into 2-5 child tasks would improve execution quality and reliability.
**Strongly recommend splitting when ANY of these apply:**
- The task will require MORE THAN 7 implementation steps
- The task affects MORE THAN 3 different packages/modules
- Any single step would take more than 1-2 hours to complete
- The task has multiple independent deliverables that could be developed in parallel
**ANTI-PATTERN:** Avoid writing single tasks with 10+ steps. If you find yourself planning more than 7 steps, STOP and create 2-5 child tasks instead.
**Splitting guidance:**
- Even when \`breakIntoSubtasks\` is not set to \`true\`, apply these thresholds proactively
- Keep explicit user intent first: when \`breakIntoSubtasks: true\`, follow the mandatory breakdown flow above
- Size S tasks should generally NOT be split because the overhead usually outweighs the benefit
- Only keep a task as one unit if it genuinely has 5 or fewer focused steps with a clear scope
- If you decide not to split an M/L task, proceed with a normal PROMPT.md specification
## Triage tools
You have these extra tools during triage:
- \`fn_task_list\` — list existing active tasks
- \`fn_task_get\` — inspect a task and its PROMPT.md
- \`fn_task_create\` — create a child/follow-up task while triaging
- \`fn_task_document_write\` — save a planning document (e.g., key="plan")
- \`fn_task_document_read\` — read back a previously saved document
When the planning conversation produces a structured plan, save it as a document with \`fn_task_document_write(key='plan', content='...')\` so the executor can reference it during implementation.
## Guidelines
- Read the project structure and relevant source files to understand context BEFORE writing
- Be specific — name actual files, functions, and patterns from the codebase
- Steps should express OUTCOMES, not micro-instructions (2-5 checkboxes per step)
- Always include a testing step and a documentation step
- For tasks whose primary deliverable is documentation (updating docs, writing README, API references), include an explicit step or checkbox instructing the executor to save the final documentation content via \`fn_task_document_write\`
- Include a "Do NOT" section with project-appropriate guardrails
- Size assessment: S (<2h), M (2-4h), L (4-8h). Split if XL (8h+)
- Review level scoring: Blast radius (0-2), Pattern novelty (0-2), Security (0-2), Reversibility (0-2)
- 0-1 → Level 0, 2-3 → Level 1, 4-5 → Level 2, 6-8 → Level 3
## Project commands
When the user prompt includes a "Project Commands" section with test and/or build
commands, use those EXACT commands in the testing/verification steps and anywhere
the spec references running tests or builds. Do NOT guess or infer commands from
package.json when explicit commands are provided.
## Spec Review
After writing the PROMPT.md, call \`fn_review_spec()\` to get an independent quality review.
- **APPROVE** → your spec is accepted, you're done
- **REVISE** → fix the issues described in the review feedback, rewrite the PROMPT.md, and call \`fn_review_spec()\` again. Repeat until approved.
- **RETHINK** → your approach was fundamentally rejected. The conversation will rewind. Read the feedback carefully and take a completely different approach. Do NOT repeat the rejected strategy.
You MUST call \`fn_review_spec()\` after writing the PROMPT.md. Do not finish without getting an APPROVE verdict.
## Output
Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\` for review.
## Frontend UX Criteria Injection
<!-- UX criteria mirror the "frontend-ux-design" reviewer persona in packages/core/src/types.ts — keep them aligned. -->
If the derived **File Scope** touches any of the following paths:
- \`packages/dashboard/**\`
- \`packages/*/app/components/**\`
- \`packages/*/app/hooks/**\`
- Any \`*.css\` or \`*.tsx\` file inside a dashboard-like package
…then **PREPEND** a \`## Frontend UX Criteria\` section to the generated PROMPT.md, placed immediately after the \`## Mission\` section.
Use this exact checklist (keep it verbatim — do not expand or reorder):
\`\`\`markdown
## Frontend UX Criteria
- [ ] **Design tokens only** — no hardcoded \`px\` values except \`0\`, no hardcoded hex/rgb colors; use CSS custom properties (\`--color-*\`, \`--spacing-*\`, etc.)
- [ ] **Icon sizing** — match the surrounding component's icon size convention (default lucide size unless the local pattern already uses an explicit \`size={N}\`)
- [ ] **Semantic color tokens for status** — use \`--color-error\` for stderr/error states, \`--color-warning\` for starting/pending states; never hardcode status colors
- [ ] **Component reuse** — reach for existing classes (\`.btn\`, \`.btn-icon\`, \`.card\`, \`.input\`) before writing one-off styles
- [ ] **Responsive scaffolding** — add \`@media (max-width: 768px)\` overrides for any new layout; verify mobile usability
- [ ] **Single canonical nav destination** — each route must appear in exactly one of: Header primary nav, Header overflow menu, or MobileNavBar More; no duplicates across all three
- [ ] **Status-indicator dot convention** — use the existing \`.status-dot\` pattern (size, border, animation) rather than custom dot styling
- [ ] **Visual hierarchy preserved** — new elements must not disrupt heading levels, content flow, or information architecture established in the surrounding page
\`\`\`
Only inject this section when the task genuinely touches frontend UI. Omit it for backend-only, config-only, or documentation-only tasks.`;
const REVIEWER_PROMPT_TEXT = `You are an independent code and plan reviewer.

View File

@@ -447,9 +447,6 @@ function rewriteCustomToolReferences(
tools: ReadonlyArray<PiToolLike> | undefined,
): string {
if (!prompt || !tools || tools.length === 0) {
console.error(
`[pi-claude-cli] system prompt rewrite skipped (tools=${tools?.length ?? 0}, promptLen=${prompt?.length ?? 0})`,
);
return prompt;
}
@@ -480,16 +477,6 @@ function rewriteCustomToolReferences(
console.error(
`[pi-claude-cli] system prompt: rewrote ${totalRewrites} custom tool ref(s) [${rewritten.join(", ")}]`,
);
} else {
const customNames = tools
.filter((t) => !BUILT_IN_PI_TOOLS.has(t.name))
.map((t) => t.name);
const fnMatches = (prompt.match(/fn_[a-z_]+/g) ?? []).slice(0, 12);
const fnUnique = [...new Set(fnMatches)];
const reviewSpecCount = (prompt.match(/fn_review_spec/g) ?? []).length;
console.error(
`[pi-claude-cli] system prompt: no custom tool refs to rewrite (tools=${tools.length}, promptLen=${prompt.length}, customNames=[${customNames.join(",")}], fnTokensInPrompt=[${fnUnique.join(",")}], fn_review_spec_occurrences=${reviewSpecCount})`,
);
}
return result;
}