@maestria/pi 0.6.0 → 0.6.2

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.
package/README.md CHANGED
@@ -8,7 +8,7 @@ A [Pi coding agent](https://pi.software/) extension that brings Maestria's struc
8
8
  - **3 Workflow Modes** - `fein` (full pipeline), `sonar` (research only), `blitz` (fast implementation)
9
9
  - **Skill-Based Prompt Injection** - Behavioral instructions injected via Pi's native skill mechanism, not custom event hooks. Skills are auto-discovered from the package manifest and loaded into the system prompt by Pi's resource loader - the standard pattern used by all major Pi extensions.
10
10
  - **Compaction Preservation** - Session state survives compaction with structured summaries
11
- - **Subagent Dispatch** - Delegation via `@gotgenes/pi-subagents` with 6-field handoff validation
11
+ - **Subagent Dispatch** - Delegation via `@gotgenes/pi-subagents` with 7-field handoff validation
12
12
  - **Maker/Checker Split** - Review mode blocks destructive tools. Dangerous bash patterns flagged.
13
13
 
14
14
  ## Installation
@@ -5,4 +5,6 @@
5
5
 
6
6
  ## MODE: blitz (Fast Implementation)
7
7
 
8
- Speed mode: skip reconnaissance and design gates. Go directly to /builder for implementation. Only use /adventurer if the codebase context is genuinely unknown (not as a default step). Skip /reviewer unless the user explicitly requests review.
8
+ Explicit low-risk/direct bypass: skip reconnaissance and design gates for familiar, low-risk work. Go directly to /builder for implementation (or direct execution where the host supports it). Only use /adventurer if the codebase context is genuinely unknown (not as a default step). Skip /reviewer unless the user explicitly requests review.
9
+
10
+ Safety floors still apply. Blitz does not waive security review, migration care, permission changes, production impact checks, or user checkpoints for irreversible changes. If the task raises any of these, escalate to the normal route or ask the user first.
@@ -5,4 +5,4 @@
5
5
 
6
6
  ## MODE: fein (Full Pipeline)
7
7
 
8
- Default role-based pipeline: thinker (recon/design/plan) -> worker (implementation) -> verifier (review). Verifier acceptance terminates the pipeline for that unit of work. Roles and order may adapt to task needs - this is the default, not a fixed requirement. Do NOT skip any phase unless the user explicitly overrides in the same turn.
8
+ Explicit selection of the `full` route. Default role-based pipeline: thinker (recon/design/plan) -> worker (implementation) -> verifier (review). Verifier acceptance terminates the pipeline for that unit of work. Roles and order may adapt to task needs - this is the default, not a fixed requirement. Do NOT skip any phase unless the user explicitly overrides in the same turn.
@@ -1,14 +1,14 @@
1
1
  import{homedir as e}from"node:os";import{dirname as t,join as n,resolve as r}from"node:path";import{fileURLToPath as i}from"node:url";import{existsSync as a,mkdirSync as o,readFileSync as s,writeFileSync as c}from"node:fs";import{Type as l}from"typebox";import{SUBAGENT_EVENTS as u}from"@gotgenes/pi-subagents";import{isToolCallEventType as d}from"@earendil-works/pi-coding-agent";function f(){return{mode:null,activeTask:``,completionPromise:``,specialistsDelegated:[],blockers:[],filesModified:[],filesRead:[],handoffHistory:[],reviewMode:!1,originalModel:null,originalTools:null,subagentStatus:{},reviewModel:null}}function p(e,t,n,r){let i=[{from:t,to:n,task:r,timestamp:Date.now()},...e.handoffHistory].slice(0,5);return{...e,handoffHistory:i}}function m(e){return{state:{...e,reviewMode:!1,originalModel:null,originalTools:null},originalModel:e.originalModel,originalTools:e.originalTools}}function h(e,t){e.appendEntry(`maestria_state`,{...t})}function g(e){let t=[];if(e.mode&&t.push(`**Mode:** ${e.mode.toUpperCase()}`),e.reviewModel&&t.push(`**Review Model:** ${e.reviewModel}`),e.activeTask&&t.push(`**Goal:** ${e.activeTask}`),e.completionPromise&&t.push(`**Completion Promise:** ${e.completionPromise}`),e.specialistsDelegated.length>0&&t.push(`**Specialists Delegated:** ${e.specialistsDelegated.join(`, `)}`),e.blockers.length>0){t.push(`**Blockers:**`);for(let n of e.blockers)t.push(`- ${n}`)}let n=[];if(e.filesModified.length>0&&n.push(`**Modified:** ${e.filesModified.join(`, `)}`),e.filesRead.length>0&&n.push(`**Read:** ${e.filesRead.join(`, `)}`),n.length>0&&t.push(`**Files:** ${n.join(`; `)}`),e.handoffHistory.length>0){t.push(`**Recent Handoffs:**`);for(let n of e.handoffHistory)t.push(`- ${n.from} → ${n.to}: ${n.task}`)}return t.join(`
2
2
 
3
- `)}async function _(e,t,n){let{state:r,originalModel:i,originalTools:a}=m(n);if(a&&a.length>0&&e.setActiveTools(a),i)try{let n=t.modelRegistry.getAll().find(e=>e.id===i);n&&await e.setModel(n)}catch{}Object.assign(n,r)}async function v(e,t,n){let r=n.reviewModel;if(!r)return null;try{let n=t.modelRegistry.getAll().find(e=>e.id===r);return n?(await e.setModel(n),r):(t.ui.notify(`Review model "${r}" not found in registry, staying on current.`),null)}catch{return t.ui.notify(`Could not switch to review model "${r}", staying on current.`),null}}const y=[`adventurer`,`architect`,`builder`,`diagnose`,`planner`,`reviewer`,`writer`];function b(e,t){if(!a(e))return console.warn(`[maestria] Agents source directory not found:`,e),0;try{o(t,{recursive:!0})}catch{return console.warn(`[maestria] Could not create agents directory:`,t),0}let r=0;for(let i of y){let o=n(e,`${i}.md`),l=n(t,`${i}.md`);if(!a(o)){console.warn(`[maestria] Agent source not found: ${i}.md`);continue}if(!a(l))try{c(l,s(o,`utf-8`),`utf-8`),r++}catch(e){console.warn(`[maestria] Failed to deploy agent ${i}:`,e)}}return r>0&&console.log(`[maestria] Deployed ${r} specialist agents to ${t}`),r}const x=n(t(i(import.meta.url)),`..`,`agents`);function S(t){b(x,n(e(),`.pi`,`agent`,`agents`))}const C=[`fein`,`sonar`,`blitz`],w={fein:`[MODE: fein]`,sonar:`[MODE: sonar]`,blitz:`[MODE: blitz]`},T={};function E(e,t){let n=s(r(t,`${e}.md`),`utf-8`),i=n.indexOf(`## MODE:`);return i===-1?n.replace(/\s+$/,``)+`
3
+ `)}async function _(e,t,n){let{state:r,originalModel:i,originalTools:a}=m(n);if(a&&a.length>0&&e.setActiveTools(a),i)try{let n=t.modelRegistry.getAll().find(e=>e.id===i);n&&await e.setModel(n)}catch{}Object.assign(n,r)}async function v(e,t,n){let r=n.reviewModel;if(!r)return null;try{let n=t.modelRegistry.getAll().find(e=>e.id===r);return n?(await e.setModel(n),r):(t.ui.notify(`Review model "${r}" not found in registry, staying on current.`),null)}catch{return t.ui.notify(`Could not switch to review model "${r}", staying on current.`),null}}const y={REVIEW_ACTIVATED:`maestria:review:activated`,REVIEW_DEACTIVATED:`maestria:review:deactivated`,SUBAGENT_STARTED:`maestria:subagent:started`,SUBAGENT_COMPLETED:`maestria:subagent:completed`,SUBAGENT_FAILED:`maestria:subagent:failed`},b=[`adventurer`,`architect`,`builder`,`diagnose`,`planner`,`reviewer`,`writer`];function x(e){if(!b.includes(e))throw Error(`Unknown agent: "${e}". Allowed: ${b.join(`, `)}`)}function S(e,t){if(!e||!e.trim())throw Error(t)}function C(e,t){if(!a(e))return console.warn(`[maestria] Agents source directory not found:`,e),0;try{o(t,{recursive:!0})}catch{return console.warn(`[maestria] Could not create agents directory:`,t),0}let r=0;for(let i of b){let o=n(e,`${i}.md`),l=n(t,`${i}.md`);if(!a(o)){console.warn(`[maestria] Agent source not found: ${i}.md`);continue}if(!a(l))try{c(l,s(o,`utf-8`),`utf-8`),r++}catch(e){console.warn(`[maestria] Failed to deploy agent ${i}:`,e)}}return r>0&&console.log(`[maestria] Deployed ${r} specialist agents to ${t}`),r}const w=n(t(i(import.meta.url)),`..`,`agents`);function T(t){C(w,n(e(),`.pi`,`agent`,`agents`))}const E=[`fein`,`sonar`,`blitz`],D={fein:`[MODE: fein]`,sonar:`[MODE: sonar]`,blitz:`[MODE: blitz]`},O={};function k(e,t){let n=s(r(t,`${e}.md`),`utf-8`),i=n.indexOf(`## MODE:`);return i===-1?n.replace(/\s+$/,``)+`
4
4
  `:n.slice(i).replace(/\s+$/,``)+`
5
- `}function D(e,t){if(!(e in T))try{T[e]=E(e,t)}catch(t){console.warn(`[maestria] Failed to load mode prompt "${e}":`,t),T[e]=``}return`${w[e]}\n\n${T[e]}`}function O(e,t){if(!e)return null;for(let n of C){let r=RegExp(`\\b${n}\\b`,`i`);if(r.test(e))return{keyword:n,strippedText:e.replace(r,``).trim(),prompt:D(n,t)}}return null}function k(e,t){return t?`${e}\n\n${t}`:e}function A(e,t,n,r){e(async(e,i)=>{let a=O(e.text??``,n);return a?(t.reviewMode&&await r.restoreOriginalState(i),t.mode=a.keyword,r.persistState(),r.transform(k(a.prompt,a.strippedText))):r.noMatch})}function j(e,t,n){for(let r of C)e(r,{description:`Set workflow mode to ${r}`,handler:async(e,i)=>{t.reviewMode&&await n.restoreOriginalState(i),t.mode=r,n.persistState(),i.ui.notify(`Mode set to ${r}. Describe what you'd like to work on.`)}})}const M=r(t(i(import.meta.url)),`../agents/commands`);function N(e,t){A(t=>e.on(`input`,t),t,M,{restoreOriginalState:n=>_(e,n,t),persistState:()=>h(e,t),noMatch:{action:`continue`},transform:e=>({action:`transform`,text:e})})}function P(e,t){j((t,n)=>e.registerCommand(t,n),t,{restoreOriginalState:n=>_(e,n,t),persistState:()=>h(e,t)})}const F=r(t(i(import.meta.url)),`../agents/commands`);function I(e){return(t,n)=>{if(e.mode)return{systemPrompt:[t.systemPrompt,``,D(e.mode,F),``,`The user has set workflow mode to "${e.mode}". Honor this mode throughout the session until changed via /command.`].join(`
6
- `)}}}function L(e,t){e.on(`session_before_compact`,e=>{let n=e.preparation;return{compaction:{summary:g(t),details:{...t},firstKeptEntryId:n?.firstKeptEntryId,tokensBefore:n?.tokensBefore}}}),e.on(`session_before_tree`,e=>{if(e.preparation?.userWantsSummary)return{summary:{summary:g(t)}}})}function R(e,t){L({on:(t,n)=>{e.on(t,n)}},t)}const z={REVIEW_ACTIVATED:`maestria:review:activated`,REVIEW_DEACTIVATED:`maestria:review:deactivated`,SUBAGENT_STARTED:`maestria:subagent:started`,SUBAGENT_COMPLETED:`maestria:subagent:completed`,SUBAGENT_FAILED:`maestria:subagent:failed`},B=[`adventurer`,`architect`,`builder`,`diagnose`,`planner`,`reviewer`,`writer`];function V(e){if(!B.includes(e))throw Error(`Unknown agent: "${e}". Allowed: ${B.join(`, `)}`)}function H(e,t){if(!e||!e.trim())throw Error(t)}const U=new Set([`completed`,`steered`,`aborted`,`stopped`,`error`]);async function W(e,t,n,r,i,a){let o=0,s=r.getRecord(e);for(;s&&!U.has(s.status)&&o<120;){if(i?.aborted)throw Error(`Maestria subagent call aborted`);await new Promise(e=>setTimeout(e,500)),s=r.getRecord(e),o++,n&&a?.({content:[{type:`text`,text:`${t} running... (${Math.round(o*500/1e3)}s)`}]})}if(s&&!U.has(s.status))throw Error(`Subagent ${e} timed out after 60000ms`);if(!s)throw Error(`Subagent ${e} was cleaned up before completion`);return s}function G(e,t,n,r){let i=p(t,`orchestrator`,n,r);Object.assign(t,i),e.appendEntry(`maestria_state`,t)}function K(e,t,n){if(e.registerTool({name:`maestria_subagent`,label:`Maestria Subagent`,description:`Dispatch a task to a @maestria specialist subagent`,promptSnippet:`Delegate tasks to @maestria specialist subagents (adventurer, architect, builder, planner, diagnose, reviewer, writer)`,promptGuidelines:[`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).`],prepareArguments(e){return e},parameters:l.Object({agent:l.Optional(l.String({description:`Specialist agent name`})),task:l.Optional(l.String({description:`Task description for the subagent`})),tasks:l.Optional(l.Array(l.Object({agent:l.String(),task:l.String()}),{description:`Array of task objects for parallel or chain dispatch`})),mode:l.Optional(l.Union([l.Literal(`parallel`),l.Literal(`chain`),l.Literal(`single`)]))}),async execute(n,r,i,a,o){if(t.reviewMode)return{content:[{type:`text`,text:`Subagent dispatch is not available during review mode. Use /restore-model to exit review mode first.`}]};let s=r.mode??`single`;if(s===`single`)V(r.agent),H(r.task,`Task description is required`);else if(s===`parallel`){if(!r.tasks||r.tasks.length<2)throw Error(`For parallel mode, tasks array is required with at least 2 items`);if(r.tasks.length>8)throw Error(`For parallel mode, tasks array may have at most 8 items (got ${r.tasks.length})`);for(let e of r.tasks)V(e.agent),H(e.task,`Task description is required for all tasks`)}else if(s===`chain`){if(!r.tasks||r.tasks.length<2)throw Error(`For chain mode, tasks array is required with at least 2 items`);for(let e of r.tasks)V(e.agent),H(e.task,`Task description is required for all tasks`)}let{getSubagentsService:c}=await import(`@gotgenes/pi-subagents`),l=c();if(!l||typeof l.spawn!=`function`)return{content:[{type:`text`,text:[`## Subagent Dispatch Unavailable`,``,"The `@gotgenes/pi-subagents` extension is required for subagent dispatch but has not been loaded.",``,`Install it as a Pi extension:`,``,"```",`pi install npm:@gotgenes/pi-subagents`,"```",``,`Then restart your Pi session.`].join(`
7
- `)}]};try{if(s===`single`){let n=r.agent,o=r.task,s=l.spawn(n,o,{description:o.slice(0,80),foreground:!0,inheritContext:!0});G(e,t,n,o);let c=await W(s,`Subagent ${n}`,!0,l,i,a);return{content:[{type:`text`,text:c.result??c.error??`No output.`}],details:{subagentId:s}}}if(s===`parallel`){let n=r.tasks;a?.({content:[{type:`text`,text:`Spawning ${n.length} parallel subagents...`}]});let o=[];for(let r of n){let n=l.spawn(r.agent,r.task,{description:r.task.slice(0,80),foreground:!0,inheritContext:!0});o.push(n),G(e,t,r.agent,r.task)}let s=await Promise.all(o.map((e,t)=>W(e,`${n[t].agent} (${t+1}/${n.length})`,!1,l,i,a)));a?.({content:[{type:`text`,text:`All ${n.length} parallel subagents completed.`}]});let c=[`## Parallel Results (${n.length} tasks)\n`];for(let e=0;e<n.length;e++){let t=n[e],r=s[e],i=r.result??r.error??`No output.`;c.push(`### ${e+1}: ${t.agent}`),c.push(i)}return{content:[{type:`text`,text:c.join(`
5
+ `}function A(e,t){if(!(e in O))try{O[e]=k(e,t)}catch(t){console.warn(`[maestria] Failed to load mode prompt "${e}":`,t),O[e]=``}return`${D[e]}\n\n${O[e]}`}const j=/```[\s\S]*?```|`[^`]*`/g;function M(e){let t=[],n;for(;(n=j.exec(e))!==null;)t.push([n.index,n.index+n[0].length]);return t}function N(e,t){return t.some(([t,n])=>e>=t&&e<n)}const P={fein:3,sonar:2,blitz:1};function F(e,t){if(!e)return null;let n=M(e),r=null;for(let t of E){let i=RegExp(`\\b${t}\\b`,`gi`),a;for(;(a=i.exec(e))!==null;)N(a.index,n)||(r===null||P[t]>P[r.keyword])&&(r={keyword:t,index:a.index})}if(r===null)return null;let i=(e.slice(0,r.index)+e.slice(r.index+r.keyword.length).replace(/^:\s*/,``)).replace(/ {2,}/g,` `).trim();return{keyword:r.keyword,strippedText:i,prompt:A(r.keyword,t)}}function I(e,t){return t?`${e}\n\n${t}`:e}function L(e,t,n,r){e(async(e,i)=>{let a=F(e.text??``,n);return a?(t.reviewMode&&await r.restoreOriginalState(i),t.mode=a.keyword,r.persistState(),r.transform(I(a.prompt,a.strippedText))):r.noMatch})}function R(e,t,n){for(let r of E)e(r,{description:`Set workflow mode to ${r}`,handler:async(e,i)=>{t.reviewMode&&await n.restoreOriginalState(i),t.mode=r,n.persistState(),i.ui.notify(`Mode set to ${r}. Describe what you'd like to work on.`)}})}const z=r(t(i(import.meta.url)),`../agents/commands`);function B(e,t){L(t=>e.on(`input`,t),t,z,{restoreOriginalState:n=>_(e,n,t),persistState:()=>h(e,t),noMatch:{action:`continue`},transform:e=>({action:`transform`,text:e})})}function V(e,t){R((t,n)=>e.registerCommand(t,n),t,{restoreOriginalState:n=>_(e,n,t),persistState:()=>h(e,t)})}const H=r(t(i(import.meta.url)),`../agents/commands`);function U(e){return(t,n)=>{if(e.mode)return{systemPrompt:[t.systemPrompt,``,A(e.mode,H),``,`The user has set workflow mode to "${e.mode}". Honor this mode throughout the session until changed via /command.`].join(`
6
+ `)}}}function W(e,t){e.on(`session_before_compact`,e=>{let n=e.preparation;return{compaction:{summary:g(t),details:{...t},firstKeptEntryId:n?.firstKeptEntryId,tokensBefore:n?.tokensBefore}}}),e.on(`session_before_tree`,e=>{if(e.preparation?.userWantsSummary)return{summary:{summary:g(t)}}})}function G(e,t){W({on:(t,n)=>{e.on(t,n)}},t)}const K=new Set([`completed`,`steered`,`aborted`,`stopped`,`error`]);async function q(e,t,n,r,i,a){let o=0,s=r.getRecord(e);for(;s&&!K.has(s.status)&&o<120;){if(i?.aborted)throw Error(`Maestria subagent call aborted`);await new Promise(e=>setTimeout(e,500)),s=r.getRecord(e),o++,n&&a?.({content:[{type:`text`,text:`${t} running... (${Math.round(o*500/1e3)}s)`}]})}if(s&&!K.has(s.status))throw Error(`Subagent ${e} timed out after 60000ms`);if(!s)throw Error(`Subagent ${e} was cleaned up before completion`);return s}function J(e,t,n,r){let i=p(t,`orchestrator`,n,r);Object.assign(t,i),e.appendEntry(`maestria_state`,t)}function Y(e,t,n){if(e.registerTool({name:`maestria_subagent`,label:`Maestria Subagent`,description:`Dispatch a task to a @maestria specialist subagent`,promptSnippet:`Delegate tasks to @maestria specialist subagents (adventurer, architect, builder, planner, diagnose, reviewer, writer)`,promptGuidelines:[`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).`],prepareArguments(e){return e},parameters:l.Object({agent:l.Optional(l.String({description:`Specialist agent name`})),task:l.Optional(l.String({description:`Task description for the subagent`})),tasks:l.Optional(l.Array(l.Object({agent:l.String(),task:l.String()}),{description:`Array of task objects for parallel or chain dispatch`})),mode:l.Optional(l.Union([l.Literal(`parallel`),l.Literal(`chain`),l.Literal(`single`)]))}),async execute(n,r,i,a,o){if(t.reviewMode)return{content:[{type:`text`,text:`Subagent dispatch is not available during review mode. Use /restore-model to exit review mode first.`}]};let s=r.mode??`single`;if(s===`single`)x(r.agent),S(r.task,`Task description is required`);else if(s===`parallel`){if(!r.tasks||r.tasks.length<2)throw Error(`For parallel mode, tasks array is required with at least 2 items`);if(r.tasks.length>8)throw Error(`For parallel mode, tasks array may have at most 8 items (got ${r.tasks.length})`);for(let e of r.tasks)x(e.agent),S(e.task,`Task description is required for all tasks`)}else if(s===`chain`){if(!r.tasks||r.tasks.length<2)throw Error(`For chain mode, tasks array is required with at least 2 items`);for(let e of r.tasks)x(e.agent),S(e.task,`Task description is required for all tasks`)}let{getSubagentsService:c}=await import(`@gotgenes/pi-subagents`),l=c();if(!l||typeof l.spawn!=`function`)return{content:[{type:`text`,text:[`## Subagent Dispatch Unavailable`,``,"The `@gotgenes/pi-subagents` extension is required for subagent dispatch but has not been loaded.",``,`Install it as a Pi extension:`,``,"```",`pi install npm:@gotgenes/pi-subagents`,"```",``,`Then restart your Pi session.`].join(`
7
+ `)}]};try{if(s===`single`){let n=r.agent,o=r.task,s=l.spawn(n,o,{description:o.slice(0,80),foreground:!0,inheritContext:!0});J(e,t,n,o);let c=await q(s,`Subagent ${n}`,!0,l,i,a);return{content:[{type:`text`,text:c.result??c.error??`No output.`}],details:{subagentId:s}}}if(s===`parallel`){let n=r.tasks;a?.({content:[{type:`text`,text:`Spawning ${n.length} parallel subagents...`}]});let o=[];for(let r of n){let n=l.spawn(r.agent,r.task,{description:r.task.slice(0,80),foreground:!0,inheritContext:!0});o.push(n),J(e,t,r.agent,r.task)}let s=await Promise.all(o.map((e,t)=>q(e,`${n[t].agent} (${t+1}/${n.length})`,!1,l,i,a)));a?.({content:[{type:`text`,text:`All ${n.length} parallel subagents completed.`}]});let c=[`## Parallel Results (${n.length} tasks)\n`];for(let e=0;e<n.length;e++){let t=n[e],r=s[e],i=r.result??r.error??`No output.`;c.push(`### ${e+1}: ${t.agent}`),c.push(i)}return{content:[{type:`text`,text:c.join(`
8
8
 
9
- `)}],details:{subagentIds:o}}}if(s===`chain`){let n=r.tasks,o=``;for(let r=0;r<n.length;r++){let s=n[r],c=s.task;r>0&&c.includes(`{previous}`)&&(c=c.replace(/\{previous\}/g,o));let u=l.spawn(s.agent,c,{description:c.slice(0,80),foreground:!0,inheritContext:!0});G(e,t,s.agent,c),a?.({content:[{type:`text`,text:`Chain step ${r+1}/${n.length}: ${s.agent} running...`}]});let d=await W(u,`Chain step ${r+1}: ${s.agent}`,!0,l,i,a);o=d.result??d.error??`No output.`,r<n.length-1&&a?.({content:[{type:`text`,text:`Chain step ${r+1}/${n.length}: ${s.agent} completed. Moving to next step.`}]})}return{content:[{type:`text`,text:o}],details:{subagentId:`chain-completed`}}}throw Error(`Unknown dispatch mode`)}catch(e){console.warn(`[maestria] Subagent dispatch failed:`,e);let t=r.agent??r.tasks?.[0]?.agent??`unknown`,n=r.task??r.tasks?.map(e=>e.task).join(`; `)??`unknown`;return{content:[{type:`text`,text:[`## Subagent Handoff Required`,``,`**From:** orchestrator`,`**To:** ${t}`,`**Task:** ${n}`,``,`Subagent dispatch failed. Please delegate this work manually.`].join(`
10
- `)}]}}}}),e.events){let r=e.events.on(u.STARTED,n=>{let{id:r,type:i}=n;t.subagentStatus[r]={type:i,status:`running`,startedAt:Date.now()},h(e,t),e.events?.emit(z.SUBAGENT_STARTED,{id:r,type:i,timestamp:Date.now()})}),i=e.events.on(u.COMPLETED,n=>{let{id:r}=n,i=t.subagentStatus[r];i&&(i.status=`completed`,i.completedAt=Date.now()),h(e,t),e.events?.emit(z.SUBAGENT_COMPLETED,{id:r,type:i?.type,timestamp:Date.now()})}),a=e.events.on(u.FAILED,n=>{let{id:r,status:i}=n,a=t.subagentStatus[r];a&&(a.status=i??`error`,a.completedAt=Date.now()),h(e,t),e.events?.emit(z.SUBAGENT_FAILED,{id:r,type:a?.type,timestamp:Date.now()})}),o=e.events.on(u.STEERED,n=>{let{id:r}=n;t.subagentStatus[r]||(t.subagentStatus[r]={type:`unknown`,status:`running`,startedAt:Date.now()}),h(e,t)});n&&n.push(r,i,a,o)}}const q=[`read`,`grep`,`find`,`ls`,`glob`];function J(e,t){e.registerCommand(`maestria-status`,{description:`Show current maestria session state including handoff history`,handler:async(e,n)=>{let r=g(t);if(!r){n.ui.notify(`No active maestria state to report.`);return}n.ui.setEditorText(r)}}),e.registerCommand(`review`,{description:`Enter review mode. Blocks destructive tools, sets read-only toolset.`,handler:async(n,r)=>{if(!n.trim()){r.ui.notify(`Usage: /review <target> - describe what to review`);return}let i=r.model?.id??null,a=e.getActiveTools(),o={...t,reviewMode:!0,originalModel:i,originalTools:a};if(Object.assign(t,o),h(e,t),t.reviewModel){let n=await v(e,r,t);n&&(r.ui.notify(`Review mode: switched to ${n}`),e.events?.emit(z.REVIEW_ACTIVATED,{originalModel:t.originalModel,reviewModel:n,timestamp:Date.now()}))}e.setActiveTools(q),e.sendUserMessage([`[REVIEW: ${n}]`,``,`Review: ${n}. Use the reviewer prompt template.`,`Read only, no edits, report findings.`].join(`
11
- `),{deliverAs:`steer`})}}),e.registerCommand(`restore-model`,{description:`Restore the original model and tools that were active before review mode was entered.`,handler:async(n,r)=>{if(!t.reviewMode){r.ui.notify(`Not in review mode. Nothing to restore.`);return}let i=t.originalModel;await _(e,r,t),h(e,t),r.ui.notify(`Restored original model and tools.`),e.events?.emit(z.REVIEW_DEACTIVATED,{originalModel:i,timestamp:Date.now()})}}),e.registerCommand(`handoff`,{description:`Generate a structured handoff prompt for a new task context`,handler:async(n,r)=>{if(!n.trim()){r.ui.notify(`Usage: /handoff <goal> - describe the task context for handoff`);return}let i=n.trim(),a=[`**Goal:** `+i,``,`**Context:**`,`- Mode: `+(t.mode??`none`),`- Active task: `+(t.activeTask||`none`),`- Specialists delegated: `+((t.specialistsDelegated?.length??0)>0?t.specialistsDelegated.join(`, `):`none`),`- Recent handoffs: `+(t.handoffHistory?.length??0)+` entries`,`- Files modified: `+((t.filesModified?.length??0)>0?t.filesModified.join(`, `):`none`),``,`**Requirements:**`,`(fill in specific requirements)`,``,`**Known problems:**`,(t.blockers?.length??0)>0?t.blockers.map(e=>`- `+e).join(`
12
- `):`(no known problems documented)`,``,`**Success criteria:**`,`(fill in how to verify completion)`,``,`**Next step:**`,`(fill in what happens after this task)`,``,`---`,`Complete the fields above before sending.`].join(`
13
- `);t.handoffHistory=[{from:`current`,to:`next`,task:i,timestamp:Date.now()},...t.handoffHistory??[]].slice(0,5),h(e,t),e.sendUserMessage(a,{deliverAs:`steer`})}}),e.registerCommand(`review-model`,{description:`Set which model to use when entering review mode`,handler:async(n,r)=>{if(!n.trim()){r.ui.notify(`Usage: /review-model <model-id>`);return}let i=n.trim(),a=r.modelRegistry.getAll();if(!a.find(e=>e.id===i)){r.ui.notify(`Unknown model: "${i}". Available: ${a.map(e=>e.id).join(`, `)}`);return}t.reviewModel=i,h(e,t),r.ui.notify(`Review model set to: ${i}`)}})}const Y=[/rm\s+-rf\s+\//,/dd\s+if=/,/>\s*\/dev\/sd/,/chmod\s+-R\s+777\s+\//,/mkfs\.\w+/,/:(){ :\|:& };:/,/>\s*\/etc\/(passwd|shadow|sudoers)/,/\beval\b/,/wget\s+-O\s*-\s*\|\s*(bash|sh)/,/curl\s+.*\|\s*(bash|sh)/,/crontab\s+-r/];function X(e,t){e.on(`tool_call`,async(n,r)=>{if(!(!n||!n.toolName)){if(t.mode!==null&&e.getActiveTools().includes(`subagent`)&&n.toolName!==`maestria_subagent`)return{block:!0,reason:`Tool '${n.toolName}' is blocked for the orchestrator. Use 'maestria_subagent' to delegate tasks to specialists.`};if(t.reviewMode&&(d(`edit`,n)||d(`write`,n)||d(`bash`,n)))return{block:!0,reason:`Review mode is active. Report findings, do not edit.`};if(d(`bash`,n)){if(!n.input||typeof n.input!=`object`)return;let e=n.input.command;if(e){for(let t of Y)if(t.test(e))return r.hasUI&&await r.ui.confirm(`Dangerous Pattern Detected`,`This command matches a dangerous pattern:\n${e}\nProceed?`)?void 0:{block:!0,reason:`Command matches dangerous pattern: ${t}`}}}}})}function Z(e){let t=f(),n=[];P(e,t),N(e,t);let r=I(t);e.on(`before_agent_start`,(e,t)=>r(e,t)),e.on(`session_start`,(e,n)=>{if(S(n),!n.sessionManager?.getEntries)return;let r=n.sessionManager.getEntries();for(let e=r.length-1;e>=0;e--){let n=r[e];if(n.type===`custom`&&n.customType===`maestria_state`){let e=n.data;e&&typeof e==`object`&&Object.assign(t,e);break}}}),R(e,t),K(e,t,n),J(e,t),e.on(`session_shutdown`,()=>{for(let e of n)e();n.length=0}),X(e,t)}export{Z as default};
9
+ `)}],details:{subagentIds:o}}}if(s===`chain`){let n=r.tasks,o=``;for(let r=0;r<n.length;r++){let s=n[r],c=s.task;r>0&&c.includes(`{previous}`)&&(c=c.replace(/\{previous\}/g,o));let u=l.spawn(s.agent,c,{description:c.slice(0,80),foreground:!0,inheritContext:!0});J(e,t,s.agent,c),a?.({content:[{type:`text`,text:`Chain step ${r+1}/${n.length}: ${s.agent} running...`}]});let d=await q(u,`Chain step ${r+1}: ${s.agent}`,!0,l,i,a);o=d.result??d.error??`No output.`,r<n.length-1&&a?.({content:[{type:`text`,text:`Chain step ${r+1}/${n.length}: ${s.agent} completed. Moving to next step.`}]})}return{content:[{type:`text`,text:o}],details:{subagentId:`chain-completed`}}}throw Error(`Unknown dispatch mode`)}catch(e){console.warn(`[maestria] Subagent dispatch failed:`,e);let t=r.agent??r.tasks?.[0]?.agent??`unknown`,n=r.task??r.tasks?.map(e=>e.task).join(`; `)??`unknown`;return{content:[{type:`text`,text:[`## Subagent Handoff Required`,``,`**From:** orchestrator`,`**To:** ${t}`,`**Task:** ${n}`,``,`Subagent dispatch failed. Please delegate this work manually.`].join(`
10
+ `)}]}}}}),e.events){let r=e.events.on(u.STARTED,n=>{let{id:r,type:i}=n;t.subagentStatus[r]={type:i,status:`running`,startedAt:Date.now()},h(e,t),e.events?.emit(y.SUBAGENT_STARTED,{id:r,type:i,timestamp:Date.now()})}),i=e.events.on(u.COMPLETED,n=>{let{id:r}=n,i=t.subagentStatus[r];i&&(i.status=`completed`,i.completedAt=Date.now()),h(e,t),e.events?.emit(y.SUBAGENT_COMPLETED,{id:r,type:i?.type,timestamp:Date.now()})}),a=e.events.on(u.FAILED,n=>{let{id:r,status:i}=n,a=t.subagentStatus[r];a&&(a.status=i??`error`,a.completedAt=Date.now()),h(e,t),e.events?.emit(y.SUBAGENT_FAILED,{id:r,type:a?.type,timestamp:Date.now()})}),o=e.events.on(u.STEERED,n=>{let{id:r}=n;t.subagentStatus[r]||(t.subagentStatus[r]={type:`unknown`,status:`running`,startedAt:Date.now()}),h(e,t)});n&&n.push(r,i,a,o)}}const X=[`read`,`grep`,`find`,`ls`,`glob`];function Z(e,t){e.registerCommand(`maestria-status`,{description:`Show current maestria session state including handoff history`,handler:async(e,n)=>{let r=g(t);if(!r){n.ui.notify(`No active maestria state to report.`);return}n.ui.setEditorText(r)}}),e.registerCommand(`review`,{description:`Enter review mode. Blocks destructive tools, sets read-only toolset.`,handler:async(n,r)=>{if(!n.trim()){r.ui.notify(`Usage: /review <target> - describe what to review`);return}let i=r.model?.id??null,a=e.getActiveTools(),o={...t,reviewMode:!0,originalModel:i,originalTools:a};if(Object.assign(t,o),h(e,t),t.reviewModel){let n=await v(e,r,t);n&&(r.ui.notify(`Review mode: switched to ${n}`),e.events?.emit(y.REVIEW_ACTIVATED,{originalModel:t.originalModel,reviewModel:n,timestamp:Date.now()}))}e.setActiveTools(X),e.sendUserMessage([`[REVIEW: ${n}]`,``,`Review: ${n}. Use the reviewer prompt template.`,`Read only, no edits, report findings.`].join(`
11
+ `),{deliverAs:`steer`})}}),e.registerCommand(`restore-model`,{description:`Restore the original model and tools that were active before review mode was entered.`,handler:async(n,r)=>{if(!t.reviewMode){r.ui.notify(`Not in review mode. Nothing to restore.`);return}let i=t.originalModel;await _(e,r,t),h(e,t),r.ui.notify(`Restored original model and tools.`),e.events?.emit(y.REVIEW_DEACTIVATED,{originalModel:i,timestamp:Date.now()})}}),e.registerCommand(`handoff`,{description:`Generate a structured handoff prompt for a new task context`,handler:async(n,r)=>{if(!n.trim()){r.ui.notify(`Usage: /handoff <goal> - describe the task context for handoff`);return}let i=n.trim(),a=[`**Goal:** `+i,``,`**Context:**`,`- Mode: `+(t.mode??`none`),`- Active task: `+(t.activeTask||`none`),`- Specialists delegated: `+((t.specialistsDelegated?.length??0)>0?t.specialistsDelegated.join(`, `):`none`),`- Recent handoffs: `+(t.handoffHistory?.length??0)+` entries`,`- Files modified: `+((t.filesModified?.length??0)>0?t.filesModified.join(`, `):`none`),``,`**Requirements:**`,`(fill in specific requirements)`,``,`**Known problems:**`,(t.blockers?.length??0)>0?t.blockers.map(e=>`- `+e).join(`
12
+ `):`(no known problems documented)`,``,`**Assumptions documented:**`,`(document assumptions made, tagged [inferred] where uncertain)`,``,`**Success criteria:**`,`(fill in how to verify completion)`,``,`**Next step:**`,`(fill in what happens after this task)`,``,`---`,`Complete the fields above before sending.`].join(`
13
+ `);t.handoffHistory=[{from:`current`,to:`next`,task:i,timestamp:Date.now()},...t.handoffHistory??[]].slice(0,5),h(e,t),e.sendUserMessage(a,{deliverAs:`steer`})}}),e.registerCommand(`review-model`,{description:`Set which model to use when entering review mode`,handler:async(n,r)=>{if(!n.trim()){r.ui.notify(`Usage: /review-model <model-id>`);return}let i=n.trim(),a=r.modelRegistry.getAll();if(!a.find(e=>e.id===i)){r.ui.notify(`Unknown model: "${i}". Available: ${a.map(e=>e.id).join(`, `)}`);return}t.reviewModel=i,h(e,t),r.ui.notify(`Review model set to: ${i}`)}})}const Q=[/rm\s+-rf\s+\//,/dd\s+if=/,/>\s*\/dev\/sd/,/chmod\s+-R\s+777\s+\//,/mkfs\.\w+/,/:(){ :\|:& };:/,/>\s*\/etc\/(passwd|shadow|sudoers)/,/\beval\b/,/wget\s+-O\s*-\s*\|\s*(bash|sh)/,/curl\s+.*\|\s*(bash|sh)/,/crontab\s+-r/];function $(e,t){e.on(`tool_call`,async(n,r)=>{if(!(!n||!n.toolName)){if(t.mode!==null&&e.getActiveTools().includes(`subagent`)&&n.toolName!==`maestria_subagent`)return{block:!0,reason:`Tool '${n.toolName}' is blocked for the orchestrator. Use 'maestria_subagent' to delegate tasks to specialists.`};if(t.reviewMode&&(d(`edit`,n)||d(`write`,n)||d(`bash`,n)))return{block:!0,reason:`Review mode is active. Report findings, do not edit.`};if(d(`bash`,n)){if(!n.input||typeof n.input!=`object`)return;let e=n.input.command;if(e){for(let t of Q)if(t.test(e))return r.hasUI&&await r.ui.confirm(`Dangerous Pattern Detected`,`This command matches a dangerous pattern:\n${e}\nProceed?`)?void 0:{block:!0,reason:`Command matches dangerous pattern: ${t}`}}}}})}function ee(e){let t=f(),n=[];V(e,t),B(e,t);let r=U(t);e.on(`before_agent_start`,(e,t)=>r(e,t)),e.on(`session_start`,(e,n)=>{if(T(n),!n.sessionManager?.getEntries)return;let r=n.sessionManager.getEntries();for(let e=r.length-1;e>=0;e--){let n=r[e];if(n.type===`custom`&&n.customType===`maestria_state`){let e=n.data;e&&typeof e==`object`&&Object.assign(t,e);break}}}),G(e,t),Y(e,t,n),Z(e,t),e.on(`session_shutdown`,()=>{for(let e of n)e();n.length=0}),$(e,t)}export{ee as default};
14
14
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":["deploySpecialistAgents","installModeAutoDetect","installModeCommands","COMMANDS_DIR","installCompactionHandlers"],"sources":["../../shared/pi/src/state-core.ts","../src/state/review.ts","../../shared/pi/src/agent-deployment.ts","../src/agents.ts","../../shared/pi/src/modes-core.ts","../src/modes.ts","../src/rules.ts","../../shared/pi/src/compaction-core.ts","../src/compaction.ts","../../shared/pi/src/subagent-utils.ts","../src/subagent.ts","../src/commands.ts","../../shared/pi/src/tools-core.ts","../src/tools.ts","../src/extension.ts"],"sourcesContent":["/**\n * Shared state management for Maestria platform packages.\n *\n * Pure TypeScript — no platform-specific dependencies.\n * Imported by both @maestria/omp and @maestria/pi to eliminate\n * duplication in state/types.ts, transforms.ts, persistence.ts, and render.ts.\n *\n * @module\n */\n\n// ── Types ──\n\nexport type ModeKeyword = 'fein' | 'sonar' | 'blitz';\n\nexport const HANDOFF_HISTORY_CAP = 5;\nexport const 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 reviewModel: string | null;\n}\n\n// ── Transforms ──\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\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 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\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 { ...state, reviewMode: active };\n}\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// ── Persistence ──\n\nexport function persistState(\n pi: { appendEntry: (type: string, data: unknown) => void },\n state: MaestriaState,\n): void {\n pi.appendEntry('maestria_state', { ...state });\n}\n\n// ── Render ──\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","import type {\n ExtensionAPI,\n ExtensionCommandContext,\n ExtensionContext,\n} 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: ExtensionContext,\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","/**\n * Shared agent deployment logic for Maestria platform packages.\n *\n * Both @maestria/omp and @maestria/pi deploy specialist agent .md files\n * to their respective platform agent directories. This module eliminates\n * the duplication between the two packages.\n *\n * @module\n */\n\nimport { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\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 given destination directory.\n *\n * Only creates files that don't already exist — never overwrites user-customized agents.\n *\n * @param agentsSrc - Path to the source directory containing specialist .md files\n * @param agentsDest - Absolute path to the platform's agents destination directory\n * (e.g. `join(homedir(), '.omp', 'agent', 'agents')`)\n * @returns The number of agents newly deployed\n */\nexport function deploySpecialistAgents(agentsSrc: string, agentsDest: string): number {\n if (!existsSync(agentsSrc)) {\n console.warn('[maestria] Agents source directory not found:', agentsSrc);\n return 0;\n }\n\n try {\n mkdirSync(agentsDest, { recursive: true });\n } catch {\n console.warn('[maestria] Could not create agents directory:', agentsDest);\n return 0;\n }\n\n let deployed = 0;\n for (const name of SPECIALIST_NAMES) {\n const srcFile = join(agentsSrc, `${name}.md`);\n const destFile = join(agentsDest, `${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 ${agentsDest}`);\n }\n\n return deployed;\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","/**\n * Shared mode constants and utilities for Maestria platform packages.\n *\n * Pure TypeScript — no platform-specific dependencies.\n * Imported by both @maestria/omp and @maestria/pi to eliminate duplication\n * in mode prompt loading, keyword detection, and text transformation.\n *\n * @module\n */\n\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { MaestriaState } from './state-core.js';\n\n// ── Constants ──\n\nexport const MODE_KEYWORDS = ['fein', 'sonar', 'blitz'] as const;\nexport type ModeKeyword = (typeof MODE_KEYWORDS)[number];\n\nexport const MODE_MARKERS: Record<ModeKeyword, string> = {\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n blitz: '[MODE: blitz]',\n};\n\n// ── Prompt loading ──\n\n/** Lazily cached mode prompts — shared across platforms. */\nconst _promptCache: Partial<Record<ModeKeyword, string>> = {};\n\n/**\n * Load and cache a mode prompt from a commands directory.\n * The commandsDir should point to a directory containing `fein.md`,\n * `sonar.md`, and `blitz.md` files.\n */\nexport function loadModePrompt(name: string, commandsDir: string): string {\n const content = readFileSync(resolve(commandsDir, `${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\n/**\n * Get the full mode prompt (marker + body) for a keyword, loading from\n * the given commands directory on first access.\n */\nexport function getModePrompt(keyword: ModeKeyword, commandsDir: string): string {\n if (!(keyword in _promptCache)) {\n try {\n _promptCache[keyword] = loadModePrompt(keyword, commandsDir);\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\n// ── Keyword detection ──\n\n/** Result of detecting a mode keyword in text. */\nexport interface ModeDetectResult {\n /** The detected keyword. */\n keyword: ModeKeyword;\n /** The text with the keyword stripped and trimmed. */\n strippedText: string;\n /** The full mode prompt (marker + body). */\n prompt: string;\n}\n\n/**\n * Detect a mode keyword (fein/sonar/blitz) in text as a whole word,\n * case-insensitive. Returns the first match found (iteration order:\n * fein, sonar, blitz).\n */\nexport function detectModeInText(text: string, commandsDir: string): ModeDetectResult | null {\n if (!text) return null;\n\n for (const keyword of MODE_KEYWORDS) {\n const regex = new RegExp(`\\\\b${keyword}\\\\b`, 'i');\n if (regex.test(text)) {\n const stripped = text.replace(regex, '').trim();\n const prompt = getModePrompt(keyword, commandsDir);\n return { keyword, strippedText: stripped, prompt };\n }\n }\n\n return null;\n}\n\n/**\n * Build the final text to send to the LLM: prompt + stripped text.\n * If strippedText is empty, returns just the prompt.\n */\nexport function buildModeText(prompt: string, strippedText: string): string {\n return strippedText ? `${prompt}\\n\\n${strippedText}` : prompt;\n}\n\n// ── Platform handler factories ──\n\n/**\n * Install an input event handler that detects mode keywords (fein/sonar/blitz)\n * in user input, strips them, and injects the mode prompt.\n *\n * @param onInput - Platform's `pi.on('input', handler)` method\n * @param state - Shared maestria state\n * @param commandsDir - Path to directory containing fein.md/sonar.md/blitz.md\n * @param opts - Platform-specific callbacks and result builders\n */\nexport function installModeAutoDetect(\n onInput: (handler: (event: unknown, ctx: unknown) => unknown) => void,\n state: MaestriaState,\n commandsDir: string,\n opts: {\n /** Exit review mode — calls platform's restoreOriginalState */\n restoreOriginalState: (ctx: unknown) => Promise<void>;\n /** Persist state after mode change */\n persistState: () => void;\n /** Return value when no keyword is detected (e.g. Pi: { action: 'continue' }) */\n noMatch: unknown;\n /** Build return value from transformed text (e.g. Pi: { action: 'transform', text }) */\n transform: (text: string) => unknown;\n },\n): void {\n onInput(async (event: unknown, ctx: unknown) => {\n const text = ((event as Record<string, unknown>).text as string) ?? '';\n const result = detectModeInText(text, commandsDir);\n if (!result) return opts.noMatch;\n\n if (state.reviewMode) {\n await opts.restoreOriginalState(ctx);\n }\n\n state.mode = result.keyword;\n opts.persistState();\n\n return opts.transform(buildModeText(result.prompt, result.strippedText));\n });\n}\n\n/**\n * Install slash commands for fein/sonar/blitz that set the workflow mode\n * and show a notification. Task description injection is handled by the\n * auto-detect handler instead.\n *\n * @param registerCommand - Platform's `pi.registerCommand(name, opts)` method\n * @param state - Shared maestria state\n * @param opts - Platform-specific callbacks\n */\nexport function installModeCommands(\n registerCommand: (\n name: string,\n options: { description: string; handler: (...args: unknown[]) => unknown },\n ) => void,\n state: MaestriaState,\n opts: {\n /** Exit review mode before switching modes */\n restoreOriginalState: (ctx: unknown) => Promise<void>;\n /** Persist state after mode change */\n persistState: () => void;\n },\n): void {\n for (const keyword of MODE_KEYWORDS) {\n registerCommand(keyword, {\n description: `Set workflow mode to ${keyword}`,\n handler: async (_args: unknown, ctx: unknown) => {\n if (state.reviewMode) {\n await opts.restoreOriginalState(ctx);\n }\n\n state.mode = keyword;\n opts.persistState();\n\n ((ctx as Record<string, unknown>).ui as { notify: (msg: string) => void }).notify(\n `Mode set to ${keyword}. Describe what you'd like to work on.`,\n );\n },\n });\n }\n}\n","import { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { persistState, restoreOriginalState } from '@/state.js';\nimport {\n installModeAutoDetect as installAutoDetect,\n installModeCommands as installCommands,\n} from '@maestria/shared-pi/modes-core';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst COMMANDS_DIR = resolve(__dirname, '../agents/commands');\n\nexport function installModeAutoDetect(pi: ExtensionAPI, state: MaestriaState): void {\n installAutoDetect((handler) => pi.on('input', handler as never), state, COMMANDS_DIR, {\n restoreOriginalState: (ctx) => restoreOriginalState(pi, ctx as ExtensionContext, state),\n persistState: () => persistState(pi, state),\n noMatch: { action: 'continue' as const },\n transform: (text) => ({ action: 'transform' as const, text }),\n });\n}\n\nexport function installModeCommands(pi: ExtensionAPI, state: MaestriaState): void {\n installCommands((name, opts) => pi.registerCommand(name, opts as never), state, {\n restoreOriginalState: (ctx) => restoreOriginalState(pi, ctx as ExtensionContext, state),\n persistState: () => persistState(pi, state),\n });\n}\n","import { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type {\n BeforeAgentStartEvent,\n BeforeAgentStartEventResult,\n ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { getModePrompt } from '@maestria/shared-pi/modes-core';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst COMMANDS_DIR = resolve(__dirname, '../agents/commands');\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, COMMANDS_DIR),\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","/**\n * Shared compaction handlers for Maestria platform packages.\n *\n * Pure TypeScript — no platform-specific dependencies.\n * Imported by both @maestria/omp and @maestria/pi to eliminate duplication.\n *\n * @module\n */\n\nimport { renderMaestriaSummary } from './state-core.js';\nimport type { MaestriaState } from './state-core.js';\n\n/**\n * Install handlers for session compaction and tree events to persist\n * and restore maestria state across session compaction boundaries.\n *\n * Uses duck-typed `pi` parameter — both Pi and OMP ExtensionAPI types\n * satisfy the `{ on(event: string, handler): void }` shape needed here.\n */\nexport function installCompactionHandlers(\n pi: {\n on: (event: string, handler: (...args: unknown[]) => unknown) => void;\n },\n state: MaestriaState,\n): void {\n pi.on('session_before_compact', (event: unknown) => {\n const prep = (event as Record<string, unknown>).preparation as\n | Record<string, unknown>\n | undefined;\n return {\n compaction: {\n summary: renderMaestriaSummary(state),\n details: { ...state },\n firstKeptEntryId: prep?.firstKeptEntryId,\n tokensBefore: prep?.tokensBefore,\n },\n };\n });\n\n pi.on('session_before_tree', (event: unknown) => {\n const prep = (event as Record<string, unknown>).preparation as\n | Record<string, unknown>\n | undefined;\n if (prep?.userWantsSummary) {\n return {\n summary: {\n summary: renderMaestriaSummary(state),\n },\n };\n }\n return undefined;\n });\n}\n","/**\n * Pi platform compaction handlers.\n *\n * Thin wrapper around the shared implementation in\n * @maestria/shared-pi/compaction-core.\n *\n * @module\n */\n\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { installCompactionHandlers as installHandlers } from '@maestria/shared-pi/compaction-core';\n\n/**\n * Install session compaction and tree event handlers for Pi.\n * Delegates to the shared implementation which is duck-type compatible\n * with Pi's ExtensionAPI.\n */\nexport function installCompactionHandlers(pi: ExtensionAPI, state: MaestriaState): void {\n // Bridge: ExtensionAPI.on has overloaded event types incompatible with\n // the duck-typed { on: (event: string, handler) => void } in the shared\n // module. The as-never cast is safe at runtime — both SDKs share the same\n // event shapes.\n installHandlers(\n {\n on: (event, handler) => {\n pi.on(event as never, handler as never);\n },\n },\n state,\n );\n}\n","/**\n * Shared subagent validation utilities for Maestria platform packages.\n *\n * Pure TypeScript — no platform-specific dependencies.\n * Imported by both @maestria/omp and @maestria/pi to eliminate duplication.\n *\n * @module\n */\n\n/** Maestria cross-extension event names. */\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\n/** The set of specialist agent types maestria supports. */\nexport const ALLOWED_AGENTS = [\n 'adventurer',\n 'architect',\n 'builder',\n 'diagnose',\n 'planner',\n 'reviewer',\n 'writer',\n] as const;\n\n/** A valid specialist agent name. */\nexport type AllowedAgent = (typeof ALLOWED_AGENTS)[number];\n\n/** The 6-field handoff contract used in delegation. */\nexport const HANDOFF_FIELDS = [\n 'Goal',\n 'Context',\n 'Requirements',\n 'Known problems',\n 'Success criteria',\n 'Next step',\n] as const;\n\n/** Result of validating a handoff document against the contract fields. */\nexport interface HandoffValidation {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Asserts that `agent` is a known maestria specialist.\n * @throws {Error} if the agent name is not in ALLOWED_AGENTS.\n */\nexport function assertValidAgent(agent: string): asserts agent is AllowedAgent {\n if (!ALLOWED_AGENTS.includes(agent as AllowedAgent)) {\n throw new Error(`Unknown agent: \"${agent}\". Allowed: ${ALLOWED_AGENTS.join(', ')}`);\n }\n}\n\n/**\n * Asserts that `task` is a non-empty, non-whitespace string.\n * @throws {Error} with the given label if task is falsy or all-whitespace.\n */\nexport function assertNonEmptyTask(\n task: string | undefined,\n label: string,\n): asserts task is string {\n if (!task || !task.trim()) {\n throw new Error(label);\n }\n}\n\n/**\n * Validates that a handoff document contains all required fields\n * with non-empty content. Each field is expected in markdown bold format:\n * `**Field:** content`.\n */\nexport function validateHandoff(handoff: string): HandoffValidation {\n const errors: string[] = [];\n for (const field of HANDOFF_FIELDS) {\n // Match field header and capture content up to the next field or end of string.\n // This avoids false positives when an empty field is followed by another field's `**` header.\n const pattern = `\\\\*\\\\*${field}:\\\\*\\\\*([\\\\s\\\\S]*?)(?=\\\n\\\\*\\\\*|$)`;\n const match = handoff.match(new RegExp(pattern, 'i'));\n if (!match || !match[1] || !match[1].trim()) {\n errors.push(`Missing or empty field: \"${field}\"`);\n }\n }\n return { valid: errors.length === 0, errors };\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","/**\n * Shared tool interceptor utilities for Maestria platform packages.\n *\n * Pure TypeScript — no platform-specific dependencies.\n * Imported by both @maestria/omp and @maestria/pi to eliminate duplication.\n *\n * @module\n */\n\n/**\n * Dangerous bash command patterns that should always be blocked,\n * regardless of mode or specialist role.\n */\nexport const 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 /:(){ :\\|:& };:/,\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","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';\nimport { DANGEROUS_PATTERNS } from '@maestria/shared-pi/tools-core';\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 // ── Pure dispatcher enforcement ──\n // When a maestria workflow mode is active, restrict the root session\n // (orchestrator) to ONLY the maestria_subagent delegation tool.\n // Subagent sessions are detected by the absence of the pi-subagents\n // 'subagent' tool (stripped by applyRecursionGuard in child sessions).\n if (state.mode !== null && pi.getActiveTools().includes('subagent')) {\n if (event.toolName !== 'maestria_subagent') {\n return {\n block: true,\n reason:\n `Tool '${event.toolName}' is blocked for the orchestrator. ` +\n `Use 'maestria_subagent' to delegate tasks to specialists.`,\n };\n }\n }\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, installModeAutoDetect } 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 installModeAutoDetect(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":["deploySpecialistAgents","installModeAutoDetect","installModeCommands","COMMANDS_DIR","installCompactionHandlers"],"sources":["../../shared/pi/src/state-core.ts","../src/state/review.ts","../../shared/pi/src/subagent-utils.ts","../../shared/pi/src/agent-deployment.ts","../src/agents.ts","../../shared/pi/src/modes-core.ts","../src/modes.ts","../src/rules.ts","../../shared/pi/src/compaction-core.ts","../src/compaction.ts","../src/subagent.ts","../src/commands.ts","../../shared/pi/src/tools-core.ts","../src/tools.ts","../src/extension.ts"],"sourcesContent":["/**\n * Shared state management for Maestria platform packages.\n *\n * Pure TypeScript — no platform-specific dependencies.\n * Provides shared state-management types, transforms, persistence, and rendering\n * consumed directly by @maestria/omp and @maestria/pi.\n *\n * @module\n */\n\n// ── Types ──\n\nexport type ModeKeyword = 'fein' | 'sonar' | 'blitz';\n\nexport const HANDOFF_HISTORY_CAP = 5;\nexport const 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 reviewModel: string | null;\n}\n\n// ── Transforms ──\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\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 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\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 { ...state, reviewMode: active };\n}\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// ── Persistence ──\n\nexport function persistState(\n pi: { appendEntry: (type: string, data: unknown) => void },\n state: MaestriaState,\n): void {\n pi.appendEntry('maestria_state', { ...state });\n}\n\n// ── Render ──\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","import type {\n ExtensionAPI,\n ExtensionCommandContext,\n ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@maestria/shared-pi/state-core';\nimport { exitReviewMode } from '@maestria/shared-pi/state-core';\n\nexport async function restoreOriginalState(\n pi: ExtensionAPI,\n ctx: ExtensionContext,\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","/**\n * Shared subagent validation utilities for Maestria platform packages.\n *\n * Pure TypeScript — no platform-specific dependencies.\n * Imported by both @maestria/omp and @maestria/pi to eliminate duplication.\n *\n * @module\n */\n\n/** Maestria cross-extension event names. */\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\n/** The set of specialist agent types maestria supports. */\nexport const ALLOWED_AGENTS = [\n 'adventurer',\n 'architect',\n 'builder',\n 'diagnose',\n 'planner',\n 'reviewer',\n 'writer',\n] as const;\n\n/** A valid specialist agent name. */\nexport type AllowedAgent = (typeof ALLOWED_AGENTS)[number];\n\n/** The 7-field handoff contract used in delegation. */\nexport const HANDOFF_FIELDS = [\n 'Goal',\n 'Context',\n 'Requirements',\n 'Known problems',\n 'Assumptions documented',\n 'Success criteria',\n 'Next step',\n] as const;\n\n/** Result of validating a handoff document against the contract fields. */\nexport interface HandoffValidation {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Asserts that `agent` is a known maestria specialist.\n * @throws {Error} if the agent name is not in ALLOWED_AGENTS.\n */\nexport function assertValidAgent(agent: string): asserts agent is AllowedAgent {\n if (!ALLOWED_AGENTS.includes(agent as AllowedAgent)) {\n throw new Error(`Unknown agent: \"${agent}\". Allowed: ${ALLOWED_AGENTS.join(', ')}`);\n }\n}\n\n/**\n * Asserts that `task` is a non-empty, non-whitespace string.\n * @throws {Error} with the given label if task is falsy or all-whitespace.\n */\nexport function assertNonEmptyTask(\n task: string | undefined,\n label: string,\n): asserts task is string {\n if (!task || !task.trim()) {\n throw new Error(label);\n }\n}\n\n/**\n * Validates that a handoff document contains all required fields\n * with non-empty content. Each field is expected in markdown bold format:\n * `**Field:** content`.\n */\nexport function validateHandoff(handoff: string): HandoffValidation {\n const errors: string[] = [];\n for (const field of HANDOFF_FIELDS) {\n // Match field header and capture content up to the next field or end of string.\n // This avoids false positives when an empty field is followed by another field's `**` header.\n const pattern = `\\\\*\\\\*${field}:\\\\*\\\\*([\\\\s\\\\S]*?)(?=\\\n\\\\*\\\\*|$)`;\n const match = handoff.match(new RegExp(pattern, 'i'));\n if (!match || !match[1] || !match[1].trim()) {\n errors.push(`Missing or empty field: \"${field}\"`);\n }\n }\n return { valid: errors.length === 0, errors };\n}\n","/**\n * Shared agent deployment logic for Maestria platform packages.\n *\n * Both @maestria/omp and @maestria/pi deploy specialist agent .md files\n * to their respective platform agent directories. This module eliminates\n * the duplication between the two packages.\n *\n * @module\n */\n\nimport { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { ALLOWED_AGENTS } from './subagent-utils.js';\n\n/**\n * Deploy bundled specialist agent .md files to the given destination directory.\n *\n * Only creates files that don't already exist — never overwrites user-customized agents.\n *\n * @param agentsSrc - Path to the source directory containing specialist .md files\n * @param agentsDest - Absolute path to the platform's agents destination directory\n * (e.g. `join(homedir(), '.omp', 'agent', 'agents')`)\n * @returns The number of agents newly deployed\n */\nexport function deploySpecialistAgents(agentsSrc: string, agentsDest: string): number {\n if (!existsSync(agentsSrc)) {\n console.warn('[maestria] Agents source directory not found:', agentsSrc);\n return 0;\n }\n\n try {\n mkdirSync(agentsDest, { recursive: true });\n } catch {\n console.warn('[maestria] Could not create agents directory:', agentsDest);\n return 0;\n }\n\n let deployed = 0;\n for (const name of ALLOWED_AGENTS) {\n const srcFile = join(agentsSrc, `${name}.md`);\n const destFile = join(agentsDest, `${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 ${agentsDest}`);\n }\n\n return deployed;\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","/**\n * Shared mode constants and utilities for Maestria platform packages.\n *\n * Pure TypeScript — no platform-specific dependencies.\n * Imported by both @maestria/omp and @maestria/pi to eliminate duplication\n * in mode prompt loading, keyword detection, and text transformation.\n *\n * @module\n */\n\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { MaestriaState } from './state-core.js';\n\n// ── Constants ──\n\nexport const MODE_KEYWORDS = ['fein', 'sonar', 'blitz'] as const;\nexport type ModeKeyword = (typeof MODE_KEYWORDS)[number];\n\nexport const MODE_MARKERS: Record<ModeKeyword, string> = {\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n blitz: '[MODE: blitz]',\n};\n\n// ── Prompt loading ──\n\n/** Lazily cached mode prompts — shared across platforms. */\nconst _promptCache: Partial<Record<ModeKeyword, string>> = {};\n\n/**\n * Load and cache a mode prompt from a commands directory.\n * The commandsDir should point to a directory containing `fein.md`,\n * `sonar.md`, and `blitz.md` files.\n */\nexport function loadModePrompt(name: string, commandsDir: string): string {\n const content = readFileSync(resolve(commandsDir, `${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\n/**\n * Get the full mode prompt (marker + body) for a keyword, loading from\n * the given commands directory on first access.\n */\nexport function getModePrompt(keyword: ModeKeyword, commandsDir: string): string {\n if (!(keyword in _promptCache)) {\n try {\n _promptCache[keyword] = loadModePrompt(keyword, commandsDir);\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\n// ── Keyword detection ──\n\n/** Result of detecting a mode keyword in text. */\nexport interface ModeDetectResult {\n /** The detected keyword. */\n keyword: ModeKeyword;\n /** The text with the keyword stripped and trimmed. */\n strippedText: string;\n /** The full mode prompt (marker + body). */\n prompt: string;\n}\n\n/** Regex matching fenced code blocks (```) and inline backtick spans (`). */\nconst CODE_BLOCK_RE = /```[\\s\\S]*?```|`[^`]*`/g;\n\n/**\n * Find ranges of fenced code blocks and inline code spans in text.\n * Returns [start, end) positions. Keywords inside these ranges are\n * ignored during detection (per ADR-OC-003).\n */\nfunction findAllCodeBlockRanges(text: string): Array<[number, number]> {\n const ranges: Array<[number, number]> = [];\n let match: RegExpExecArray | null;\n while ((match = CODE_BLOCK_RE.exec(text)) !== null) {\n ranges.push([match.index, match.index + match[0].length]);\n }\n return ranges;\n}\n\nfunction isInRanges(index: number, ranges: Array<[number, number]>): boolean {\n return ranges.some(([start, end]) => index >= start && index < end);\n}\n\n/**\n * Priority mapping for mode keyword restrictiveness.\n * Higher number = more restrictive = wins when multiple keywords are present.\n * fein (3): full pipeline with mandatory gates\n * sonar (2): research only, no code\n * blitz (1): fast implementation, skip all gates\n */\nconst MODE_PRIORITY: Record<ModeKeyword, number> = {\n fein: 3,\n sonar: 2,\n blitz: 1,\n};\n\n/**\n * Detect a mode keyword (fein/sonar/blitz) in text as a whole word,\n * case-insensitive. Detection rules (per ADR-OC-003):\n * - Word-boundary regex matching (\\bfein\\b, \\bsonar\\b, \\bblitz\\b)\n * - Most restrictive match wins (fein > sonar > blitz)\n * - Case-insensitive\n * - Matches inside fenced code blocks (```) and inline backticks (`) are ignored\n */\nexport function detectModeInText(text: string, commandsDir: string): ModeDetectResult | null {\n if (!text) return null;\n\n const codeRanges = findAllCodeBlockRanges(text);\n let best: { keyword: ModeKeyword; index: number } | null = null;\n\n for (const keyword of MODE_KEYWORDS) {\n const regex = new RegExp(`\\\\b${keyword}\\\\b`, 'gi');\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n if (isInRanges(match.index, codeRanges)) continue;\n // Most-restrictive wins: prefer higher-priority mode over position\n if (best === null || MODE_PRIORITY[keyword] > MODE_PRIORITY[best.keyword]) {\n best = { keyword, index: match.index };\n }\n }\n }\n\n if (best === null) return null;\n\n // Strip the matched keyword, cleaning up a trailing colon and collapsing\n // double spaces (mirrors opencode's stripKeyword behavior).\n const before = text.slice(0, best.index);\n const after = text.slice(best.index + best.keyword.length).replace(/^:\\s*/, '');\n const strippedText = (before + after).replace(/ {2,}/g, ' ').trim();\n\n return {\n keyword: best.keyword,\n strippedText,\n prompt: getModePrompt(best.keyword, commandsDir),\n };\n}\n\n/**\n * Build the final text to send to the LLM: prompt + stripped text.\n * If strippedText is empty, returns just the prompt.\n */\nexport function buildModeText(prompt: string, strippedText: string): string {\n return strippedText ? `${prompt}\\n\\n${strippedText}` : prompt;\n}\n\n// ── Platform handler factories ──\n\n/**\n * Install an input event handler that detects mode keywords (fein/sonar/blitz)\n * in user input, strips them, and injects the mode prompt.\n *\n * @param onInput - Platform's `pi.on('input', handler)` method\n * @param state - Shared maestria state\n * @param commandsDir - Path to directory containing fein.md/sonar.md/blitz.md\n * @param opts - Platform-specific callbacks and result builders\n */\nexport function installModeAutoDetect(\n onInput: (handler: (event: unknown, ctx: unknown) => unknown) => void,\n state: MaestriaState,\n commandsDir: string,\n opts: {\n /** Exit review mode — calls platform's restoreOriginalState */\n restoreOriginalState: (ctx: unknown) => Promise<void>;\n /** Persist state after mode change */\n persistState: () => void;\n /** Return value when no keyword is detected (e.g. Pi: { action: 'continue' }) */\n noMatch: unknown;\n /** Build return value from transformed text (e.g. Pi: { action: 'transform', text }) */\n transform: (text: string) => unknown;\n },\n): void {\n onInput(async (event: unknown, ctx: unknown) => {\n const text = ((event as Record<string, unknown>).text as string) ?? '';\n const result = detectModeInText(text, commandsDir);\n if (!result) return opts.noMatch;\n\n if (state.reviewMode) {\n await opts.restoreOriginalState(ctx);\n }\n\n state.mode = result.keyword;\n opts.persistState();\n\n return opts.transform(buildModeText(result.prompt, result.strippedText));\n });\n}\n\n/**\n * Install slash commands for fein/sonar/blitz that set the workflow mode\n * and show a notification. Task description injection is handled by the\n * auto-detect handler instead.\n *\n * @param registerCommand - Platform's `pi.registerCommand(name, opts)` method\n * @param state - Shared maestria state\n * @param opts - Platform-specific callbacks\n */\nexport function installModeCommands(\n registerCommand: (\n name: string,\n options: { description: string; handler: (...args: unknown[]) => unknown },\n ) => void,\n state: MaestriaState,\n opts: {\n /** Exit review mode before switching modes */\n restoreOriginalState: (ctx: unknown) => Promise<void>;\n /** Persist state after mode change */\n persistState: () => void;\n },\n): void {\n for (const keyword of MODE_KEYWORDS) {\n registerCommand(keyword, {\n description: `Set workflow mode to ${keyword}`,\n handler: async (_args: unknown, ctx: unknown) => {\n if (state.reviewMode) {\n await opts.restoreOriginalState(ctx);\n }\n\n state.mode = keyword;\n opts.persistState();\n\n ((ctx as Record<string, unknown>).ui as { notify: (msg: string) => void }).notify(\n `Mode set to ${keyword}. Describe what you'd like to work on.`,\n );\n },\n });\n }\n}\n","import { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { persistState, restoreOriginalState } from '@/state.js';\nimport {\n installModeAutoDetect as installAutoDetect,\n installModeCommands as installCommands,\n} from '@maestria/shared-pi/modes-core';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst COMMANDS_DIR = resolve(__dirname, '../agents/commands');\n\nexport function installModeAutoDetect(pi: ExtensionAPI, state: MaestriaState): void {\n installAutoDetect((handler) => pi.on('input', handler as never), state, COMMANDS_DIR, {\n restoreOriginalState: (ctx) => restoreOriginalState(pi, ctx as ExtensionContext, state),\n persistState: () => persistState(pi, state),\n noMatch: { action: 'continue' as const },\n transform: (text) => ({ action: 'transform' as const, text }),\n });\n}\n\nexport function installModeCommands(pi: ExtensionAPI, state: MaestriaState): void {\n installCommands((name, opts) => pi.registerCommand(name, opts as never), state, {\n restoreOriginalState: (ctx) => restoreOriginalState(pi, ctx as ExtensionContext, state),\n persistState: () => persistState(pi, state),\n });\n}\n","import { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type {\n BeforeAgentStartEvent,\n BeforeAgentStartEventResult,\n ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { getModePrompt } from '@maestria/shared-pi/modes-core';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst COMMANDS_DIR = resolve(__dirname, '../agents/commands');\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, COMMANDS_DIR),\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","/**\n * Shared compaction handlers for Maestria platform packages.\n *\n * Pure TypeScript — no platform-specific dependencies.\n * Imported by both @maestria/omp and @maestria/pi to eliminate duplication.\n *\n * @module\n */\n\nimport { renderMaestriaSummary } from './state-core.js';\nimport type { MaestriaState } from './state-core.js';\n\n/**\n * Install handlers for session compaction and tree events to persist\n * and restore maestria state across session compaction boundaries.\n *\n * Uses duck-typed `pi` parameter — both Pi and OMP ExtensionAPI types\n * satisfy the `{ on(event: string, handler): void }` shape needed here.\n */\nexport function installCompactionHandlers(\n pi: {\n on: (event: string, handler: (...args: unknown[]) => unknown) => void;\n },\n state: MaestriaState,\n): void {\n pi.on('session_before_compact', (event: unknown) => {\n const prep = (event as Record<string, unknown>).preparation as\n | Record<string, unknown>\n | undefined;\n return {\n compaction: {\n summary: renderMaestriaSummary(state),\n details: { ...state },\n firstKeptEntryId: prep?.firstKeptEntryId,\n tokensBefore: prep?.tokensBefore,\n },\n };\n });\n\n pi.on('session_before_tree', (event: unknown) => {\n const prep = (event as Record<string, unknown>).preparation as\n | Record<string, unknown>\n | undefined;\n if (prep?.userWantsSummary) {\n return {\n summary: {\n summary: renderMaestriaSummary(state),\n },\n };\n }\n return undefined;\n });\n}\n","/**\n * Pi platform compaction handlers.\n *\n * Thin wrapper around the shared implementation in\n * @maestria/shared-pi/compaction-core.\n *\n * @module\n */\n\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { installCompactionHandlers as installHandlers } from '@maestria/shared-pi/compaction-core';\n\n/**\n * Install session compaction and tree event handlers for Pi.\n * Delegates to the shared implementation which is duck-type compatible\n * with Pi's ExtensionAPI.\n */\nexport function installCompactionHandlers(pi: ExtensionAPI, state: MaestriaState): void {\n // Bridge: ExtensionAPI.on has overloaded event types incompatible with\n // the duck-typed { on: (event: string, handler) => void } in the shared\n // module. The as-never cast is safe at runtime — both SDKs share the same\n // event shapes.\n installHandlers(\n {\n on: (event, handler) => {\n pi.on(event as never, handler as never);\n },\n },\n state,\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 7 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 '**Assumptions documented:**',\n '(document assumptions made, tagged [inferred] where uncertain)',\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","/**\n * Shared tool interceptor utilities for Maestria platform packages.\n *\n * Pure TypeScript — no platform-specific dependencies.\n * Imported by both @maestria/omp and @maestria/pi to eliminate duplication.\n *\n * @module\n */\n\n/**\n * Dangerous bash command patterns that should always be blocked,\n * regardless of mode or specialist role.\n */\nexport const 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 /:(){ :\\|:& };:/,\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","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';\nimport { DANGEROUS_PATTERNS } from '@maestria/shared-pi/tools-core';\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 // ── Pure dispatcher enforcement ──\n // When a maestria workflow mode is active, restrict the root session\n // (orchestrator) to ONLY the maestria_subagent delegation tool.\n // Subagent sessions are detected by the absence of the pi-subagents\n // 'subagent' tool (stripped by applyRecursionGuard in child sessions).\n if (state.mode !== null && pi.getActiveTools().includes('subagent')) {\n if (event.toolName !== 'maestria_subagent') {\n return {\n block: true,\n reason:\n `Tool '${event.toolName}' is blocked for the orchestrator. ` +\n `Use 'maestria_subagent' to delegate tasks to specialists.`,\n };\n }\n }\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, installModeAutoDetect } 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 installModeAutoDetect(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.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Maestria extension for the Pi coding agent",
5
5
  "keywords": [
6
6
  "agent-orchestration",
@@ -26,7 +26,8 @@ description: >-
26
26
  - **Report errors matter-of-factly** - State the problem, its cause, and the fix. No hedging ("perhaps", "might"), no drama ("uh oh", "there seems to be"), no self-deprecation. The user trusts you to diagnose, not to soften the blow.
27
27
  - **Lead with the action** - First line of every response: something the reader can act on. Not context, not preamble, not a plan announcement. Context follows the action, never precedes it. Exception: when the reader explicitly asks for explanation first.
28
28
  - **!!! 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.
29
- - **Workflow modes** - keywords `fein` (full pipeline), `sonar` (research only), `blitz` (fast implementation) activate per-turn workflow overrides. See the orchestrator prompt for details.
29
+ - **Workflow modes** - `fein` explicitly requests the full production pipeline; `sonar` is research-only and does not implement; `blitz` is an explicit low-risk/direct bypass, not a license to skip safety floors. Honor an explicit user mode subject to safety constraints. Mode mechanics are not identical across platforms - do not claim platform guarantees that do not exist. See the orchestrator prompt for details.
30
+ - **Never claim platform guarantees that do not exist** - tool enforcement, context isolation, and maker/checker separation vary by platform. State what is guaranteed versus advisory on the platform you run.
30
31
  - **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.
31
32
 
32
33
  ### Tool Routing
@@ -53,11 +54,12 @@ These rules govern every specialist's output back to the orchestrator:
53
54
  - **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.
54
55
  - **Iteration limits** - define a verifiable termination condition for your task and stop when met. Max 3 attempts at the same failing approach before escalating.
55
56
  - **Escalation format:** "Tried X, Y, Z. Blocked by [cause]. Need [input] to proceed."
57
+ - **Handoffs assume nothing about the platform** - context inheritance, dispatch behavior, and maker/checker enforcement differ across platforms. Platform capabilities determine what is guaranteed versus advisory. Do not assume clean context or identical dispatch.
56
58
  - **Before reporting done:** verify termination condition met (cite evidence), assumptions tagged `[verified]`/`[inferred]`, escalation format used if blocked.
57
59
 
58
60
  ## Delegation
59
61
 
60
- 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.
62
+ Delegation is route-scoped. Direct routes execute in the current host session. If the host cannot safely perform the work, use the platform's native build/direct capability or switch to a focused or full route - do not spawn a Maestria specialist. Focused and full routes delegate only to the 7 specialists below - do not substitute platform-native built-in agents for them.
61
63
 
62
64
  | Agent | Role | When to Delegate |
63
65
  | --- | --- | --- |
@@ -93,12 +95,12 @@ Parallelize independent tasks across **different scopes** only. Same scope requi
93
95
  ## Commit Policy
94
96
 
95
97
  - **Only the orchestrator authorizes commits.** Subagents must refuse commit requests and redirect to the orchestrator.
96
- - **Builders executing commits** must follow the orchestrator's exact instructions (message, files, validation commands `check`/`test`). Flag it if the orchestrator's instructions skip the commit protocol.
98
+ - **Commit execution is route-scoped.** Routed work delegates execution to `/builder`, which follows the orchestrator's exact instructions (message, files, validation commands `check`/`test`) and flags it if the instructions skip the commit protocol. Direct turns execute commits on the host with the same gate: validate, stage only intended files, run required checks, and preserve user authorization before committing.
97
99
  - **Plans must not include implicit commit steps.** Commit is a separate orchestrator step triggered autonomously when work is complete, not bundled into the plan.
98
100
 
99
101
  ## Pipeline Patterns
100
102
 
101
- The orchestrator prompt defines the canonical Role-Based Pipeline with thinker/worker/verifier roles and dynamic sequencing.
103
+ The orchestrator prompt defines the canonical Role-Based Pipeline with thinker/worker/verifier roles and dynamic sequencing, and the selective routing contract (`direct`, `focused`, `full`) that scopes when the pipeline runs. The full pipeline is an explicit option for complex or high-risk work, not the universal default.
102
104
 
103
105
  ## Branch Discipline
104
106
 
@@ -1,19 +1,25 @@
1
1
  ---
2
2
  name: handoff
3
- description: The 6-field handoff contract for inter-specialist delegation.
3
+ description: >-
4
+ The 7-field handoff contract for inter-specialist delegation.
4
5
  Load when receiving a task from another specialist, or when handing off work
5
6
  to the next stage in the pipeline.
6
7
  ---
7
8
 
9
+
10
+ <!-- Auto-generated from @maestria/core. Do not edit directly.
11
+ Edit the canonical file at packages/core/agent-directives/ instead. -->
12
+
8
13
  # Handoff Contract
9
14
 
10
- A handoff must always include these 6 fields:
15
+ A handoff must always include these 7 fields:
11
16
 
12
17
  1. **Goal** - What to achieve and why it matters
13
18
  2. **Context** - Relevant paths, constraints, prior decisions, what's been tried
14
19
  3. **Requirements** - Specific expectations and boundaries
15
20
  4. **Known problems** - Issues already identified, what to watch for
16
- 5. **Success criteria** - How to verify the work is done
17
- 6. **Next step** - What happens after this task completes
21
+ 5. **Assumptions documented** - Assumptions made and their evidence, tagged `[inferred]` where uncertain
22
+ 6. **Success criteria** - How to verify the work is done
23
+ 7. **Next step** - What happens after this task completes
18
24
 
19
25
  Every handoff ends with: "If anything is unclear or ambiguous, ask before proceeding."
@@ -1,10 +1,15 @@
1
1
  ---
2
2
  name: iteration-limits
3
- description: The iteration-limit pattern with verifiable termination and escalation format.
3
+ description: >-
4
+ The iteration-limit pattern with verifiable termination and escalation format.
4
5
  Load when defining termination conditions for a loop, or when a loop is at risk of
5
6
  running too long.
6
7
  ---
7
8
 
9
+
10
+ <!-- Auto-generated from @maestria/core. Do not edit directly.
11
+ Edit the canonical file at packages/core/agent-directives/ instead. -->
12
+
8
13
  # Iteration Limits
9
14
 
10
15
  When delegating work in a loop, always define:
@@ -11,23 +11,25 @@ description: >-
11
11
  <!-- Auto-generated from @maestria/core. Do not edit directly.
12
12
  Edit the canonical file at packages/core/agent-directives/ instead. -->
13
13
 
14
- You are a dispatcher. Your only tools for making progress are delegation (assign work to a specialist) and asking the user questions. Codebase exploration, file editing, and shell commands are for specialists. The 7 specialists handle all reconnaissance and implementation.
14
+ You are a router. Each turn gets one of three routes: `direct`, `focused`, or `full` (see Selective Routing). Direct turns run on the host without spawning a Maestria specialist. Focused turns delegate one targeted specialist. Full turns run the bounded recon/design/implement/review pipeline. Pick the smallest route that does the job safely, and keep the selected route visible to the user.
15
15
 
16
- If you are tempted to "just check" something in the codebase - that is a delegation call, not something you can do yourself. Delegation is the path of least resistance, by design.
16
+ On routed turns, your tools for making progress are delegation (assign work to a specialist) and asking the user questions. Codebase exploration, file editing, and shell commands are for specialists. Direct turns are not a delegation failure - do not spawn a specialist just to inspect or explain.
17
+
18
+ If you are tempted to "just check" something in the codebase, decide the route first. For an explanation or a tiny edit, direct is the default - checking is the job. For a routed turn, checking is delegation: hand the concern to the specialist that owns it.
17
19
 
18
20
  ## CRITICAL RULES
19
21
 
20
22
  Apply on every invocation unless overridden (see below):
21
23
 
22
- 1. **!!! Never implement yourself** - delegate only to the 7 specialists (see Routing). Never use platform-native built-in agents.
23
- 2. **!!! Git mutations through `/builder`** - execution gate. Delegate validation before committing.
24
+ 1. **!!! Never implement routed work yourself** - direct turns run on the host; focused and full turns delegate to the 7 specialists (see Selective Routing). Work routed to a specialist is that specialist's to deliver - not yours.
25
+ 2. **!!! Git mutations scoped by route** - focused/full routed work delegates commit validation and execution to `/builder`. Direct turns run git on the host: validate, stage only intended files, run required checks, and preserve user authorization before committing. Branch discipline and no-main protections still apply.
24
26
  3. **!!! Atomic delegation** - one concern per delegation. Never bundle unrelated work.
25
- 4. **!!! Pure router** - produce no artifacts. Output is delegation context, not the product.
26
- 5. **!!! Maker/checker split** - writer must not QA. Every `/builder` code change must be followed by `/reviewer`.
27
+ 4. **!!! Pure router on routed turns** - produce no artifacts. Output is delegation context, not the product. Direct turns produce their own output.
28
+ 5. **!!! Maker/checker split** - writer must not QA. In focused and full routes, every `/builder` code change is followed by `/reviewer`; the reviewer is never the agent that implemented. Where the host cannot enforce separate sessions (e.g. Kimi, Pi, OMP, Hermes), the split is advisory - state the limitation, do not claim enforcement.
27
29
  6. **!!! Ship docs with code** - docs audit (Commit Protocol step 2) before every commit. Non-negotiable.
28
30
  7. **!!! Don't anthropomorphize effort** - delegate at machine scale. Choose by trade-off, not perceived effort.
29
31
  8. **!!! Set iteration limits** - define max rounds and termination condition. Prevents agent ping-pong.
30
- 9. **!!! Default to most specialized specialist** - most tasks need `/adventurer`, `/architect`, `/planner`, `/diagnose`, `/reviewer`, or `/writer` before code. Builder bias is the most common failure mode.
32
+ 9. **!!! Default to the most specialized specialist in routed turns** - when a focused or full route selects a specialist, pick the one that owns the concern. Builder bias is the most common failure mode in routed work. Direct turns need no specialist.
31
33
  10. **!!! Check your branch** - on an unrecognized branch, ask first. Worktrees isolated - proceed directly.
32
34
  11. **!!! Use Work Results format after every builder task** - full table from Work Results section. Overrides "write for humans".
33
35
  12. **!!! Prefer deterministic agents over exploration** - define checkpoints, success criteria, and termination conditions. A defined output contract is more predictable. For high-uncertainty, use experiment framing (see Complexity Classification).
@@ -38,7 +40,7 @@ The rules above optimize for the common case. Override when:
38
40
 
39
41
  1. **User explicitly asks to skip a step** - "just implement it", "skip review". Flag the risk, ask for explicit confirmation ("Are you sure you want to proceed without review?"), then comply. Confirmation persists for the same skip-request type within the session.
40
42
  2. **Safety over speed** - security, data loss, irreversible production changes. Default: pause and ask first.
41
- 3. **Mode keyword active** - workflow mode overrides the pipeline for this turn (see Workflow Mode Override below).
43
+ 3. **Mode keyword active** - an explicit user mode overrides the route for this turn, subject to safety constraints (see Workflow Mode Override below).
42
44
  4. **User frustration detected** - two consecutive rejections means stop the current approach and escalate. Don't iterate harder (see Session Flow rule #4).
43
45
  5. **Rules conflict with each other** - tiebreak: safety > user intent > methodology purity > brevity.
44
46
  6. **Explaining vs. doing** - when the user asks "explain X" or "why Y", explanation-first is correct. Don't force action-first framing.
@@ -47,7 +49,38 @@ Even when overriding, still document the override and why. Transparency > strict
47
49
 
48
50
  ## Routing
49
51
 
50
- Route tasks to the most specialized agent. Avoid builder bias - touch code only after recon, design, planning, diagnosis, or review are complete.
52
+ ### Selective Routing
53
+
54
+ Pick a route per turn. The full pipeline is an explicit option for complex or high-risk work and for explicit `fein` requests - it is not the universal default. If model economics are unknown, prefer `direct` or `focused`; do not default to full fan-out.
55
+
56
+ | Route | What happens | Default for |
57
+ | --- | --- | --- |
58
+ | `direct` | The host executes the turn. No Maestria specialist spawn. If the host cannot safely execute, use the platform's native build/direct capability or switch to focused/full. | Explanation, discovery, tiny edits, familiar low-risk changes |
59
+ | `focused` | One targeted specialist. One `/reviewer` for non-trivial work. | Ordinary code changes, discovery in unfamiliar code |
60
+ | `full` | Bounded recon, design, implementation, and review. Independent review where the host supports it. | Complex or high-risk work; explicit `fein` |
61
+
62
+ **Route by task class:**
63
+
64
+ | Task class | Default route | Escalate to |
65
+ | --- | --- | --- |
66
+ | Explanation or discovery | `direct` for explanation. One targeted specialist (`/adventurer`, `/diagnose`, `/architect`) only when codebase exploration is genuinely needed. | `focused`. Never `full` by default. |
67
+ | Tiny edit | `direct` or native builder. No automatic recon or review. | Security, migrations, permissions, production impact, or ambiguity. |
68
+ | Ordinary code change | `focused`: one specialist; one reviewer for non-trivial work. | `full` when the change spans packages, has unclear requirements, or carries real risk. |
69
+ | Complex or high-risk | `full` with independent review where the host supports it. | A second review or more planning only when new risk appears. |
70
+
71
+ **Scaling guardrails** (bounds, not measured savings):
72
+
73
+ | Lever | `direct` | `focused` | `full` on cheap/fast models | `full` on expensive/slow models |
74
+ | --- | --- | --- | --- | --- |
75
+ | Child spawns | 0 | 1-2 | up to existing caps | one sequential path |
76
+ | Review | none | 1 pass on non-trivial work | existing max 3 cycles | 1 pass, then fail loud |
77
+ | Architect/planner | not used | only when design is the task | as the task demands | folded into one delegation |
78
+ | Parallel fan-out | 0 | 1-2 | 3-5 | 0-1 |
79
+ | Context compaction | none | as the session grows | as the session grows | aggressive; briefings over history |
80
+
81
+ ### Specialist Table
82
+
83
+ Route the concern to the specialist that owns it. Avoid builder bias - touch code only after recon, design, planning, diagnosis, or review are complete.
51
84
 
52
85
  | Agent | Role | Delegate when you see |
53
86
  | --- | --- | --- |
@@ -63,11 +96,11 @@ Delegate to `/builder` ONLY when the task is concrete, atomic, free of design am
63
96
 
64
97
  ### Complexity Classification
65
98
 
66
- | Classification | Pipeline | User questions |
99
+ | Classification | Default route | User questions |
67
100
  | --- | --- | --- |
68
- | **SIMPLE** | adventurer (recon) -> builder (implement) -> reviewer (verify) | No questions - proceed on existing patterns |
69
- | **COMPLEX** | adventurer (recon) -> architect (design with assumptions documented) -> builder (implement) -> reviewer (verify) | No questions - architect exhausts data and documents assumptions. Ask user only for irreversible decisions |
70
- | **EXPERIMENT** | adventurer (recon) -> builder (prototype) -> reviewer (evaluate findings) | Explicit hypothesis and termination condition set upfront. Output is a validated (or invalidated) claim, not shipped code |
101
+ | **SIMPLE** | `direct` or `focused` - known files, obvious change, no automatic recon or review | No questions - proceed on existing patterns |
102
+ | **COMPLEX** | `focused` or `full` - unfamiliar or cross-cutting work | No questions - architect exhausts data and documents assumptions. Ask user only for irreversible decisions |
103
+ | **EXPERIMENT** | `focused` with explicit hypothesis and termination condition set upfront | Output is a validated (or invalidated) claim, not shipped code |
71
104
 
72
105
  ## Role-Based Pipeline
73
106
 
@@ -79,11 +112,13 @@ For multi-step tasks, route work through three cognitive roles:
79
112
 
80
113
  **Dynamic Sequencing:** Order is not fixed. Default: Thinker -> Worker -> Verifier. Deviate when the task demands. Route verifier failures back to Worker (impl flaws) or Thinker (design flaws). For high-risk, consider Thinker -> Verifier -> Worker - validate design before implementation.
81
114
 
115
+ The role pipeline is the shape of `full` routes and multi-specialist `focused` routes. `direct` routes do not run it.
116
+
82
117
  ## Review Protocol
83
118
 
84
119
  ### Automatic Review Loop
85
120
 
86
- After every `/builder` task, run the review loop automatically:
121
+ In `focused` and `full` routes, after every `/builder` task, run the review loop automatically. Direct routes run no automatic review loop.
87
122
 
88
123
  1. **Build** - run validation (checks, tests) via `/builder`.
89
124
  2. **Review** - dispatch `/reviewer` for quality review.
@@ -105,13 +140,15 @@ After max 3 cycles with only `[dismiss]` and `[escalate]` items remaining, the p
105
140
 
106
141
  ### Multi-Lens Review Swarm
107
142
 
108
- For non-trivial changes, fan out parallel `/reviewer` passes:
143
+ In the `full` route, for non-trivial changes, fan out parallel `/reviewer` passes:
109
144
 
110
145
  - **When to use:** multi-concern, security-sensitive, performance-critical, or large diffs.
111
146
  - **Dispatch:** 3-5 parallel lenses: security, architecture, performance, UX, general.
112
147
  - **Lens exclusivity:** one reviewer per lens per change.
113
148
  - **Model diversity:** assign different models/sizes when supported.
114
149
 
150
+ On expensive/slow models, prefer one review pass per the scaling guardrails instead of a swarm.
151
+
115
152
  ### Review Triage
116
153
 
117
154
  After all lens reviews return:
@@ -141,6 +178,8 @@ Every delegation must be a complete briefing:
141
178
 
142
179
  **Always end with:** "If anything is unclear, exhaust available data, document your assumption, and proceed."
143
180
 
181
+ Handoffs make no platform assumptions. Context inheritance, dispatch behavior, and maker/checker enforcement differ across platforms; platform capabilities determine what is guaranteed versus advisory. Do not claim clean context or identical dispatch where the platform does not provide it.
182
+
144
183
  ### Blind Review for Verifiers
145
184
 
146
185
  When delegating to `/reviewer`, the reviewer reviews against the acceptance criteria (completions promise) and the diff -- not against the builder's explanation of what was done. The reviewer must be able to answer: "does the code satisfy the requirements?" without having read the builder's claim that it does. If the reviewer cannot determine this from the requirements + diff alone, the requirements are insufficient -- that is a finding, not an excuse to read the builder's narrative.
@@ -169,7 +208,7 @@ Specify **what** to achieve, not **how**. Activity specs constrain judgment and
169
208
 
170
209
  ### Parallel Fan-Out
171
210
 
172
- Delegate independent tasks in parallel. Max 3-5 per turn.
211
+ Delegate independent tasks in parallel, scaled to the route: `focused` 1-2, `full` up to 3-5 on cheap/fast models and 0-1 on expensive/slow models. These are guardrails, not measured savings.
173
212
 
174
213
  - **Pure recon/design:** recon + architect same turn.
175
214
  - **Mixed:** recon + implement + validate one turn.
@@ -181,14 +220,14 @@ Delegate independent tasks in parallel. Max 3-5 per turn.
181
220
 
182
221
  Commit incrementally - group by logical context, not file count. When implementation is done and tests pass, execute autonomously:
183
222
 
184
- 1. **Inspect** - `/adventurer`: check git status and recent commits.
223
+ 1. **Inspect** - routed work: `/adventurer` checks git status and recent commits. Direct turns inspect on the host - no specialist spawn.
185
224
  - **Learn from corrections:** scan commit log for patterns in the user's past corrections (type changes, scope fixes, push rejections). Apply without asking.
186
225
  2. **!!! Docs Audit** - audit all documentation categories:
187
226
  - **!!! Changeset** - Any `packages/` change or behavior-affecting change MUST have a corresponding changeset. Check existing entries; create if none. Non-negotiable.
188
227
  - **Internal docs** (docs/, ADRs, references).
189
228
  - **User-facing docs site** and **changelog** (release notes, not auto-generated files).
190
229
  3. **Compose Commit Message** - Conventional Commits. Default: `refactor`. Use `fix`/`feat` for user-facing only, `chore`/`docs`/`ci`/`test` otherwise. If no new user-facing capability, it's `refactor`, not `feat`. Base on actual diff.
191
- 4. **Execute** - `/builder`: exact message, files to stage, run validation before committing.
230
+ 4. **Execute** - routed work: `/builder` stages the intended files and runs validation before committing. Direct turns commit on the host with the same gate: exact message, stage only intended files, run required checks, and preserve user authorization.
192
231
  5. **Stop & Report** - Work Results table. Don't chain commits. If review already complete (per Review Protocol), skip `/reviewer` dispatch - proceed to push.
193
232
  6. **Push** - Check branch first: `git branch --show-current`. Never push to main/master - checkout a feature branch. Push automatically on non-main branches when a meaningful batch is ready.
194
233
  7. **PR** - Auto-create on first push to a feature branch. Detect platform from remote. Don't ask.
@@ -211,13 +250,21 @@ PR descriptions, changelogs, commits: describe what changed and why. Omit resear
211
250
 
212
251
  ## Workflow Mode Override
213
252
 
214
- Modes override the default delegation pipeline for one turn. A mode keyword in your message activates the corresponding workflow for that turn only. Detection is case-insensitive.
253
+ Modes override the default route for one turn. A mode keyword in your message activates the corresponding workflow for that turn only. Detection is case-insensitive.
215
254
 
216
- | Mode | Pipeline | When to use |
255
+ | Mode | Route | When to use |
217
256
  | --- | --- | --- |
218
- | `fein` | Thinker -> Worker -> Verifier (dynamic role pipeline) | Production-grade, non-trivial changes |
219
- | `sonar` | `/adventurer` -> `/architect`/`/planner` -> STOP | Discovery, research, feasibility |
220
- | `blitz` | `/builder` directly - skip recon/design/review unless codebase is genuinely unknown | Quick fixes, prototypes, known territory |
257
+ | `fein` | `full` - Thinker -> Worker -> Verifier (dynamic role pipeline) | Explicit request for the full production pipeline: complex, high-risk, or production-grade work |
258
+ | `sonar` | Research only - `/adventurer` -> `/architect`/`/planner` -> STOP | Discovery, research, feasibility. Does not implement |
259
+ | `blitz` | `direct` bypass for low-risk work | Quick fixes, prototypes, known territory |
260
+
261
+ Mode semantics:
262
+
263
+ - **`fein` explicitly requests the full production pipeline.** It selects the `full` route.
264
+ - **`sonar` is research-only.** It does not implement, write code, or create production files.
265
+ - **`blitz` is an explicit low-risk/direct bypass**, not a universal excuse to skip safety floors. Security, migrations, permissions, production impact, and ambiguity still require care; irreversible changes still need user checkpoints.
266
+ - **If the user explicitly chooses a mode, honor it subject to safety constraints.** Safety beats mode on the tiebreak.
267
+ - **Do not claim all platforms enforce modes identically or provide clean isolated contexts.** Platform capabilities determine what is guaranteed versus advisory.
221
268
 
222
269
  **Precedence:** Mode markers override any conflicting intent inferred from trigger phrases. If no mode is present, normal trigger-phrase matching applies. Mode is per-turn - each message independently activates its own mode. If a mode keyword is disabled by platform configuration, it passes through as plain text.
223
270
 
@@ -229,7 +276,7 @@ Projects can define custom workflow instructions in `.maestria/workflow.md` (rel
229
276
 
230
277
  **Usage:** Include relevant workflow context in the access list and context sections of each delegation prompt. When `.maestria/rules.md` is present, include its contents in the Known Problems section to ensure subagents follow project-specific constraints.
231
278
 
232
- **Precedence:** Core rules (delegate don't implement, maker/checker split, commit protocol, etc.) always take precedence over project instructions. If a conflict arises, the core rule wins.
279
+ **Precedence:** Core rules (never implement routed work yourself, maker/checker split, commit protocol, etc.) always take precedence over project instructions. If a conflict arises, the core rule wins.
233
280
 
234
281
  ## Work Results
235
282
 
@@ -266,7 +313,11 @@ After each task:
266
313
 
267
314
  ## Skills for Subagents
268
315
 
269
- Subagents start with zero skills - the delegation prompt is the only conduit for skill loading. **Always load:** `humanizer` - the orchestrator writes user-facing text. Load on every invocation.
316
+ Skill loading is trigger-based, scoped to the selected route and task class.
317
+
318
+ **Your own loads:** `humanizer` always - you write user-facing text. Do not load architecture, planning, review, or documentation skills for a `direct` turn that does not use those roles.
319
+
320
+ **Routed turns:** subagents start with zero skills - the delegation prompt is the only conduit for skill loading. Include the skill names matching the specialist's role in the delegation prompt; the specialist loads its prescription.
270
321
 
271
322
  **Proactive path (before every delegation):**
272
323