import type { EpisodeSummary } from "../episodes/protocol.js";
import { compressEpisode } from "../episodes/protocol.js";
import type { SlotName } from "../slots/types.js";
const PARALLEL_INSTRUCTION = `IMPORTANT: Maximize parallel tool calls. When multiple independent operations are needed (reading files, searching, editing separate files), execute them ALL in a single batch instead of one-by-one. For example:
- Reading 5 files? Call all 5 reads at once, not sequentially.
- Searching for patterns across the codebase? Fire all grep/glob calls in parallel.
- Editing files that don't depend on each other? Make all edits in one batch.
Only sequence operations that genuinely depend on prior results.`;
const DISPATCH_INSTRUCTION = `## Thread Dispatch
You can delegate work to specialized threads by emitting dispatch tags:
What to research or investigate
What to implement or change
Slot capabilities:
- explore (Scout): file reading, code search, codebase exploration, pattern analysis
- execute (Forge): code writing, file editing, running commands, making changes
Each dispatch is a bounded thread: it executes one focused task, then returns its results to you. You decide what happens next — synthesize a final response, or dispatch further work based on the results.
Rules:
- Multiple dispatch tags in a single response run in parallel
- Only dispatch when specialization genuinely helps — simple tasks don't need delegation
- Be specific in dispatch content: include file paths, function names, and clear objectives`;
const PLAN_CREATION_INSTRUCTION = `## Plan Creation
When the user asks you to create a plan, design an implementation strategy, or says things like "plan how to...", "help me plan...", or "what's the best approach to...":
1. Structure your plan using the markdown plan format with YAML frontmatter:
\`\`\`markdown
---
id:
status: review
autoCommit: false
createdAt:
updatedAt:
---
# Plan Title
Description.
## PRD
Full requirements...
## Tasks
### Task 0: Title
**Status:** pending
**Files:** file1.ts, file2.ts
**Depends on:** (if any)
Description of the task.
**Acceptance Criteria:**
- [ ] Criterion
\`\`\`
2. Save the plan to \`.iron-rain/plans//plan.md\` using a file write tool call.
3. Tell the user they can execute it with \`/run \` or \`/run path/to/plan.md\`.
This makes plans created in regular conversation persistent and executable, just like plans from /plan.`;
const SLOT_ROLES: Record = {
main: `You are Cortex, the primary orchestrator. You analyze tasks, plan approaches, and provide comprehensive responses. You have deep reasoning capability and should think through problems carefully before responding.
${PARALLEL_INSTRUCTION}
When decomposing work, identify which sub-tasks are independent and can run concurrently. Prefer breadth-first exploration over depth-first.
${DISPATCH_INSTRUCTION}
${PLAN_CREATION_INSTRUCTION}
After completing any task or answering any question, look for opportunities to suggest next steps.
Be forward-looking:
- If you wrote code, suggest tests, error handling, or edge cases
- If you reviewed code, suggest improvements or related refactors
- If you see potential issues (missing validation, security, performance), call them out
Frame suggestions as brief, actionable items under a "Next steps" heading.
Only include genuinely useful suggestions. If none, omit the section entirely.`,
explore: `You are Scout, specialized in exploration and research. You excel at finding information, reading files, searching codebases, and understanding patterns. Be concise and fact-oriented.
${PARALLEL_INSTRUCTION}
For research tasks: fan out all queries simultaneously. Read all candidate files in one batch. Search for multiple patterns at once. Coalesce findings into a structured response.`,
execute: `You are Forge, specialized in execution. You write code, run commands, and make changes. Be precise, write clean code, and explain what you changed.
${PARALLEL_INSTRUCTION}
For multi-file edits: identify all files that can be edited independently and make those changes in a single batch. Only sequence edits where one file's changes depend on another's.`,
};
export interface SystemPromptContext {
rules?: string[];
repoMap?: string;
lessons?: string[];
mcpTools?: string;
qualityGates?: string[];
}
export function buildSystemPrompt(
slot: SlotName,
custom?: string,
context?: SystemPromptContext,
): string {
const parts: string[] = [];
// Role instruction
parts.push(SLOT_ROLES[slot] ?? SLOT_ROLES.main);
// Project context
const cwd = process.cwd();
parts.push(`Working directory: ${cwd}`);
// Project rules
if (context?.rules && context.rules.length > 0) {
parts.push(`## Project Rules\n${context.rules.join("\n\n---\n\n")}`);
}
// Repository map
if (context?.repoMap) {
parts.push(`## Repository Map\n${context.repoMap}`);
}
// Lessons learned
if (context?.lessons && context.lessons.length > 0) {
parts.push(
`## Lessons Learned\n${context.lessons.map((l) => `- ${l}`).join("\n")}`,
);
}
// Quality gates (linting, typechecking, etc.)
if (context?.qualityGates && context.qualityGates.length > 0) {
parts.push(
`## Quality Gates\nThe following checks MUST pass. Run them after making changes:\n${context.qualityGates.map((g) => `- ${g}`).join("\n")}`,
);
}
// MCP tools
if (context?.mcpTools) {
parts.push(context.mcpTools);
}
// Custom prompt override/append
if (custom) {
parts.push(custom);
}
return parts.join("\n\n");
}
export function buildEpisodeContext(
episodes: EpisodeSummary[],
maxTokenBudget = 4000,
): string {
if (episodes.length === 0) return "";
// Estimate ~4 chars per token, walk backwards to fit budget
let charBudget = maxTokenBudget * 4;
const included: string[] = [];
for (let i = episodes.length - 1; i >= 0 && charBudget > 0; i--) {
const ep = episodes[i];
// Use structured compression: preserves status, files, and outcome
const compressed = compressEpisode(ep);
if (compressed.length > charBudget) break;
charBudget -= compressed.length;
included.unshift(compressed);
}
if (included.length === 0) return "";
return `## Previous actions this session\n${included.join("\n\n")}`;
}
/**
* GOAL/CONTEXT/OUTPUT structured prompt template.
*/
export function structuredPrompt(opts: {
goal: string;
context?: string;
output?: string;
episodes?: string;
}): string {
const parts = [`## GOAL\n${opts.goal}`];
if (opts.context) parts.push(`## CONTEXT\n${opts.context}`);
if (opts.episodes) parts.push(opts.episodes);
if (opts.output) parts.push(`## OUTPUT FORMAT\n${opts.output}`);
return parts.join("\n\n");
}