@maestria/pi 0.5.9 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../src/state.ts","../src/agents.ts","../src/modes.ts","../src/rules.ts","../src/compaction.ts","../src/subagent.ts","../src/commands.ts","../src/tools.ts","../src/extension.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionCommandContext } from '@earendil-works/pi-coding-agent';\nimport type { ModeKeyword } from '@/modes.js';\n\nconst HANDOFF_HISTORY_CAP = 5;\nconst FILE_HISTORY_CAP = 10;\n\nexport interface HandoffEntry {\n from: string;\n to: string;\n task: string;\n timestamp: number;\n}\n\nexport interface SubagentStatusInfo {\n type: string;\n status: string;\n startedAt: number;\n completedAt?: number;\n}\n\nexport interface MaestriaState {\n mode: ModeKeyword | null;\n activeTask: string;\n completionPromise: string;\n specialistsDelegated: string[];\n blockers: string[];\n filesModified: string[];\n filesRead: string[];\n handoffHistory: HandoffEntry[];\n reviewMode: boolean;\n originalModel: string | null;\n originalTools: string[] | null;\n subagentStatus: Record<string, SubagentStatusInfo>;\n /** Model ID to use when entering review mode. Null = no preference. */\n reviewModel: string | null;\n}\n\nexport function createInitialState(): MaestriaState {\n return {\n mode: null,\n activeTask: '',\n completionPromise: '',\n specialistsDelegated: [],\n blockers: [],\n filesModified: [],\n filesRead: [],\n handoffHistory: [],\n reviewMode: false,\n originalModel: null,\n originalTools: null,\n subagentStatus: {},\n reviewModel: null,\n };\n}\n\nexport function recordHandoff(\n state: MaestriaState,\n from: string,\n to: string,\n task: string,\n): MaestriaState {\n const entry: HandoffEntry = { from, to, task, timestamp: Date.now() };\n const history = [entry, ...state.handoffHistory].slice(0, HANDOFF_HISTORY_CAP);\n return { ...state, handoffHistory: history };\n}\n\nfunction prependDeduped(files: string[], path: string, cap: number): string[] {\n const filtered = files.filter((f) => f !== path);\n return [path, ...filtered].slice(0, cap);\n}\n\nexport function recordFileModified(state: MaestriaState, path: string): MaestriaState {\n return { ...state, filesModified: prependDeduped(state.filesModified, path, FILE_HISTORY_CAP) };\n}\n\nexport function recordFileRead(state: MaestriaState, path: string): MaestriaState {\n return { ...state, filesRead: prependDeduped(state.filesRead, path, FILE_HISTORY_CAP) };\n}\n\nexport function recordSubagentStatus(\n state: MaestriaState,\n id: string,\n info: SubagentStatusInfo,\n): MaestriaState {\n return { ...state, subagentStatus: { ...state.subagentStatus, [id]: info } };\n}\n\nexport function setReviewMode(state: MaestriaState, active: boolean): MaestriaState {\n return {\n ...state,\n reviewMode: active,\n };\n}\n\n/**\n * Exit review mode and return the original model/tools for restoration.\n * Returns a new state (immutable) with review mode cleared and the\n * saved originals so the caller can pass them to pi.setModel/setActiveTools.\n */\nexport function exitReviewMode(state: MaestriaState): {\n state: MaestriaState;\n originalModel: string | null;\n originalTools: string[] | null;\n} {\n return {\n state: {\n ...state,\n reviewMode: false,\n originalModel: null,\n originalTools: null,\n },\n originalModel: state.originalModel,\n originalTools: state.originalTools,\n };\n}\n\n/**\n * Restore the original model and tools saved when review mode was entered.\n * Clears review mode from state and resets pi to the pre-review configuration.\n */\nexport async function restoreOriginalState(\n pi: ExtensionAPI,\n ctx: ExtensionCommandContext,\n state: MaestriaState,\n): Promise<void> {\n const { state: clearedState, originalModel, originalTools } = exitReviewMode(state);\n\n // Restore original tools first (makes full toolset available again)\n if (originalTools && originalTools.length > 0) {\n pi.setActiveTools(originalTools);\n }\n\n // Restore original model - best-effort, failures are non-fatal\n if (originalModel) {\n try {\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m: { id: string }) => m.id === originalModel);\n if (model) {\n await pi.setModel(model);\n }\n } catch {\n // Best-effort: model restoration is non-critical\n }\n }\n\n // Clear review mode state\n Object.assign(state, clearedState);\n}\n\n/**\n * Persist the current state to the session by appending a custom entry.\n * Creates a shallow copy to ensure appendEntry sees the latest snapshot.\n */\nexport function persistState(pi: ExtensionAPI, state: MaestriaState): void {\n pi.appendEntry('maestria_state', { ...state });\n}\n\n/**\n * If a review model is configured, switch to it.\n * Returns the model ID switched to, or null if no switch occurred.\n */\nexport async function cycleToReviewModel(\n pi: ExtensionAPI,\n ctx: ExtensionCommandContext,\n state: MaestriaState,\n): Promise<string | null> {\n const reviewModel = state.reviewModel;\n if (!reviewModel) {\n return null;\n }\n try {\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m) => m.id === reviewModel);\n if (model) {\n await pi.setModel(model);\n return reviewModel;\n } else {\n ctx.ui.notify(`Review model \"${reviewModel}\" not found in registry, staying on current.`);\n return null;\n }\n } catch {\n ctx.ui.notify(`Could not switch to review model \"${reviewModel}\", staying on current.`);\n return null;\n }\n}\n\nexport function renderMaestriaSummary(state: MaestriaState): string {\n const parts: string[] = [];\n\n if (state.mode) {\n parts.push(`**Mode:** ${state.mode.toUpperCase()}`);\n }\n\n if (state.reviewModel) {\n parts.push(`**Review Model:** ${state.reviewModel}`);\n }\n\n if (state.activeTask) {\n parts.push(`**Goal:** ${state.activeTask}`);\n }\n\n if (state.completionPromise) {\n parts.push(`**Completion Promise:** ${state.completionPromise}`);\n }\n\n if (state.specialistsDelegated.length > 0) {\n parts.push(`**Specialists Delegated:** ${state.specialistsDelegated.join(', ')}`);\n }\n\n if (state.blockers.length > 0) {\n parts.push('**Blockers:**');\n for (const blocker of state.blockers) {\n parts.push(`- ${blocker}`);\n }\n }\n\n const fileSubs: string[] = [];\n if (state.filesModified.length > 0) {\n fileSubs.push(`**Modified:** ${state.filesModified.join(', ')}`);\n }\n if (state.filesRead.length > 0) {\n fileSubs.push(`**Read:** ${state.filesRead.join(', ')}`);\n }\n if (fileSubs.length > 0) {\n parts.push(`**Files:** ${fileSubs.join('; ')}`);\n }\n\n if (state.handoffHistory.length > 0) {\n parts.push('**Recent Handoffs:**');\n for (const entry of state.handoffHistory) {\n parts.push(`- ${entry.from} → ${entry.to}: ${entry.task}`);\n }\n }\n\n return parts.join('\\n\\n');\n}\n\nexport { HANDOFF_HISTORY_CAP, FILE_HISTORY_CAP };\n","import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { homedir } from 'node:os';\nimport type { ExtensionContext } from '@earendil-works/pi-coding-agent';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/** Source directory for bundled specialist agent files (synced from canonical) */\nconst AGENTS_SRC = join(__dirname, '..', 'agents');\n\nconst SPECIALIST_NAMES = [\n 'adventurer',\n 'architect',\n 'builder',\n 'diagnose',\n 'planner',\n 'reviewer',\n 'writer',\n] as const;\n\n/**\n * Deploy bundled specialist agent .md files to the pi-subagents agents directory.\n *\n * pi-subagents discovers agent types from ~/.pi/agent/agents/*.md on every\n * registry.reload() call (which fires automatically on each tool invocation).\n * This function ensures the files are in place before the first subagent dispatch.\n *\n * Only creates files that don't already exist - never overwrites user-customized agents.\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function deploySpecialistAgents(_ctx?: ExtensionContext): void {\n const AGENTS_DEST = join(homedir(), '.pi', 'agent', 'agents');\n const srcDir = AGENTS_SRC;\n\n if (!existsSync(srcDir)) {\n console.warn('[maestria] Agents source directory not found:', srcDir);\n return;\n }\n\n // Ensure destination directory exists\n try {\n mkdirSync(AGENTS_DEST, { recursive: true });\n } catch {\n console.warn('[maestria] Could not create agents directory:', AGENTS_DEST);\n return;\n }\n\n let deployed = 0;\n for (const name of SPECIALIST_NAMES) {\n const srcFile = join(srcDir, `${name}.md`);\n const destFile = join(AGENTS_DEST, `${name}.md`);\n\n if (!existsSync(srcFile)) {\n console.warn(`[maestria] Agent source not found: ${name}.md`);\n continue;\n }\n\n if (existsSync(destFile)) continue;\n\n try {\n const content = readFileSync(srcFile, 'utf-8');\n writeFileSync(destFile, content, 'utf-8');\n deployed++;\n } catch (err) {\n console.warn(`[maestria] Failed to deploy agent ${name}:`, err);\n }\n }\n\n if (deployed > 0) {\n console.log(`[maestria] Deployed ${deployed} specialist agents to ${AGENTS_DEST}`);\n }\n}\n","import { readFileSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { persistState, restoreOriginalState } from '@/state.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst COMMANDS_DIR = resolve(__dirname, '../agents/commands');\n\nfunction loadModePrompt(name: string): string {\n const content = readFileSync(resolve(COMMANDS_DIR, `${name}.md`), 'utf-8');\n const modeIdx = content.indexOf('## MODE:');\n if (modeIdx !== -1) {\n return content.slice(modeIdx).replace(/\\s+$/, '') + '\\n';\n }\n return content.replace(/\\s+$/, '') + '\\n';\n}\n\nexport const MODE_KEYWORDS = ['fein', 'sonar', 'blitz'] as const;\nexport type ModeKeyword = (typeof MODE_KEYWORDS)[number];\n\nconst MODE_MARKERS: Record<ModeKeyword, string> = {\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n blitz: '[MODE: blitz]',\n};\n\n/** Lazily cached mode prompts — loaded on first access, never throws. */\nconst _promptCache: Partial<Record<ModeKeyword, string>> = {};\n\nexport function getModePrompt(keyword: ModeKeyword): string {\n if (!(keyword in _promptCache)) {\n try {\n _promptCache[keyword] = loadModePrompt(keyword);\n } catch (e) {\n console.warn(`[maestria] Failed to load mode prompt \"${keyword}\":`, e);\n _promptCache[keyword] = '';\n }\n }\n return `${MODE_MARKERS[keyword]}\\n\\n${_promptCache[keyword]}`;\n}\n\nexport function installModeCommands(pi: ExtensionAPI, state: MaestriaState): void {\n for (const keyword of MODE_KEYWORDS) {\n pi.registerCommand(keyword, {\n description: `Set workflow mode to ${keyword}`,\n handler: async (args, ctx) => {\n if (state.reviewMode) {\n await restoreOriginalState(pi, ctx, state);\n }\n\n state.mode = keyword;\n persistState(pi, state);\n\n if (args.trim()) {\n const modeMessage = [\n getModePrompt(keyword),\n '',\n `Run the maestria default pipeline on: ${args}`,\n ].join('\\n');\n pi.sendUserMessage(modeMessage, { deliverAs: 'steer' });\n } else {\n ctx.ui.notify(`Mode set to ${keyword}. Describe what you'd like to work on.`);\n }\n },\n });\n }\n}\n","import type {\n BeforeAgentStartEvent,\n BeforeAgentStartEventResult,\n ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { getModePrompt } from '@/modes.js';\n\n/**\n * Creates a before_agent_start handler that injects workflow mode prompts.\n *\n * This is the only dynamic prompt injection needed from the extension.\n * Static behavioral content (orchestrator prompt + global rules) is\n * auto-injected by Pi's skill system via SKILL.md files registered in\n * the pi.skills manifest field - the standard Pi extension pattern.\n *\n * When no mode is active, the handler returns void (no modification),\n * letting Pi's built-in prompt assembly (skills + context files + tools)\n * stand as-is.\n */\nexport function createModePromptHandler(state: MaestriaState) {\n return (\n event: BeforeAgentStartEvent,\n _ctx: ExtensionContext,\n ): BeforeAgentStartEventResult | void => {\n if (!state.mode) return;\n\n const parts: string[] = [\n event.systemPrompt,\n '',\n getModePrompt(state.mode),\n '',\n `The user has set workflow mode to \"${state.mode}\". ` +\n 'Honor this mode throughout the session until changed via /command.',\n ];\n\n return { systemPrompt: parts.join('\\n') };\n };\n}\n","import type {\n ExtensionAPI,\n SessionBeforeCompactEvent,\n SessionBeforeTreeEvent,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { renderMaestriaSummary } from '@/state.js';\n\nexport function installCompactionHandlers(pi: ExtensionAPI, state: MaestriaState): void {\n pi.on('session_before_compact', (event: SessionBeforeCompactEvent) => {\n return {\n compaction: {\n summary: renderMaestriaSummary(state),\n details: { ...state },\n firstKeptEntryId: event.preparation.firstKeptEntryId,\n tokensBefore: event.preparation.tokensBefore,\n },\n };\n });\n\n pi.on('session_before_tree', (event: SessionBeforeTreeEvent) => {\n if (event.preparation.userWantsSummary) {\n return {\n summary: {\n summary: renderMaestriaSummary(state),\n },\n };\n }\n return undefined;\n });\n}\n","import { Type } from 'typebox';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { SUBAGENT_EVENTS } from '@gotgenes/pi-subagents';\nimport type { MaestriaState } from '@/state.js';\nimport { persistState, recordHandoff } from '@/state.js';\n\n/**\n * Maestria cross-extension event names.\n * Other Pi extensions can subscribe via `pi.events?.on(...)`.\n * Convention: `maestria:<domain>:<action>`\n */\nexport const MAESTRIA_EVENTS = {\n REVIEW_ACTIVATED: 'maestria:review:activated',\n REVIEW_DEACTIVATED: 'maestria:review:deactivated',\n SUBAGENT_STARTED: 'maestria:subagent:started',\n SUBAGENT_COMPLETED: 'maestria:subagent:completed',\n SUBAGENT_FAILED: 'maestria:subagent:failed',\n} as const;\n\nconst ALLOWED_AGENTS = [\n 'adventurer',\n 'architect',\n 'builder',\n 'diagnose',\n 'planner',\n 'reviewer',\n 'writer',\n] as const;\ntype AllowedAgent = (typeof ALLOWED_AGENTS)[number];\n\n// The 6-field handoff contract\nconst HANDOFF_FIELDS = [\n 'Goal',\n 'Context',\n 'Requirements',\n 'Known problems',\n 'Success criteria',\n 'Next step',\n] as const;\n\n/** Terminal subagent statuses - agent will produce no more updates. */\nconst TERMINAL_STATUSES = new Set(['completed', 'steered', 'aborted', 'stopped', 'error']);\n\n/** Maximum time to wait for a subagent to complete, in milliseconds. */\nexport const POLL_TIMEOUT_MS = 60_000;\n\n/** Interval between subagent status checks, in milliseconds. */\nexport const POLL_INTERVAL_MS = 500;\n\n/** Maximum number of tasks allowed in parallel dispatch. */\nexport const MAX_PARALLEL_TASKS = 8;\n\nexport function validateHandoff(handoff: string): { valid: boolean; errors: string[] } {\n const errors: string[] = [];\n for (const field of HANDOFF_FIELDS) {\n const regex = new RegExp(`\\\\*\\\\*${field}:\\\\*\\\\*[\\\\s\\\\S]*?\\\\S`, 'i');\n if (!regex.test(handoff)) {\n errors.push(`Missing or empty field: \"${field}\"`);\n }\n }\n return { valid: errors.length === 0, errors };\n}\n\nexport function installSubagentTool(\n pi: ExtensionAPI,\n state: MaestriaState,\n cleanups?: Array<() => void>,\n): void {\n pi.registerTool({\n name: 'maestria_subagent',\n label: 'Maestria Subagent',\n description: 'Dispatch a task to a @maestria specialist subagent',\n promptSnippet:\n 'Delegate tasks to @maestria specialist subagents (adventurer, architect, builder, planner, diagnose, reviewer, writer)',\n promptGuidelines: [\n 'Use maestria_subagent when a task MUST be delegated to a specialist subagent rather than handled directly. Each specialist has focused capabilities: adventurer (recon), architect (design), builder (impl), planner (planning), diagnose (bugs), reviewer (QA), writer (docs).',\n ],\n prepareArguments(args: unknown) {\n return args;\n },\n parameters: Type.Object({\n agent: Type.Optional(Type.String({ description: 'Specialist agent name' })),\n task: Type.Optional(Type.String({ description: 'Task description for the subagent' })),\n tasks: Type.Optional(\n Type.Array(\n Type.Object({\n agent: Type.String(),\n task: Type.String(),\n }),\n { description: 'Array of task objects for parallel or chain dispatch' },\n ),\n ),\n mode: Type.Optional(\n Type.Union([Type.Literal('parallel'), Type.Literal('chain'), Type.Literal('single')]),\n ),\n }),\n async execute(\n _toolCallId: string,\n params: {\n agent?: string;\n task?: string;\n tasks?: Array<{ agent: string; task: string }>;\n mode?: 'parallel' | 'chain' | 'single';\n },\n signal: AbortSignal | undefined,\n onUpdate: ((result: { content: Array<{ type: string; text: string }> }) => void) | undefined,\n _ctx: ExtensionContext,\n ) {\n // Block subagent dispatch when in review mode\n if (state.reviewMode) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Subagent dispatch is not available during review mode. Use /restore-model to exit review mode first.',\n },\n ],\n };\n }\n\n // Determine dispatch mode (default to 'single' for backward compat)\n const mode = params.mode ?? 'single';\n\n // Validate parameters based on mode\n if (mode === 'single') {\n // Backward-compatible validation - must match original error messages exactly\n if (!ALLOWED_AGENTS.includes(params.agent as AllowedAgent)) {\n throw new Error(\n `Unknown agent: \"${params.agent}\". Allowed: ${ALLOWED_AGENTS.join(', ')}`,\n );\n }\n if (!params.task || !params.task.trim()) {\n throw new Error('Task description is required');\n }\n } else if (mode === 'parallel') {\n if (!params.tasks || params.tasks.length < 2) {\n throw new Error(`For parallel mode, tasks array is required with at least 2 items`);\n }\n if (params.tasks.length > MAX_PARALLEL_TASKS) {\n throw new Error(\n `For parallel mode, tasks array may have at most ${MAX_PARALLEL_TASKS} items (got ${params.tasks.length})`,\n );\n }\n for (const t of params.tasks) {\n if (!ALLOWED_AGENTS.includes(t.agent as AllowedAgent)) {\n throw new Error(`Unknown agent: \"${t.agent}\". Allowed: ${ALLOWED_AGENTS.join(', ')}`);\n }\n if (!t.task || !t.task.trim()) {\n throw new Error('Task description is required for all tasks');\n }\n }\n } else if (mode === 'chain') {\n if (!params.tasks || params.tasks.length < 2) {\n throw new Error('For chain mode, tasks array is required with at least 2 items');\n }\n for (const t of params.tasks) {\n if (!ALLOWED_AGENTS.includes(t.agent as AllowedAgent)) {\n throw new Error(`Unknown agent: \"${t.agent}\". Allowed: ${ALLOWED_AGENTS.join(', ')}`);\n }\n if (!t.task || !t.task.trim()) {\n throw new Error('Task description is required for all tasks');\n }\n }\n }\n\n // Attempt to dispatch via @gotgenes/pi-subagents; handle missing service\n const { getSubagentsService } = await import('@gotgenes/pi-subagents');\n const service = getSubagentsService();\n if (!service || typeof service.spawn !== 'function') {\n return {\n content: [\n {\n type: 'text' as const,\n text: [\n '## Subagent Dispatch Unavailable',\n '',\n 'The `@gotgenes/pi-subagents` extension is required for subagent dispatch but has not been loaded.',\n '',\n 'Install it as a Pi extension:',\n '',\n '```',\n 'pi install npm:@gotgenes/pi-subagents',\n '```',\n '',\n 'Then restart your Pi session.',\n ].join('\\n'),\n },\n ],\n };\n }\n\n try {\n // Helper: poll a single subagent until terminal or timeout\n async function pollSubagent(\n id: string,\n label: string,\n sendUpdates: boolean,\n ): Promise<{ status: string; result?: string; error?: string }> {\n const maxPolls = POLL_TIMEOUT_MS / POLL_INTERVAL_MS;\n let polls = 0;\n let record = service!.getRecord(id);\n while (record && !TERMINAL_STATUSES.has(record.status) && polls < maxPolls) {\n if (signal?.aborted) throw new Error('Maestria subagent call aborted');\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n record = service!.getRecord(id);\n polls++;\n if (sendUpdates) {\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `${label} running... (${Math.round((polls * POLL_INTERVAL_MS) / 1000)}s)`,\n },\n ],\n });\n }\n }\n if (record && !TERMINAL_STATUSES.has(record.status)) {\n throw new Error(`Subagent ${id} timed out after ${POLL_TIMEOUT_MS}ms`);\n }\n if (!record) {\n throw new Error(`Subagent ${id} was cleaned up before completion`);\n }\n return record;\n }\n\n // --- SINGLE MODE ---\n if (mode === 'single') {\n const agent = params.agent!;\n const task = params.task!;\n\n // Spawn in foreground - returns subagent ID synchronously\n const id = service.spawn(agent, task, {\n description: task.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n\n // Record handoff in state and persist (only after spawn succeeds)\n const updatedState = recordHandoff(state, 'orchestrator', agent, task);\n Object.assign(state, updatedState);\n pi.appendEntry('maestria_state', state);\n\n // Poll for completion\n const record = await pollSubagent(id, `Subagent ${agent}`, true);\n\n const resultText = record.result ?? record.error ?? 'No output.';\n\n return {\n content: [{ type: 'text' as const, text: resultText }],\n details: { subagentId: id },\n };\n }\n\n // --- PARALLEL MODE ---\n if (mode === 'parallel') {\n const taskList = params.tasks!;\n\n onUpdate?.({\n content: [\n { type: 'text' as const, text: `Spawning ${taskList.length} parallel subagents...` },\n ],\n });\n\n // Spawn all tasks\n const spawnedIds: string[] = [];\n for (const t of taskList) {\n const id = service.spawn(t.agent, t.task, {\n description: t.task.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n spawnedIds.push(id);\n\n // Record each handoff\n const updatedState = recordHandoff(state, 'orchestrator', t.agent, t.task);\n Object.assign(state, updatedState);\n }\n pi.appendEntry('maestria_state', state);\n\n // Poll all concurrently\n const records = await Promise.all(\n spawnedIds.map((id, i) =>\n pollSubagent(id, `${taskList[i].agent} (${i + 1}/${taskList.length})`, false),\n ),\n );\n\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `All ${taskList.length} parallel subagents completed.`,\n },\n ],\n });\n\n // Aggregate results\n const parts = [`## Parallel Results (${taskList.length} tasks)\\n`];\n for (let i = 0; i < taskList.length; i++) {\n const t = taskList[i];\n const rec = records[i];\n const resultText = rec.result ?? rec.error ?? 'No output.';\n parts.push(`### ${i + 1}: ${t.agent}`);\n parts.push(resultText);\n }\n\n return {\n content: [{ type: 'text' as const, text: parts.join('\\n\\n') }],\n details: { subagentIds: spawnedIds },\n };\n }\n\n // --- CHAIN MODE ---\n if (mode === 'chain') {\n const taskList = params.tasks!;\n let previousResult = '';\n\n for (let i = 0; i < taskList.length; i++) {\n const t = taskList[i];\n let taskText = t.task;\n\n // Substitute {previous} placeholder with previous result\n if (i > 0 && taskText.includes('{previous}')) {\n taskText = taskText.replace(/\\{previous\\}/g, previousResult);\n }\n\n const id = service.spawn(t.agent, taskText, {\n description: taskText.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n\n // Record handoff\n const updatedState = recordHandoff(state, 'orchestrator', t.agent, taskText);\n Object.assign(state, updatedState);\n pi.appendEntry('maestria_state', state);\n\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `Chain step ${i + 1}/${taskList.length}: ${t.agent} running...`,\n },\n ],\n });\n\n // Poll for completion\n const record = await pollSubagent(id, `Chain step ${i + 1}: ${t.agent}`, true);\n\n previousResult = record.result ?? record.error ?? 'No output.';\n\n if (i < taskList.length - 1) {\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `Chain step ${i + 1}/${taskList.length}: ${t.agent} completed. Moving to next step.`,\n },\n ],\n });\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: previousResult }],\n details: { subagentId: 'chain-completed' },\n };\n }\n\n // Should not reach here - all modes are handled above\n throw new Error('Unknown dispatch mode');\n } catch (err) {\n console.warn('[maestria] Subagent dispatch failed:', err);\n // Return handoff payload as structured text when dispatch fails\n const agentName = params.agent ?? params.tasks?.[0]?.agent ?? 'unknown';\n const taskDesc = params.task ?? params.tasks?.map((t) => t.task).join('; ') ?? 'unknown';\n const handoffInfo = [\n `## Subagent Handoff Required`,\n ``,\n `**From:** orchestrator`,\n `**To:** ${agentName}`,\n `**Task:** ${taskDesc}`,\n ``,\n `Subagent dispatch failed. Please delegate this work manually.`,\n ].join('\\n');\n\n return {\n content: [{ type: 'text' as const, text: handoffInfo }],\n };\n }\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any); // TypeBox inferred types don't match ToolDefinition exactly\n\n // Subscribe to subagent lifecycle events for accurate state tracking.\n // These subscriptions are set up once at extension init, not on every tool call.\n // pi.events is the shared EventBus - distinct from pi.on() lifecycle hooks.\n if (pi.events) {\n const unsubStarted = pi.events.on(SUBAGENT_EVENTS.STARTED, (data: unknown) => {\n const { id, type } = data as { id: string; type: string };\n state.subagentStatus[id] = { type, status: 'running', startedAt: Date.now() };\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_STARTED, {\n id,\n type,\n timestamp: Date.now(),\n });\n });\n\n const unsubCompleted = pi.events.on(SUBAGENT_EVENTS.COMPLETED, (data: unknown) => {\n const { id } = data as { id: string };\n const existing = state.subagentStatus[id];\n if (existing) {\n existing.status = 'completed';\n existing.completedAt = Date.now();\n }\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_COMPLETED, {\n id,\n type: existing?.type,\n timestamp: Date.now(),\n });\n });\n\n const unsubFailed = pi.events.on(SUBAGENT_EVENTS.FAILED, (data: unknown) => {\n const { id, status } = data as { id: string; status: string };\n const existing = state.subagentStatus[id];\n if (existing) {\n existing.status = status ?? 'error';\n existing.completedAt = Date.now();\n }\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_FAILED, {\n id,\n type: existing?.type,\n timestamp: Date.now(),\n });\n });\n\n const unsubSteered = pi.events.on(SUBAGENT_EVENTS.STEERED, (data: unknown) => {\n // Steering is informational - no status transition, but ensure\n // the agent is tracked as running if it wasn't already observed.\n const { id } = data as { id: string };\n if (!state.subagentStatus[id]) {\n state.subagentStatus[id] = { type: 'unknown', status: 'running', startedAt: Date.now() };\n }\n persistState(pi, state);\n });\n\n if (cleanups) {\n cleanups.push(unsubStarted, unsubCompleted, unsubFailed, unsubSteered);\n }\n }\n}\n","import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport {\n cycleToReviewModel,\n persistState,\n renderMaestriaSummary,\n restoreOriginalState,\n} from '@/state.js';\nimport { MAESTRIA_EVENTS } from '@/subagent.js';\n\n/**\n * Read-only tools that let a reviewer inspect code without making changes.\n *\n * - `read`, `grep`, `find`, `ls`, `glob` - all non-destructive.\n * - Excluded: `bash`, `edit`, `write` - these can modify the filesystem.\n *\n * `glob` is included for file pattern matching even though it's not a\n * built-in Pi tool - extensions may register it, and including it is a no-op\n * if absent.\n */\nconst READ_ONLY_TOOLS = ['read', 'grep', 'find', 'ls', 'glob'];\n\nexport function installCommands(pi: ExtensionAPI, state: MaestriaState): void {\n pi.registerCommand('maestria-status', {\n description: 'Show current maestria session state including handoff history',\n handler: async (_args: string, ctx) => {\n const summary = renderMaestriaSummary(state);\n if (!summary) {\n ctx.ui.notify('No active maestria state to report.');\n return;\n }\n ctx.ui.setEditorText(summary);\n },\n });\n\n pi.registerCommand('review', {\n description: 'Enter review mode. Blocks destructive tools, sets read-only toolset.',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /review <target> - describe what to review');\n return;\n }\n\n // 1. Save current model and tools for later restoration\n const currentModelId = ctx.model?.id ?? null;\n const currentTools = pi.getActiveTools();\n\n // 2. Update state: mark review mode, store originals\n const updatedState: MaestriaState = {\n ...state,\n reviewMode: true,\n originalModel: currentModelId,\n originalTools: currentTools,\n };\n Object.assign(state, updatedState);\n persistState(pi, state);\n\n // 3. Switch to review model if configured\n if (state.reviewModel) {\n const switched = await cycleToReviewModel(pi, ctx, state);\n if (switched) {\n ctx.ui.notify(`Review mode: switched to ${switched}`);\n pi.events?.emit(MAESTRIA_EVENTS.REVIEW_ACTIVATED, {\n originalModel: state.originalModel,\n reviewModel: switched,\n timestamp: Date.now(),\n });\n }\n }\n\n // 4. Restrict to read-only tools\n pi.setActiveTools(READ_ONLY_TOOLS);\n\n pi.sendUserMessage(\n [\n `[REVIEW: ${args}]`,\n '',\n `Review: ${args}. Use the reviewer prompt template.`,\n 'Read only, no edits, report findings.',\n ].join('\\n'),\n { deliverAs: 'steer' },\n );\n },\n });\n\n pi.registerCommand('restore-model', {\n description:\n 'Restore the original model and tools that were active before review mode was entered.',\n handler: async (_args: string, ctx) => {\n if (!state.reviewMode) {\n ctx.ui.notify('Not in review mode. Nothing to restore.');\n return;\n }\n const prevOriginalModel = state.originalModel;\n await restoreOriginalState(pi, ctx, state);\n persistState(pi, state);\n ctx.ui.notify('Restored original model and tools.');\n pi.events?.emit(MAESTRIA_EVENTS.REVIEW_DEACTIVATED, {\n originalModel: prevOriginalModel,\n timestamp: Date.now(),\n });\n },\n });\n\n pi.registerCommand('handoff', {\n description: 'Generate a structured handoff prompt for a new task context',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /handoff <goal> - describe the task context for handoff');\n return;\n }\n\n // Build a structured handoff document with 6 fields\n const goal = args.trim();\n const handoffPrompt = [\n '**Goal:** ' + goal,\n '',\n '**Context:**',\n '- Mode: ' + (state.mode ?? 'none'),\n '- Active task: ' + (state.activeTask || 'none'),\n '- Specialists delegated: ' +\n ((state.specialistsDelegated?.length ?? 0) > 0\n ? state.specialistsDelegated.join(', ')\n : 'none'),\n '- Recent handoffs: ' + (state.handoffHistory?.length ?? 0) + ' entries',\n '- Files modified: ' +\n ((state.filesModified?.length ?? 0) > 0 ? state.filesModified.join(', ') : 'none'),\n '',\n '**Requirements:**',\n '(fill in specific requirements)',\n '',\n '**Known problems:**',\n (state.blockers?.length ?? 0) > 0\n ? state.blockers.map((b: string) => '- ' + b).join('\\n')\n : '(no known problems documented)',\n '',\n '**Success criteria:**',\n '(fill in how to verify completion)',\n '',\n '**Next step:**',\n '(fill in what happens after this task)',\n '',\n '---',\n 'Complete the fields above before sending.',\n ].join('\\n');\n\n // Record in state\n state.handoffHistory = [\n { from: 'current', to: 'next', task: goal, timestamp: Date.now() },\n ...(state.handoffHistory ?? []),\n ].slice(0, 5);\n\n // Persist state\n persistState(pi, state);\n\n // Send as user message with steer delivery\n pi.sendUserMessage(handoffPrompt, { deliverAs: 'steer' });\n },\n });\n\n pi.registerCommand('review-model', {\n description: 'Set which model to use when entering review mode',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /review-model <model-id>');\n return;\n }\n const modelId = args.trim();\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m) => m.id === modelId);\n if (!model) {\n ctx.ui.notify(\n `Unknown model: \"${modelId}\". Available: ${models.map((m) => m.id).join(', ')}`,\n );\n return;\n }\n state.reviewModel = modelId;\n persistState(pi, state);\n ctx.ui.notify(`Review model set to: ${modelId}`);\n },\n });\n}\n","import {\n isToolCallEventType,\n type ExtensionAPI,\n type ToolCallEvent,\n type ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\n\nconst DANGEROUS_PATTERNS = [\n /rm\\s+-rf\\s+\\//,\n /dd\\s+if=/,\n />\\s*\\/dev\\/sd/,\n /chmod\\s+-R\\s+777\\s+\\//,\n /mkfs\\.\\w+/,\n /:(){ :\\|:& };:/, // fork bomb\n />\\s*\\/etc\\/(passwd|shadow|sudoers)/,\n /\\beval\\b/,\n /wget\\s+-O\\s*-\\s*\\|\\s*(bash|sh)/,\n /curl\\s+.*\\|\\s*(bash|sh)/,\n /crontab\\s+-r/,\n];\n\nexport function installToolInterceptors(pi: ExtensionAPI, state: MaestriaState): void {\n pi.on('tool_call', async (event: ToolCallEvent, ctx: ExtensionContext) => {\n if (!event || !event.toolName) return;\n\n // Block destructive tools in review mode\n if (state.reviewMode) {\n if (\n isToolCallEventType('edit', event) ||\n isToolCallEventType('write', event) ||\n isToolCallEventType('bash', event)\n ) {\n return {\n block: true,\n reason: 'Review mode is active. Report findings, do not edit.',\n };\n }\n }\n\n // Block dangerous bash patterns regardless of mode\n if (isToolCallEventType('bash', event)) {\n if (!event.input || typeof event.input !== 'object') return undefined;\n const command = event.input.command;\n if (command) {\n for (const pattern of DANGEROUS_PATTERNS) {\n if (pattern.test(command)) {\n if (ctx.hasUI) {\n const confirmed = await ctx.ui.confirm(\n 'Dangerous Pattern Detected',\n `This command matches a dangerous pattern:\\n${command}\\nProceed?`,\n );\n if (confirmed) return undefined;\n }\n return {\n block: true,\n reason: `Command matches dangerous pattern: ${pattern}`,\n };\n }\n }\n }\n }\n\n return undefined; // allow\n });\n}\n","import type { ExtensionAPI, SessionStartEvent } from '@earendil-works/pi-coding-agent';\nimport { createInitialState } from '@/state.js';\nimport { deploySpecialistAgents } from '@/agents.js';\nimport { installModeCommands } from '@/modes.js';\nimport { createModePromptHandler } from '@/rules.js';\nimport { installCompactionHandlers } from '@/compaction.js';\nimport { installSubagentTool } from '@/subagent.js';\nimport { installCommands } from '@/commands.js';\nimport { installToolInterceptors } from '@/tools.js';\n\nexport default function (pi: ExtensionAPI): void {\n const state = createInitialState();\n const cleanups: Array<() => void> = [];\n\n // Install mode commands: /fein, /sonar, /blitz\n installModeCommands(pi, state);\n\n // Inject mode prompt when a workflow mode is active\n const handleModePrompt = createModePromptHandler(state);\n\n pi.on('before_agent_start', (event, ctx) => {\n return handleModePrompt(event, ctx);\n });\n\n // Deploy specialist agent files for pi-subagents discovery\n pi.on('session_start', (_event: SessionStartEvent, ctx) => {\n deploySpecialistAgents(ctx);\n\n // Restore persisted state on session start (reload/resume/fork)\n if (!ctx.sessionManager?.getEntries) return;\n const entries = ctx.sessionManager.getEntries();\n // Walk from newest to oldest, find the last persisted maestria_state entry\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n if (entry.type === 'custom' && entry.customType === 'maestria_state') {\n const data = entry.data;\n if (data && typeof data === 'object') {\n Object.assign(state, data);\n }\n break;\n }\n }\n });\n\n // Install compaction preservation handlers\n installCompactionHandlers(pi, state);\n\n // Install orchestration hooks: subagent tool and commands\n installSubagentTool(pi, state, cleanups);\n installCommands(pi, state);\n\n // Cleanup subscriptions on shutdown\n pi.on('session_shutdown', () => {\n for (const cleanup of cleanups) cleanup();\n cleanups.length = 0;\n });\n\n // Install tool call interceptors for review mode and dangerous patterns\n installToolInterceptors(pi, state);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.mjs","names":["exitReviewMode","deploySpecialistAgents"],"sources":["../src/state/review.ts","../src/agents.ts","../src/modes.ts","../src/rules.ts","../src/compaction.ts","../src/subagent.ts","../src/commands.ts","../src/tools.ts","../src/extension.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionCommandContext } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from './types.js';\nimport { exitReviewMode } from './transforms.js';\n\nexport async function restoreOriginalState(\n pi: ExtensionAPI,\n ctx: ExtensionCommandContext,\n state: MaestriaState,\n): Promise<void> {\n const { state: clearedState, originalModel, originalTools } = exitReviewMode(state);\n\n if (originalTools && originalTools.length > 0) {\n pi.setActiveTools(originalTools);\n }\n\n if (originalModel) {\n try {\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m: { id: string }) => m.id === originalModel);\n if (model) {\n await pi.setModel(model);\n }\n } catch {\n // Best-effort: model restoration is non-critical\n }\n }\n\n Object.assign(state, clearedState);\n}\n\nexport async function cycleToReviewModel(\n pi: ExtensionAPI,\n ctx: ExtensionCommandContext,\n state: MaestriaState,\n): Promise<string | null> {\n const reviewModel = state.reviewModel;\n if (!reviewModel) {\n return null;\n }\n try {\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m) => m.id === reviewModel);\n if (model) {\n await pi.setModel(model);\n return reviewModel;\n } else {\n ctx.ui.notify(`Review model \"${reviewModel}\" not found in registry, staying on current.`);\n return null;\n }\n } catch {\n ctx.ui.notify(`Could not switch to review model \"${reviewModel}\", staying on current.`);\n return null;\n }\n}\n","import { homedir } from 'node:os';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { deploySpecialistAgents as deployAgents } from '@maestria/shared-pi/agent-deployment';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nconst AGENTS_SRC = join(__dirname, '..', 'agents');\n\n/**\n * Deploy bundled specialist agent .md files to the pi-subagents agents directory.\n *\n * pi-subagents discovers agent types from ~/.pi/agent/agents/*.md on every\n * registry.reload() call (which fires automatically on each tool invocation).\n * This function ensures the files are in place before the first subagent dispatch.\n *\n * Only creates files that don't already exist — never overwrites user-customized agents.\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function deploySpecialistAgents(_ctx?: unknown): void {\n deployAgents(AGENTS_SRC, join(homedir(), '.pi', 'agent', 'agents'));\n}\n","import { readFileSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { persistState, restoreOriginalState } from '@/state.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst COMMANDS_DIR = resolve(__dirname, '../agents/commands');\n\nfunction loadModePrompt(name: string): string {\n const content = readFileSync(resolve(COMMANDS_DIR, `${name}.md`), 'utf-8');\n const modeIdx = content.indexOf('## MODE:');\n if (modeIdx !== -1) {\n return content.slice(modeIdx).replace(/\\s+$/, '') + '\\n';\n }\n return content.replace(/\\s+$/, '') + '\\n';\n}\n\nexport const MODE_KEYWORDS = ['fein', 'sonar', 'blitz'] as const;\nexport type ModeKeyword = (typeof MODE_KEYWORDS)[number];\n\nconst MODE_MARKERS: Record<ModeKeyword, string> = {\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n blitz: '[MODE: blitz]',\n};\n\n/** Lazily cached mode prompts — loaded on first access, never throws. */\nconst _promptCache: Partial<Record<ModeKeyword, string>> = {};\n\nexport function getModePrompt(keyword: ModeKeyword): string {\n if (!(keyword in _promptCache)) {\n try {\n _promptCache[keyword] = loadModePrompt(keyword);\n } catch (e) {\n console.warn(`[maestria] Failed to load mode prompt \"${keyword}\":`, e);\n _promptCache[keyword] = '';\n }\n }\n return `${MODE_MARKERS[keyword]}\\n\\n${_promptCache[keyword]}`;\n}\n\nexport function installModeCommands(pi: ExtensionAPI, state: MaestriaState): void {\n for (const keyword of MODE_KEYWORDS) {\n pi.registerCommand(keyword, {\n description: `Set workflow mode to ${keyword}`,\n handler: async (args, ctx) => {\n if (state.reviewMode) {\n await restoreOriginalState(pi, ctx, state);\n }\n\n state.mode = keyword;\n persistState(pi, state);\n\n if (args.trim()) {\n const modeMessage = [\n getModePrompt(keyword),\n '',\n `Run the maestria default pipeline on: ${args}`,\n ].join('\\n');\n pi.sendUserMessage(modeMessage, { deliverAs: 'steer' });\n } else {\n ctx.ui.notify(`Mode set to ${keyword}. Describe what you'd like to work on.`);\n }\n },\n });\n }\n}\n","import type {\n BeforeAgentStartEvent,\n BeforeAgentStartEventResult,\n ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { getModePrompt } from '@/modes.js';\n\n/**\n * Creates a before_agent_start handler that injects workflow mode prompts.\n *\n * This is the only dynamic prompt injection needed from the extension.\n * Static behavioral content (orchestrator prompt + global rules) is\n * auto-injected by Pi's skill system via SKILL.md files registered in\n * the pi.skills manifest field - the standard Pi extension pattern.\n *\n * When no mode is active, the handler returns void (no modification),\n * letting Pi's built-in prompt assembly (skills + context files + tools)\n * stand as-is.\n */\nexport function createModePromptHandler(state: MaestriaState) {\n return (\n event: BeforeAgentStartEvent,\n _ctx: ExtensionContext,\n ): BeforeAgentStartEventResult | void => {\n if (!state.mode) return;\n\n const parts: string[] = [\n event.systemPrompt,\n '',\n getModePrompt(state.mode),\n '',\n `The user has set workflow mode to \"${state.mode}\". ` +\n 'Honor this mode throughout the session until changed via /command.',\n ];\n\n return { systemPrompt: parts.join('\\n') };\n };\n}\n","import type {\n ExtensionAPI,\n SessionBeforeCompactEvent,\n SessionBeforeTreeEvent,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { renderMaestriaSummary } from '@/state.js';\n\nexport function installCompactionHandlers(pi: ExtensionAPI, state: MaestriaState): void {\n pi.on('session_before_compact', (event: SessionBeforeCompactEvent) => {\n return {\n compaction: {\n summary: renderMaestriaSummary(state),\n details: { ...state },\n firstKeptEntryId: event.preparation.firstKeptEntryId,\n tokensBefore: event.preparation.tokensBefore,\n },\n };\n });\n\n pi.on('session_before_tree', (event: SessionBeforeTreeEvent) => {\n if (event.preparation.userWantsSummary) {\n return {\n summary: {\n summary: renderMaestriaSummary(state),\n },\n };\n }\n return undefined;\n });\n}\n","import { Type } from 'typebox';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { SUBAGENT_EVENTS } from '@gotgenes/pi-subagents';\nimport type { MaestriaState } from '@/state.js';\nimport { persistState, recordHandoff } from '@/state.js';\nimport {\n assertValidAgent,\n assertNonEmptyTask,\n MAESTRIA_EVENTS,\n} from '@maestria/shared-pi/subagent-utils';\n\n/** Terminal subagent statuses - agent will produce no more updates. */\nconst TERMINAL_STATUSES = new Set(['completed', 'steered', 'aborted', 'stopped', 'error']);\n\n/** Maximum time to wait for a subagent to complete, in milliseconds. */\nexport const POLL_TIMEOUT_MS = 60_000;\n\n/** Interval between subagent status checks, in milliseconds. */\nexport const POLL_INTERVAL_MS = 500;\n\n/** Maximum number of tasks allowed in parallel dispatch. */\nexport const MAX_PARALLEL_TASKS = 8;\n\n// ── Polling helper ───────────────────────────────────────────────\n\ntype SubagentRecord = { status: string; result?: string; error?: string };\n\nasync function pollSubagent(\n id: string,\n label: string,\n sendUpdates: boolean,\n service: { getRecord(id: string): SubagentRecord | undefined },\n signal: AbortSignal | undefined,\n onUpdate: ((result: { content: Array<{ type: string; text: string }> }) => void) | undefined,\n): Promise<SubagentRecord> {\n const maxPolls = POLL_TIMEOUT_MS / POLL_INTERVAL_MS;\n let polls = 0;\n let record = service.getRecord(id);\n while (record && !TERMINAL_STATUSES.has(record.status) && polls < maxPolls) {\n if (signal?.aborted) throw new Error('Maestria subagent call aborted');\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n record = service.getRecord(id);\n polls++;\n if (sendUpdates) {\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `${label} running... (${Math.round((polls * POLL_INTERVAL_MS) / 1000)}s)`,\n },\n ],\n });\n }\n }\n if (record && !TERMINAL_STATUSES.has(record.status)) {\n throw new Error(`Subagent ${id} timed out after ${POLL_TIMEOUT_MS}ms`);\n }\n if (!record) {\n throw new Error(`Subagent ${id} was cleaned up before completion`);\n }\n return record;\n}\n\n// ── Handoff recording helper ────────────────────────────────────\n\nfunction recordAndPersist(\n pi: ExtensionAPI,\n state: MaestriaState,\n agentName: string,\n taskText: string,\n): void {\n const updatedState = recordHandoff(state, 'orchestrator', agentName, taskText);\n Object.assign(state, updatedState);\n pi.appendEntry('maestria_state', state);\n}\n\nexport function installSubagentTool(\n pi: ExtensionAPI,\n state: MaestriaState,\n cleanups?: Array<() => void>,\n): void {\n pi.registerTool({\n name: 'maestria_subagent',\n label: 'Maestria Subagent',\n description: 'Dispatch a task to a @maestria specialist subagent',\n promptSnippet:\n 'Delegate tasks to @maestria specialist subagents (adventurer, architect, builder, planner, diagnose, reviewer, writer)',\n promptGuidelines: [\n 'Use maestria_subagent when a task MUST be delegated to a specialist subagent rather than handled directly. Each specialist has focused capabilities: adventurer (recon), architect (design), builder (impl), planner (planning), diagnose (bugs), reviewer (QA), writer (docs).',\n ],\n prepareArguments(args: unknown) {\n return args;\n },\n parameters: Type.Object({\n agent: Type.Optional(Type.String({ description: 'Specialist agent name' })),\n task: Type.Optional(Type.String({ description: 'Task description for the subagent' })),\n tasks: Type.Optional(\n Type.Array(\n Type.Object({\n agent: Type.String(),\n task: Type.String(),\n }),\n { description: 'Array of task objects for parallel or chain dispatch' },\n ),\n ),\n mode: Type.Optional(\n Type.Union([Type.Literal('parallel'), Type.Literal('chain'), Type.Literal('single')]),\n ),\n }),\n async execute(\n _toolCallId: string,\n params: {\n agent?: string;\n task?: string;\n tasks?: Array<{ agent: string; task: string }>;\n mode?: 'parallel' | 'chain' | 'single';\n },\n signal: AbortSignal | undefined,\n onUpdate: ((result: { content: Array<{ type: string; text: string }> }) => void) | undefined,\n _ctx: ExtensionContext,\n ) {\n // Block subagent dispatch when in review mode\n if (state.reviewMode) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Subagent dispatch is not available during review mode. Use /restore-model to exit review mode first.',\n },\n ],\n };\n }\n\n // Determine dispatch mode (default to 'single' for backward compat)\n const mode = params.mode ?? 'single';\n\n // Validate parameters based on mode\n if (mode === 'single') {\n assertValidAgent(params.agent!);\n assertNonEmptyTask(params.task, 'Task description is required');\n } else if (mode === 'parallel') {\n if (!params.tasks || params.tasks.length < 2) {\n throw new Error(`For parallel mode, tasks array is required with at least 2 items`);\n }\n if (params.tasks.length > MAX_PARALLEL_TASKS) {\n throw new Error(\n `For parallel mode, tasks array may have at most ${MAX_PARALLEL_TASKS} items (got ${params.tasks.length})`,\n );\n }\n for (const t of params.tasks) {\n assertValidAgent(t.agent);\n assertNonEmptyTask(t.task, 'Task description is required for all tasks');\n }\n } else if (mode === 'chain') {\n if (!params.tasks || params.tasks.length < 2) {\n throw new Error('For chain mode, tasks array is required with at least 2 items');\n }\n for (const t of params.tasks) {\n assertValidAgent(t.agent);\n assertNonEmptyTask(t.task, 'Task description is required for all tasks');\n }\n }\n\n // Attempt to dispatch via @gotgenes/pi-subagents; handle missing service\n const { getSubagentsService } = await import('@gotgenes/pi-subagents');\n const service = getSubagentsService();\n if (!service || typeof service.spawn !== 'function') {\n return {\n content: [\n {\n type: 'text' as const,\n text: [\n '## Subagent Dispatch Unavailable',\n '',\n 'The `@gotgenes/pi-subagents` extension is required for subagent dispatch but has not been loaded.',\n '',\n 'Install it as a Pi extension:',\n '',\n '```',\n 'pi install npm:@gotgenes/pi-subagents',\n '```',\n '',\n 'Then restart your Pi session.',\n ].join('\\n'),\n },\n ],\n };\n }\n\n try {\n // --- SINGLE MODE ---\n if (mode === 'single') {\n const agent = params.agent!;\n const task = params.task!;\n\n // Spawn in foreground - returns subagent ID synchronously\n const id = service.spawn(agent, task, {\n description: task.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n\n // Record handoff in state and persist (only after spawn succeeds)\n recordAndPersist(pi, state, agent, task);\n\n // Poll for completion\n const record = await pollSubagent(\n id,\n `Subagent ${agent}`,\n true,\n service,\n signal,\n onUpdate,\n );\n\n const resultText = record.result ?? record.error ?? 'No output.';\n\n return {\n content: [{ type: 'text' as const, text: resultText }],\n details: { subagentId: id },\n };\n }\n\n // --- PARALLEL MODE ---\n if (mode === 'parallel') {\n const taskList = params.tasks!;\n\n onUpdate?.({\n content: [\n { type: 'text' as const, text: `Spawning ${taskList.length} parallel subagents...` },\n ],\n });\n\n // Spawn all tasks\n const spawnedIds: string[] = [];\n for (const t of taskList) {\n const id = service.spawn(t.agent, t.task, {\n description: t.task.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n spawnedIds.push(id);\n\n // Record each handoff\n recordAndPersist(pi, state, t.agent, t.task);\n }\n\n // Poll all concurrently\n const records = await Promise.all(\n spawnedIds.map((id, i) =>\n pollSubagent(\n id,\n `${taskList[i].agent} (${i + 1}/${taskList.length})`,\n false,\n service,\n signal,\n onUpdate,\n ),\n ),\n );\n\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `All ${taskList.length} parallel subagents completed.`,\n },\n ],\n });\n\n // Aggregate results\n const parts = [`## Parallel Results (${taskList.length} tasks)\\n`];\n for (let i = 0; i < taskList.length; i++) {\n const t = taskList[i];\n const rec = records[i];\n const resultText = rec.result ?? rec.error ?? 'No output.';\n parts.push(`### ${i + 1}: ${t.agent}`);\n parts.push(resultText);\n }\n\n return {\n content: [{ type: 'text' as const, text: parts.join('\\n\\n') }],\n details: { subagentIds: spawnedIds },\n };\n }\n\n // --- CHAIN MODE ---\n if (mode === 'chain') {\n const taskList = params.tasks!;\n let previousResult = '';\n\n for (let i = 0; i < taskList.length; i++) {\n const t = taskList[i];\n let taskText = t.task;\n\n // Substitute {previous} placeholder with previous result\n if (i > 0 && taskText.includes('{previous}')) {\n taskText = taskText.replace(/\\{previous\\}/g, previousResult);\n }\n\n const id = service.spawn(t.agent, taskText, {\n description: taskText.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n\n // Record handoff\n recordAndPersist(pi, state, t.agent, taskText);\n\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `Chain step ${i + 1}/${taskList.length}: ${t.agent} running...`,\n },\n ],\n });\n\n // Poll for completion\n const record = await pollSubagent(\n id,\n `Chain step ${i + 1}: ${t.agent}`,\n true,\n service,\n signal,\n onUpdate,\n );\n\n previousResult = record.result ?? record.error ?? 'No output.';\n\n if (i < taskList.length - 1) {\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `Chain step ${i + 1}/${taskList.length}: ${t.agent} completed. Moving to next step.`,\n },\n ],\n });\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: previousResult }],\n details: { subagentId: 'chain-completed' },\n };\n }\n\n // Should not reach here - all modes are handled above\n throw new Error('Unknown dispatch mode');\n } catch (err) {\n console.warn('[maestria] Subagent dispatch failed:', err);\n // Return handoff payload as structured text when dispatch fails\n const agentName = params.agent ?? params.tasks?.[0]?.agent ?? 'unknown';\n const taskDesc = params.task ?? params.tasks?.map((t) => t.task).join('; ') ?? 'unknown';\n const handoffInfo = [\n `## Subagent Handoff Required`,\n ``,\n `**From:** orchestrator`,\n `**To:** ${agentName}`,\n `**Task:** ${taskDesc}`,\n ``,\n `Subagent dispatch failed. Please delegate this work manually.`,\n ].join('\\n');\n\n return {\n content: [{ type: 'text' as const, text: handoffInfo }],\n };\n }\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any); // TypeBox inferred types don't match ToolDefinition exactly\n\n // Subscribe to subagent lifecycle events for accurate state tracking.\n // These subscriptions are set up once at extension init, not on every tool call.\n // pi.events is the shared EventBus - distinct from pi.on() lifecycle hooks.\n if (pi.events) {\n const unsubStarted = pi.events.on(SUBAGENT_EVENTS.STARTED, (data: unknown) => {\n const { id, type } = data as { id: string; type: string };\n state.subagentStatus[id] = { type, status: 'running', startedAt: Date.now() };\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_STARTED, {\n id,\n type,\n timestamp: Date.now(),\n });\n });\n\n const unsubCompleted = pi.events.on(SUBAGENT_EVENTS.COMPLETED, (data: unknown) => {\n const { id } = data as { id: string };\n const existing = state.subagentStatus[id];\n if (existing) {\n existing.status = 'completed';\n existing.completedAt = Date.now();\n }\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_COMPLETED, {\n id,\n type: existing?.type,\n timestamp: Date.now(),\n });\n });\n\n const unsubFailed = pi.events.on(SUBAGENT_EVENTS.FAILED, (data: unknown) => {\n const { id, status } = data as { id: string; status: string };\n const existing = state.subagentStatus[id];\n if (existing) {\n existing.status = status ?? 'error';\n existing.completedAt = Date.now();\n }\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_FAILED, {\n id,\n type: existing?.type,\n timestamp: Date.now(),\n });\n });\n\n const unsubSteered = pi.events.on(SUBAGENT_EVENTS.STEERED, (data: unknown) => {\n // Steering is informational - no status transition, but ensure\n // the agent is tracked as running if it wasn't already observed.\n const { id } = data as { id: string };\n if (!state.subagentStatus[id]) {\n state.subagentStatus[id] = { type: 'unknown', status: 'running', startedAt: Date.now() };\n }\n persistState(pi, state);\n });\n\n if (cleanups) {\n cleanups.push(unsubStarted, unsubCompleted, unsubFailed, unsubSteered);\n }\n }\n}\n","import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport {\n cycleToReviewModel,\n persistState,\n renderMaestriaSummary,\n restoreOriginalState,\n} from '@/state.js';\nimport { MAESTRIA_EVENTS } from '@maestria/shared-pi/subagent-utils';\n\n/**\n * Read-only tools that let a reviewer inspect code without making changes.\n *\n * - `read`, `grep`, `find`, `ls`, `glob` - all non-destructive.\n * - Excluded: `bash`, `edit`, `write` - these can modify the filesystem.\n *\n * `glob` is included for file pattern matching even though it's not a\n * built-in Pi tool - extensions may register it, and including it is a no-op\n * if absent.\n */\nconst READ_ONLY_TOOLS = ['read', 'grep', 'find', 'ls', 'glob'];\n\nexport function installCommands(pi: ExtensionAPI, state: MaestriaState): void {\n pi.registerCommand('maestria-status', {\n description: 'Show current maestria session state including handoff history',\n handler: async (_args: string, ctx) => {\n const summary = renderMaestriaSummary(state);\n if (!summary) {\n ctx.ui.notify('No active maestria state to report.');\n return;\n }\n ctx.ui.setEditorText(summary);\n },\n });\n\n pi.registerCommand('review', {\n description: 'Enter review mode. Blocks destructive tools, sets read-only toolset.',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /review <target> - describe what to review');\n return;\n }\n\n // 1. Save current model and tools for later restoration\n const currentModelId = ctx.model?.id ?? null;\n const currentTools = pi.getActiveTools();\n\n // 2. Update state: mark review mode, store originals\n const updatedState: MaestriaState = {\n ...state,\n reviewMode: true,\n originalModel: currentModelId,\n originalTools: currentTools,\n };\n Object.assign(state, updatedState);\n persistState(pi, state);\n\n // 3. Switch to review model if configured\n if (state.reviewModel) {\n const switched = await cycleToReviewModel(pi, ctx, state);\n if (switched) {\n ctx.ui.notify(`Review mode: switched to ${switched}`);\n pi.events?.emit(MAESTRIA_EVENTS.REVIEW_ACTIVATED, {\n originalModel: state.originalModel,\n reviewModel: switched,\n timestamp: Date.now(),\n });\n }\n }\n\n // 4. Restrict to read-only tools\n pi.setActiveTools(READ_ONLY_TOOLS);\n\n pi.sendUserMessage(\n [\n `[REVIEW: ${args}]`,\n '',\n `Review: ${args}. Use the reviewer prompt template.`,\n 'Read only, no edits, report findings.',\n ].join('\\n'),\n { deliverAs: 'steer' },\n );\n },\n });\n\n pi.registerCommand('restore-model', {\n description:\n 'Restore the original model and tools that were active before review mode was entered.',\n handler: async (_args: string, ctx) => {\n if (!state.reviewMode) {\n ctx.ui.notify('Not in review mode. Nothing to restore.');\n return;\n }\n const prevOriginalModel = state.originalModel;\n await restoreOriginalState(pi, ctx, state);\n persistState(pi, state);\n ctx.ui.notify('Restored original model and tools.');\n pi.events?.emit(MAESTRIA_EVENTS.REVIEW_DEACTIVATED, {\n originalModel: prevOriginalModel,\n timestamp: Date.now(),\n });\n },\n });\n\n pi.registerCommand('handoff', {\n description: 'Generate a structured handoff prompt for a new task context',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /handoff <goal> - describe the task context for handoff');\n return;\n }\n\n // Build a structured handoff document with 6 fields\n const goal = args.trim();\n const handoffPrompt = [\n '**Goal:** ' + goal,\n '',\n '**Context:**',\n '- Mode: ' + (state.mode ?? 'none'),\n '- Active task: ' + (state.activeTask || 'none'),\n '- Specialists delegated: ' +\n ((state.specialistsDelegated?.length ?? 0) > 0\n ? state.specialistsDelegated.join(', ')\n : 'none'),\n '- Recent handoffs: ' + (state.handoffHistory?.length ?? 0) + ' entries',\n '- Files modified: ' +\n ((state.filesModified?.length ?? 0) > 0 ? state.filesModified.join(', ') : 'none'),\n '',\n '**Requirements:**',\n '(fill in specific requirements)',\n '',\n '**Known problems:**',\n (state.blockers?.length ?? 0) > 0\n ? state.blockers.map((b: string) => '- ' + b).join('\\n')\n : '(no known problems documented)',\n '',\n '**Success criteria:**',\n '(fill in how to verify completion)',\n '',\n '**Next step:**',\n '(fill in what happens after this task)',\n '',\n '---',\n 'Complete the fields above before sending.',\n ].join('\\n');\n\n // Record in state\n state.handoffHistory = [\n { from: 'current', to: 'next', task: goal, timestamp: Date.now() },\n ...(state.handoffHistory ?? []),\n ].slice(0, 5);\n\n // Persist state\n persistState(pi, state);\n\n // Send as user message with steer delivery\n pi.sendUserMessage(handoffPrompt, { deliverAs: 'steer' });\n },\n });\n\n pi.registerCommand('review-model', {\n description: 'Set which model to use when entering review mode',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /review-model <model-id>');\n return;\n }\n const modelId = args.trim();\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m) => m.id === modelId);\n if (!model) {\n ctx.ui.notify(\n `Unknown model: \"${modelId}\". Available: ${models.map((m) => m.id).join(', ')}`,\n );\n return;\n }\n state.reviewModel = modelId;\n persistState(pi, state);\n ctx.ui.notify(`Review model set to: ${modelId}`);\n },\n });\n}\n","import {\n isToolCallEventType,\n type ExtensionAPI,\n type ToolCallEvent,\n type ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\n\nconst DANGEROUS_PATTERNS = [\n /rm\\s+-rf\\s+\\//,\n /dd\\s+if=/,\n />\\s*\\/dev\\/sd/,\n /chmod\\s+-R\\s+777\\s+\\//,\n /mkfs\\.\\w+/,\n /:(){ :\\|:& };:/, // fork bomb\n />\\s*\\/etc\\/(passwd|shadow|sudoers)/,\n /\\beval\\b/,\n /wget\\s+-O\\s*-\\s*\\|\\s*(bash|sh)/,\n /curl\\s+.*\\|\\s*(bash|sh)/,\n /crontab\\s+-r/,\n];\n\nexport function installToolInterceptors(pi: ExtensionAPI, state: MaestriaState): void {\n pi.on('tool_call', async (event: ToolCallEvent, ctx: ExtensionContext) => {\n if (!event || !event.toolName) return;\n\n // Block destructive tools in review mode\n if (state.reviewMode) {\n if (\n isToolCallEventType('edit', event) ||\n isToolCallEventType('write', event) ||\n isToolCallEventType('bash', event)\n ) {\n return {\n block: true,\n reason: 'Review mode is active. Report findings, do not edit.',\n };\n }\n }\n\n // Block dangerous bash patterns regardless of mode\n if (isToolCallEventType('bash', event)) {\n if (!event.input || typeof event.input !== 'object') return undefined;\n const command = event.input.command;\n if (command) {\n for (const pattern of DANGEROUS_PATTERNS) {\n if (pattern.test(command)) {\n if (ctx.hasUI) {\n const confirmed = await ctx.ui.confirm(\n 'Dangerous Pattern Detected',\n `This command matches a dangerous pattern:\\n${command}\\nProceed?`,\n );\n if (confirmed) return undefined;\n }\n return {\n block: true,\n reason: `Command matches dangerous pattern: ${pattern}`,\n };\n }\n }\n }\n }\n\n return undefined; // allow\n });\n}\n","import type { ExtensionAPI, SessionStartEvent } from '@earendil-works/pi-coding-agent';\nimport { createInitialState } from '@/state.js';\nimport { deploySpecialistAgents } from '@/agents.js';\nimport { installModeCommands } from '@/modes.js';\nimport { createModePromptHandler } from '@/rules.js';\nimport { installCompactionHandlers } from '@/compaction.js';\nimport { installSubagentTool } from '@/subagent.js';\nimport { installCommands } from '@/commands.js';\nimport { installToolInterceptors } from '@/tools.js';\n\nexport default function (pi: ExtensionAPI): void {\n const state = createInitialState();\n const cleanups: Array<() => void> = [];\n\n // Install mode commands: /fein, /sonar, /blitz\n installModeCommands(pi, state);\n\n // Inject mode prompt when a workflow mode is active\n const handleModePrompt = createModePromptHandler(state);\n\n pi.on('before_agent_start', (event, ctx) => {\n return handleModePrompt(event, ctx);\n });\n\n // Deploy specialist agent files for pi-subagents discovery\n pi.on('session_start', (_event: SessionStartEvent, ctx) => {\n deploySpecialistAgents(ctx);\n\n // Restore persisted state on session start (reload/resume/fork)\n if (!ctx.sessionManager?.getEntries) return;\n const entries = ctx.sessionManager.getEntries();\n // Walk from newest to oldest, find the last persisted maestria_state entry\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n if (entry.type === 'custom' && entry.customType === 'maestria_state') {\n const data = entry.data;\n if (data && typeof data === 'object') {\n Object.assign(state, data);\n }\n break;\n }\n }\n });\n\n // Install compaction preservation handlers\n installCompactionHandlers(pi, state);\n\n // Install orchestration hooks: subagent tool and commands\n installSubagentTool(pi, state, cleanups);\n installCommands(pi, state);\n\n // Cleanup subscriptions on shutdown\n pi.on('session_shutdown', () => {\n for (const cleanup of cleanups) cleanup();\n cleanups.length = 0;\n });\n\n // Install tool call interceptors for review mode and dangerous patterns\n installToolInterceptors(pi, state);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maestria/pi",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
4
4
  "description": "Maestria extension for the Pi coding agent",
5
5
  "keywords": [
6
6
  "agent-orchestration",
@@ -26,7 +26,9 @@
26
26
  "access": "public",
27
27
  "provenance": true
28
28
  },
29
- "dependencies": {},
29
+ "dependencies": {
30
+ "@maestria/shared-pi": "0.2.0"
31
+ },
30
32
  "devDependencies": {
31
33
  "@types/node": "^26",
32
34
  "typescript": "^6.0.3",
@@ -18,29 +18,29 @@ description: >-
18
18
 
19
19
  `!!!` = non-negotiable. Rules without `!!!` are guidance.
20
20
 
21
- - **!!! Don't assume** - verify against actual code and docs. Guesses lead to bugs.
21
+ - **!!! Don't assume** - verify against actual code and documentation. Guesses introduce bugs.
22
22
  - **!!! Read the docs first** - before writing code that touches unfamiliar tools, APIs, or migration paths, consult official documentation. Don't guess at API changes. This rule is scar tissue from repeated failures; treat it seriously.
23
23
  - **!!! Don't anthropomorphize effort** - You operate at machine scale. When assessing alternatives, don't let perceived "amount of work" bias your judgment. What feels like a lot of work to a human is routine iteration for you. Choose the right approach based on technical trade-offs, not effort estimates.
24
- - **!!! Never leak internal context into public output.** Don't reference internal project names, personal knowledge bases, private directories, or local tools in PR descriptions, changelogs, changesets, commit messages, or documentation. Describe what was done, not where the inspiration came from. Public output must stand on its own without exposing private context.
24
+ - **!!! Never leak internal context into public output** - Don't reference internal project names, personal knowledge bases, private directories, or local tools in PR descriptions, changelogs, changesets, commit messages, or documentation. Describe what was done, not where the inspiration came from. Public output must stand on its own without exposing private context.
25
25
  - **!!! Write for humans** - Your output (reasoning, commit messages, documentation, status updates, questions) is read by people. Never use em dashes. Use standard hyphens (-) instead. Avoid inflated language and promotional phrasing. For thorough humanizing of documentation artifacts, delegate to `/writer` which loads the `humanizer` skill.
26
26
  - **!!! Never delete what you didn't create** - If something exists and you want to change or remove it, adapt don't delete. Existing code is there for a reason, even if that reason isn't obvious. Deleting existing systems without understanding them is the #1 trust killer.
27
- - **Workflow modes** - keywords `fein` (full pipeline), `sonar` (research only), `blitz` (fast impl) activate per-turn workflow overrides. See the orchestrator prompt for details.
27
+ - **Workflow modes** - keywords `fein` (full pipeline), `sonar` (research only), `blitz` (fast implementation) activate per-turn workflow overrides. See the orchestrator prompt for details.
28
28
  - **Project `.maestria/`** - `.maestria/workflow.md` and `.maestria/rules.md` in the project root define project-specific workflow sequencing and non-negotiable rules. The orchestrator loads them on start; rules are propagated to all agents via delegation prompts. See the orchestrator prompt for details.
29
29
 
30
30
  ### Tool Routing
31
31
 
32
- - **External repos `opensrc`; pages `webfetch`.** For a GitHub/GitLab/BitBucket repo or any multi-file code reference, run `opensrc path <owner/repo>` (e.g. `opensrc path facebook/react`) - it clones to a global cache and prints a path that `read`/`glob`/`grep` can use directly. Use `--cwd` to resolve versions from the current project. For a single file, page, or known URL, `webfetch` is fine. Don't fetch an entire repo one file at a time - clone once, read locally.
33
- - **`webfetch` may hang - don't block on it.** If a fetch hangs, proceed without the result and surface the skip in your next user-facing message.
34
- - **`webfetch` when you know the URL; `websearch` when you need to find something.** `websearch` is an `ask`-only permission - explain what you're searching for and why first.
35
- - **Local files - read directly** with `read`, `glob`, or `grep` (or `lsp`/code-intelligence tools when available). Don't `webfetch` a local file or a file in a checked-out repo. Prefer code intelligence tools over grep/read loops when available.
36
- - **CLI references - local first.** Run `<cmd> --help` or load the relevant `skill` instead of fetching docs. Local tools are faster and more reliable.
32
+ - **External repos -> repo cloning tool** - for GitHub/GitLab/BitBucket repos or any multi-file code reference, clone to a local cache and read with local tools. Never fetch an entire repo one file at a time.
33
+ - **URL fetching may hang** - don't block on it. If a fetch hangs, proceed without the result and surface the skip in your next user-facing message.
34
+ - **URL fetch vs web search** - use a URL fetching tool when you know the URL; use web search when you need to find something. Explain what you're searching for and why before searching.
35
+ - **Local files - read directly** with file reading tools (read, glob, grep, or code-intelligence tools). Never fetch local files via URL.
36
+ - **CLI references - local first.** Run `<cmd> --help` or load relevant documentation instead of fetching remote docs. Local tools are faster and more reliable.
37
37
 
38
38
  ## Principles
39
39
 
40
40
  - **Start from first principles** - before adopting an existing pattern or solution, verify it actually matches the fundamental problem. Prior art is a reference, not a constraint.
41
41
  - **Prefer existing solutions** - before building something yourself, verify no well-maintained open-source solution (package registries, GitHub, official libraries, plugins) already covers the need.
42
- - **Surface incidental findings** - If during a task you discover something materially relevant to the project that falls outside the brief, flag it after completing the primary deliverable. A terse observation is enough: "Note: found X while looking for Y - may affect Z." The primary task is still the contract. Exception: active security, data, or production risk - flag immediately.
43
- - **Decompose to first principles when stuck** - If a problem resists your current approach, don't try harder - decompose it into statements you can verify against source code, documentation, or physics. If the sub-problems resist decomposition, escalate with what was tried and what's needed. Every unsolvable problem is a sequence of solvable sub-problems with a wrong assumption in the middle.
42
+ - **Surface incidental findings** - If during a task you discover something materially relevant to the project that falls outside the brief, flag it after completing the primary deliverable. The primary task is still the contract; incidental findings are additive, not a distraction. Exception: flag active security/production risks immediately.
43
+ - **Decompose to first principles when stuck** - If a problem resists your current approach, don't try harder. Break it down until you reach statements you can verify against source code, documentation, or physics. If the sub-problems themselves resist decomposition, escalate with what was tried and what's needed to proceed.
44
44
 
45
45
  ## Handoff Contract
46
46
 
@@ -48,13 +48,14 @@ These rules govern every specialist's output back to the orchestrator:
48
48
 
49
49
  - **!!! Maker/checker split** - your work is reviewed by `/reviewer` before it lands. The model that produced the work is too nice grading its own homework. Produce the artifact; do not QA it.
50
50
  - **!!! Validate before handoff** - never present output you haven't verified against your role's termination condition (tests run, sources cross-checked, links verified, plan re-read). Re-read your own output before reporting back.
51
- - **Ambiguity assumptions, not questions** - exhaust available data first (codebase patterns, ADRs, `.maestria/rules.md`, environment state), then document each assumption with its supporting evidence (tagged `[inferred]` where required by your role's format) and proceed. The reviewer validates assumptions.
51
+ - **Ambiguity -> assumptions, not questions** - exhaust available data first (codebase patterns, ADRs, `.maestria/rules.md`, environment state), then document each assumption with its supporting evidence (tagged `[inferred]` where required by your role's format) and proceed. The reviewer validates assumptions.
52
52
  - **Iteration limits** - define a verifiable termination condition for your task and stop when met. Max 3 attempts at the same failing approach before escalating.
53
53
  - **Escalation format:** "Tried X, Y, Z. Blocked by [cause]. Need [input] to proceed."
54
+ - **Before reporting done:** verify termination condition met (cite evidence), assumptions tagged `[verified]`/`[inferred]`, escalation format used if blocked.
54
55
 
55
56
  ## Delegation
56
57
 
57
- When delegating work via `maestria_subagent()`, use only the 7 specialists below. **Never delegate to `explore` or `general`** - they are built-in agents, not part of the pipeline.
58
+ When delegating work, use only the 7 specialists below. **Never delegate to platform-native built-in agents** - they are built-in, not part of the pipeline.
58
59
 
59
60
  | Agent | Role | When to Delegate |
60
61
  | --- | --- | --- |
@@ -73,6 +74,20 @@ When delegating work via `maestria_subagent()`, use only the 7 specialists below
73
74
  - **Context pruning** - remove irrelevant context when no longer needed.
74
75
  - **Completion promises** - define success criteria before starting work. "This task is complete when [verifiable conditions]."
75
76
 
77
+ ### Parallelization
78
+
79
+ Parallelize independent tasks across **different scopes** only. Same scope requires single-writer or sequential execution.
80
+
81
+ | Agent | Parallel OK | Never parallelize |
82
+ | ------------- | ----------------------- | ------------------------------------- |
83
+ | `/builder` | Different files | Overlapping files (merge conflicts) |
84
+ | `/reviewer` | Different PRs/changes | Same PR (sequential after `/builder`) |
85
+ | `/adventurer` | Different modules/areas | Same module (overlapping reports) |
86
+ | `/architect` | Different decisions | Same decision (ADR is single-writer) |
87
+ | `/planner` | Different features | Same feature (plan is single-writer) |
88
+ | `/writer` | Different documents | Same document (doc is single-writer) |
89
+ | `/diagnose` | Different bugs | Same bug or root-cause cluster |
90
+
76
91
  ## Commit Policy
77
92
 
78
93
  - **Only the orchestrator authorizes commits.** Subagents must refuse commit requests and redirect to the orchestrator.