@maestria/pi 0.4.1 → 0.5.1

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
@@ -4,21 +4,45 @@ A [Pi coding agent](https://pi.software/) extension that brings Maestria's struc
4
4
 
5
5
  ## Features
6
6
 
7
- - **8 Specialist Prompts** - Orchestrator, Adventurer, Architect, Builder, Diagnose, Planner, Reviewer, Writer
7
+ - **4 Methodology Skills** - Orchestrator dispatcher, global agent rules, handoff contract, and iteration limits - automatically injected into every session via Pi's standard skill system (`SKILL.md` files registered in `pi.skills`)
8
8
  - **3 Workflow Modes** - `fein` (full pipeline), `sonar` (research only), `blitz` (fast implementation)
9
- - **Global Rules Injection** - Automatically injects orchestration rules via `before_agent_start`
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
11
  - **Subagent Dispatch** - Delegation via `@gotgenes/pi-subagents` with 6-field handoff validation
12
12
  - **Maker/Checker Split** - Review mode blocks destructive tools. Dangerous bash patterns flagged.
13
- - **2 Methodology Skills** - Handoff contract + iteration limits
14
13
 
15
14
  ## Installation
16
15
 
16
+ ### Recommended: via maestria CLI
17
+
17
18
  ```bash
19
+ pnpx maestria@latest install pi
20
+ ```
21
+
22
+ The CLI automatically installs both `@gotgenes/pi-subagents` (required peer dependency for subagent dispatch) and `@maestria/pi` in the correct order.
23
+
24
+ ### Alternative: manual Pi CLI
25
+
26
+ ```bash
27
+ # Install required peer dependency first
28
+ pi install npm:@gotgenes/pi-subagents
29
+
30
+ # Install the extension
18
31
  pi install npm:@maestria/pi
19
32
  ```
20
33
 
21
- Alternatively, use the [maestria CLI](https://maestria.sznm.dev/cli/) to manage installation across all platforms from a single command.
34
+ ### Uninstall
35
+
36
+ ```bash
37
+ # Via maestria CLI (removes @maestria/pi)
38
+ pnpx maestria@latest uninstall pi
39
+ ```
40
+
41
+ Note: `@gotgenes/pi-subagents` is a shared dependency that other Pi extensions may use. Only remove it if no other extensions need it:
42
+
43
+ ```bash
44
+ pi uninstall @gotgenes/pi-subagents
45
+ ```
22
46
 
23
47
  ## Commands
24
48
 
@@ -1,3 +1,14 @@
1
+ ---
2
+ description: >-
3
+ Codebase reconnaissance specialist. Maps unknown territory, traces
4
+ call chains and dependencies, discovers module relationships, and
5
+ produces structured recon reports for downstream specialists.
6
+ tools: read, bash, grep, find, ls, glob
7
+ prompt_mode: append
8
+ inherit_context: true
9
+ ---
10
+
11
+
1
12
  <!-- Auto-generated from @maestria/core. Do not edit directly.
2
13
  Edit the canonical file at packages/core/agent-directives/ instead. -->
3
14
 
@@ -1,3 +1,14 @@
1
+ ---
2
+ description: >-
3
+ Architecture decision specialist. Evaluates implementation approaches
4
+ with trade-off analysis, produces Architecture Decision Records (ADRs),
5
+ and documents technical decisions with business context.
6
+ tools: read, bash, grep, find, ls
7
+ prompt_mode: append
8
+ inherit_context: true
9
+ ---
10
+
11
+
1
12
  <!-- Auto-generated from @maestria/core. Do not edit directly.
2
13
  Edit the canonical file at packages/core/agent-directives/ instead. -->
3
14
 
@@ -1,3 +1,13 @@
1
+ ---
2
+ description: >-
3
+ Focused implementation specialist. Executes one atomic, verifiable
4
+ unit of work per invocation with minimal context and clean diffs.
5
+ tools: read, bash, grep, find, ls, write, edit
6
+ prompt_mode: append
7
+ inherit_context: true
8
+ ---
9
+
10
+
1
11
  <!-- Auto-generated from @maestria/core. Do not edit directly.
2
12
  Edit the canonical file at packages/core/agent-directives/ instead. -->
3
13
 
@@ -1,3 +1,14 @@
1
+ ---
2
+ description: >-
3
+ Systematic bug tracing specialist. Follows a 6-step regression protocol
4
+ from error message to root cause to prevention, covering blast radius
5
+ and similar-problem scanning.
6
+ tools: read, bash, grep, find, ls
7
+ prompt_mode: append
8
+ inherit_context: true
9
+ ---
10
+
11
+
1
12
  <!-- Auto-generated from @maestria/core. Do not edit directly.
2
13
  Edit the canonical file at packages/core/agent-directives/ instead. -->
3
14
 
@@ -1,3 +1,14 @@
1
+ ---
2
+ description: >-
3
+ Implementation planning specialist. Breaks complex features into
4
+ phased milestones with dependencies, timelines, verification criteria,
5
+ and rollback points.
6
+ tools: read, bash, grep, find, ls
7
+ prompt_mode: append
8
+ inherit_context: true
9
+ ---
10
+
11
+
1
12
  <!-- Auto-generated from @maestria/core. Do not edit directly.
2
13
  Edit the canonical file at packages/core/agent-directives/ instead. -->
3
14
 
@@ -1,3 +1,14 @@
1
+ ---
2
+ description: >-
3
+ Code review specialist. Reviews for correctness, edge cases, security,
4
+ performance, and maintainability. Supports multi-lens review swarms
5
+ with fix/dismiss/escalate triage.
6
+ tools: read, bash, grep, find, ls, glob
7
+ prompt_mode: append
8
+ inherit_context: true
9
+ ---
10
+
11
+
1
12
  <!-- Auto-generated from @maestria/core. Do not edit directly.
2
13
  Edit the canonical file at packages/core/agent-directives/ instead. -->
3
14
 
@@ -1,3 +1,14 @@
1
+ ---
2
+ description: >-
3
+ Documentation specialist. Creates clear, structured documentation
4
+ following progressive disclosure patterns for READMEs, API docs,
5
+ changelogs, and Architecture Decision Records.
6
+ tools: read, bash, grep, find, ls, write, edit
7
+ prompt_mode: append
8
+ inherit_context: true
9
+ ---
10
+
11
+
1
12
  <!-- Auto-generated from @maestria/core. Do not edit directly.
2
13
  Edit the canonical file at packages/core/agent-directives/ instead. -->
3
14
 
@@ -1,16 +1,17 @@
1
- import{readFileSync as e}from"node:fs";import{dirname as t,join as n}from"node:path";import{fileURLToPath as r}from"node:url";import{Type as i}from"typebox";import{SUBAGENT_EVENTS as a}from"@gotgenes/pi-subagents";import{isToolCallEventType as o}from"@earendil-works/pi-coding-agent";function s(){return{mode:null,activeTask:``,completionPromise:``,specialistsDelegated:[],blockers:[],filesModified:[],filesRead:[],handoffHistory:[],reviewMode:!1,originalModel:null,originalTools:null,subagentStatus:{},reviewModel:null}}function c(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 l(e){return{state:{...e,reviewMode:!1,originalModel:null,originalTools:null},originalModel:e.originalModel,originalTools:e.originalTools}}async function u(e,t,n){let{state:r,originalModel:i,originalTools:a}=l(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)}function d(e,t){e.appendEntry(`maestria_state`,{...t})}async function f(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}}function p(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(`
1
+ import{existsSync as e,mkdirSync as t,readFileSync as n,writeFileSync as r}from"node:fs";import{dirname as i,join as a}from"node:path";import{fileURLToPath as o}from"node:url";import{homedir as s}from"node:os";import{Type as c}from"typebox";import{SUBAGENT_EVENTS as l}from"@gotgenes/pi-subagents";import{isToolCallEventType as u}from"@earendil-works/pi-coding-agent";function d(){return{mode:null,activeTask:``,completionPromise:``,specialistsDelegated:[],blockers:[],filesModified:[],filesRead:[],handoffHistory:[],reviewMode:!1,originalModel:null,originalTools:null,subagentStatus:{},reviewModel:null}}function f(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 p(e){return{state:{...e,reviewMode:!1,originalModel:null,originalTools:null},originalModel:e.originalModel,originalTools:e.originalTools}}async function m(e,t,n){let{state:r,originalModel:i,originalTools:a}=p(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)}function h(e,t){e.appendEntry(`maestria_state`,{...t})}async function g(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}}function _(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
- `)}const m=[`fein`,`sonar`,`blitz`],h={fein:[`## MODE: fein (Full Pipeline)`,``,`Execute the complete fein pipeline: mandatory reconnaissance`,`(/adventurer) → design/plan (/architect or /planner) →`,`implementation (/builder) → review (/reviewer).`,`Do NOT skip any phase unless the user explicitly overrides`,`in the same turn.`].join(`
3
+ `)}const v=a(i(o(import.meta.url)),`..`,`agents`),y=a(s(),`.pi`,`agent`,`agents`),b=[`adventurer`,`architect`,`builder`,`diagnose`,`planner`,`reviewer`,`writer`];function x(i){let o=v;if(!e(o)){console.warn(`[maestria] Agents source directory not found:`,o);return}try{t(y,{recursive:!0})}catch{console.warn(`[maestria] Could not create agents directory:`,y);return}let s=0;for(let t of b){let i=a(o,`${t}.md`),c=a(y,`${t}.md`);if(!e(i)){console.warn(`[maestria] Agent source not found: ${t}.md`);continue}if(!e(c))try{r(c,n(i,`utf-8`),`utf-8`),s++}catch(e){console.warn(`[maestria] Failed to deploy agent ${t}:`,e)}}s>0&&console.log(`[maestria] Deployed ${s} specialist agents to ${y}`)}const S=[`fein`,`sonar`,`blitz`],C={fein:[`## MODE: fein (Full Pipeline)`,``,`Execute the complete fein pipeline: mandatory reconnaissance`,`(/adventurer) → design/plan (/architect or /planner) →`,`implementation (/builder) → review (/reviewer).`,`Do NOT skip any phase unless the user explicitly overrides`,`in the same turn.`].join(`
4
4
  `),sonar:[`## MODE: sonar (Research Only)`,``,`Execute research only: /adventurer (recon) →`,`/architect or /planner (design/plan) → STOP.`,`Do NOT implement anything. Return findings.`].join(`
5
5
  `),blitz:[`## MODE: blitz (Fast Implementation)`,``,`Execute fast implementation via /builder directly.`,`Skip reconnaissance and design unless the codebase`,`is genuinely unknown. Skip review unless the result`,`needs validation.`].join(`
6
- `)},g={fein:`[MODE: fein]`,sonar:`[MODE: sonar]`,blitz:`[MODE: blitz]`};function _(e){return`${g[e]}\n\n${h[e]}`}function v(e,t){for(let n of m)e.registerCommand(n,{description:`Set workflow mode to ${n}`,handler:async(r,i)=>{if(t.reviewMode&&await u(e,i,t),t.mode=n,d(e,t),r.trim()){let t=[_(n),``,`Run the maestria default pipeline on: ${r}`].join(`
7
- `);e.sendUserMessage(t,{deliverAs:`steer`})}else i.ui.notify(`Mode set to ${n}. Describe what you'd like to work on.`)}})}const y=e(n(t(r(import.meta.url)),`..`,`rules`,`AGENTS.md`),`utf-8`);function b(e){return(t,n)=>{let r=[y,``,t.systemPrompt];return e.mode&&(r.push(``,_(e.mode)),r.push(``,`The user has set workflow mode to "${e.mode}". Honor this mode throughout the session until changed via /command.`)),{systemPrompt:r.join(`
8
- `)}}}function x(e,t){e.on(`session_before_compact`,e=>({compaction:{summary:p(t),details:{...t},firstKeptEntryId:e.preparation.firstKeptEntryId,tokensBefore:e.preparation.tokensBefore}})),e.on(`session_before_tree`,e=>{if(e.preparation.userWantsSummary)return{summary:{summary:p(t)}}})}const S={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`},C=[`adventurer`,`architect`,`builder`,`diagnose`,`planner`,`reviewer`,`writer`],w=new Set([`completed`,`steered`,`aborted`,`stopped`,`error`]);function T(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:i.Object({agent:i.Optional(i.String({description:`Specialist agent name`})),task:i.Optional(i.String({description:`Task description for the subagent`})),tasks:i.Optional(i.Array(i.Object({agent:i.String(),task:i.String()}),{description:`Array of task objects for parallel or chain dispatch`})),mode:i.Optional(i.Union([i.Literal(`parallel`),i.Literal(`chain`),i.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`){if(!C.includes(r.agent))throw Error(`Unknown agent: "${r.agent}". Allowed: ${C.join(`, `)}`);if(!r.task||!r.task.trim())throw Error(`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){if(!C.includes(e.agent))throw Error(`Unknown agent: "${e.agent}". Allowed: ${C.join(`, `)}`);if(!e.task||!e.task.trim())throw Error(`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){if(!C.includes(e.agent))throw Error(`Unknown agent: "${e.agent}". Allowed: ${C.join(`, `)}`);if(!e.task||!e.task.trim())throw Error(`Task description is required for all tasks`)}}try{let{getSubagentsService:n}=await import(`@gotgenes/pi-subagents`),o=n();if(typeof o.spawn!=`function`)throw Error(`Subagents service unavailable or incomplete`);async function l(e,t,n){let r=0,s=o.getRecord(e);for(;s&&!w.has(s.status)&&r<120;){if(i?.aborted)throw Error(`Maestria subagent call aborted`);await new Promise(e=>setTimeout(e,500)),s=o.getRecord(e),r++,n&&a?.({content:[{type:`text`,text:`${t} running... (${Math.round(r*500/1e3)}s)`}]})}if(s&&!w.has(s.status))throw Error(`Subagent ${e} timed out after 60000ms`);if(!s)throw Error(`Subagent ${e} was cleaned up before completion`);return s}if(s===`single`){let n=r.agent,i=r.task,a=o.spawn(n,i,{description:i.slice(0,80),foreground:!0,inheritContext:!0}),s=c(t,`orchestrator`,n,i);Object.assign(t,s),e.appendEntry(`maestria_state`,t);let u=await l(a,`Subagent ${n}`,!0);return{content:[{type:`text`,text:u.result??u.error??`No output.`}],details:{subagentId:a}}}if(s===`parallel`){let n=r.tasks;a?.({content:[{type:`text`,text:`Spawning ${n.length} parallel subagents...`}]});let i=[];for(let e of n){let n=o.spawn(e.agent,e.task,{description:e.task.slice(0,80),foreground:!0,inheritContext:!0});i.push(n);let r=c(t,`orchestrator`,e.agent,e.task);Object.assign(t,r)}e.appendEntry(`maestria_state`,t);let s=await Promise.all(i.map((e,t)=>l(e,`${n[t].agent} (${t+1}/${n.length})`,!1)));a?.({content:[{type:`text`,text:`All ${n.length} parallel subagents completed.`}]});let u=[`## 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.`;u.push(`### ${e+1}: ${t.agent}`),u.push(i)}return{content:[{type:`text`,text:u.join(`
6
+ `)},w={fein:`[MODE: fein]`,sonar:`[MODE: sonar]`,blitz:`[MODE: blitz]`};function T(e){return`${w[e]}\n\n${C[e]}`}function E(e,t){for(let n of S)e.registerCommand(n,{description:`Set workflow mode to ${n}`,handler:async(r,i)=>{if(t.reviewMode&&await m(e,i,t),t.mode=n,h(e,t),r.trim()){let t=[T(n),``,`Run the maestria default pipeline on: ${r}`].join(`
7
+ `);e.sendUserMessage(t,{deliverAs:`steer`})}else i.ui.notify(`Mode set to ${n}. Describe what you'd like to work on.`)}})}function D(e){return(t,n)=>{if(e.mode)return{systemPrompt:[t.systemPrompt,``,T(e.mode),``,`The user has set workflow mode to "${e.mode}". Honor this mode throughout the session until changed via /command.`].join(`
8
+ `)}}}function O(e,t){e.on(`session_before_compact`,e=>({compaction:{summary:_(t),details:{...t},firstKeptEntryId:e.preparation.firstKeptEntryId,tokensBefore:e.preparation.tokensBefore}})),e.on(`session_before_tree`,e=>{if(e.preparation.userWantsSummary)return{summary:{summary:_(t)}}})}const k={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`},A=[`adventurer`,`architect`,`builder`,`diagnose`,`planner`,`reviewer`,`writer`],j=new Set([`completed`,`steered`,`aborted`,`stopped`,`error`]);function M(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:c.Object({agent:c.Optional(c.String({description:`Specialist agent name`})),task:c.Optional(c.String({description:`Task description for the subagent`})),tasks:c.Optional(c.Array(c.Object({agent:c.String(),task:c.String()}),{description:`Array of task objects for parallel or chain dispatch`})),mode:c.Optional(c.Union([c.Literal(`parallel`),c.Literal(`chain`),c.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`){if(!A.includes(r.agent))throw Error(`Unknown agent: "${r.agent}". Allowed: ${A.join(`, `)}`);if(!r.task||!r.task.trim())throw Error(`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){if(!A.includes(e.agent))throw Error(`Unknown agent: "${e.agent}". Allowed: ${A.join(`, `)}`);if(!e.task||!e.task.trim())throw Error(`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){if(!A.includes(e.agent))throw Error(`Unknown agent: "${e.agent}". Allowed: ${A.join(`, `)}`);if(!e.task||!e.task.trim())throw Error(`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(`
9
+ `)}]};try{async function n(e,t,n){let r=0,o=l.getRecord(e);for(;o&&!j.has(o.status)&&r<120;){if(i?.aborted)throw Error(`Maestria subagent call aborted`);await new Promise(e=>setTimeout(e,500)),o=l.getRecord(e),r++,n&&a?.({content:[{type:`text`,text:`${t} running... (${Math.round(r*500/1e3)}s)`}]})}if(o&&!j.has(o.status))throw Error(`Subagent ${e} timed out after 60000ms`);if(!o)throw Error(`Subagent ${e} was cleaned up before completion`);return o}if(s===`single`){let i=r.agent,a=r.task,o=l.spawn(i,a,{description:a.slice(0,80),foreground:!0,inheritContext:!0}),s=f(t,`orchestrator`,i,a);Object.assign(t,s),e.appendEntry(`maestria_state`,t);let c=await n(o,`Subagent ${i}`,!0);return{content:[{type:`text`,text:c.result??c.error??`No output.`}],details:{subagentId:o}}}if(s===`parallel`){let i=r.tasks;a?.({content:[{type:`text`,text:`Spawning ${i.length} parallel subagents...`}]});let o=[];for(let e of i){let n=l.spawn(e.agent,e.task,{description:e.task.slice(0,80),foreground:!0,inheritContext:!0});o.push(n);let r=f(t,`orchestrator`,e.agent,e.task);Object.assign(t,r)}e.appendEntry(`maestria_state`,t);let s=await Promise.all(o.map((e,t)=>n(e,`${i[t].agent} (${t+1}/${i.length})`,!1)));a?.({content:[{type:`text`,text:`All ${i.length} parallel subagents completed.`}]});let c=[`## Parallel Results (${i.length} tasks)\n`];for(let e=0;e<i.length;e++){let t=i[e],n=s[e],r=n.result??n.error??`No output.`;c.push(`### ${e+1}: ${t.agent}`),c.push(r)}return{content:[{type:`text`,text:c.join(`
9
10
 
10
- `)}],details:{subagentIds:i}}}if(s===`chain`){let n=r.tasks,i=``;for(let r=0;r<n.length;r++){let s=n[r],u=s.task;r>0&&u.includes(`{previous}`)&&(u=u.replace(/\{previous\}/g,i));let d=o.spawn(s.agent,u,{description:u.slice(0,80),foreground:!0,inheritContext:!0}),f=c(t,`orchestrator`,s.agent,u);Object.assign(t,f),e.appendEntry(`maestria_state`,t),a?.({content:[{type:`text`,text:`Chain step ${r+1}/${n.length}: ${s.agent} running...`}]});let p=await l(d,`Chain step ${r+1}: ${s.agent}`,!0);i=p.result??p.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:i}],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(`
11
- `)}]}}}}),e.events){let r=e.events.on(a.STARTED,n=>{let{id:r,type:i}=n;t.subagentStatus[r]={type:i,status:`running`,startedAt:Date.now()},d(e,t),e.events?.emit(S.SUBAGENT_STARTED,{id:r,type:i,timestamp:Date.now()})}),i=e.events.on(a.COMPLETED,n=>{let{id:r}=n,i=t.subagentStatus[r];i&&(i.status=`completed`,i.completedAt=Date.now()),d(e,t),e.events?.emit(S.SUBAGENT_COMPLETED,{id:r,type:i?.type,timestamp:Date.now()})}),o=e.events.on(a.FAILED,n=>{let{id:r,status:i}=n,a=t.subagentStatus[r];a&&(a.status=i??`error`,a.completedAt=Date.now()),d(e,t),e.events?.emit(S.SUBAGENT_FAILED,{id:r,type:a?.type,timestamp:Date.now()})}),s=e.events.on(a.STEERED,n=>{let{id:r}=n;t.subagentStatus[r]||(t.subagentStatus[r]={type:`unknown`,status:`running`,startedAt:Date.now()}),d(e,t)});n&&n.push(r,i,o,s)}}const E=[`read`,`grep`,`find`,`ls`,`glob`];function D(e,t){e.registerCommand(`orchestrate`,{description:`Start a full pipeline by delegating to the orchestrator`,handler:async(n,r)=>{if(!n.trim()){r.ui.notify(`Usage: /orchestrate <goal> - describe what to accomplish`);return}if(t.reviewMode){let n=t.originalModel;await u(e,r,t),d(e,t),e.events?.emit(S.REVIEW_DEACTIVATED,{originalModel:n,source:`orchestrate`,timestamp:Date.now()})}e.sendUserMessage([`[ORCHESTRATE: ${n}]`,``,`Orchestrate the full maestria pipeline to: ${n}`,`Use the orchestrator prompt template for subagent delegation.`].join(`
12
- `),{deliverAs:`steer`})}}),e.registerCommand(`maestria-status`,{description:`Show current maestria session state including handoff history`,handler:async(e,n)=>{let r=p(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),d(e,t),t.reviewModel){let n=await f(e,r,t);n&&(r.ui.notify(`Review mode: switched to ${n}`),e.events?.emit(S.REVIEW_ACTIVATED,{originalModel:t.originalModel,reviewModel:n,timestamp:Date.now()}))}e.setActiveTools(E),e.sendUserMessage([`[REVIEW: ${n}]`,``,`Review: ${n}. Use the reviewer prompt template.`,`Read only, no edits, report findings.`].join(`
13
- `),{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 u(e,r,t),d(e,t),r.ui.notify(`Restored original model and tools.`),e.events?.emit(S.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(`
11
+ `)}],details:{subagentIds:o}}}if(s===`chain`){let i=r.tasks,o=``;for(let r=0;r<i.length;r++){let s=i[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}),d=f(t,`orchestrator`,s.agent,c);Object.assign(t,d),e.appendEntry(`maestria_state`,t),a?.({content:[{type:`text`,text:`Chain step ${r+1}/${i.length}: ${s.agent} running...`}]});let p=await n(u,`Chain step ${r+1}: ${s.agent}`,!0);o=p.result??p.error??`No output.`,r<i.length-1&&a?.({content:[{type:`text`,text:`Chain step ${r+1}/${i.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(`
12
+ `)}]}}}}),e.events){let r=e.events.on(l.STARTED,n=>{let{id:r,type:i}=n;t.subagentStatus[r]={type:i,status:`running`,startedAt:Date.now()},h(e,t),e.events?.emit(k.SUBAGENT_STARTED,{id:r,type:i,timestamp:Date.now()})}),i=e.events.on(l.COMPLETED,n=>{let{id:r}=n,i=t.subagentStatus[r];i&&(i.status=`completed`,i.completedAt=Date.now()),h(e,t),e.events?.emit(k.SUBAGENT_COMPLETED,{id:r,type:i?.type,timestamp:Date.now()})}),a=e.events.on(l.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(k.SUBAGENT_FAILED,{id:r,type:a?.type,timestamp:Date.now()})}),o=e.events.on(l.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 N=[`read`,`grep`,`find`,`ls`,`glob`];function P(e,t){e.registerCommand(`orchestrate`,{description:`Start a full pipeline by delegating to the orchestrator`,handler:async(n,r)=>{if(!n.trim()){r.ui.notify(`Usage: /orchestrate <goal> - describe what to accomplish`);return}if(t.reviewMode){let n=t.originalModel;await m(e,r,t),h(e,t),e.events?.emit(k.REVIEW_DEACTIVATED,{originalModel:n,source:`orchestrate`,timestamp:Date.now()})}e.sendUserMessage([`[ORCHESTRATE: ${n}]`,``,`Orchestrate the full maestria pipeline to: ${n}`,`Use the orchestrator prompt template for subagent delegation.`].join(`
13
+ `),{deliverAs:`steer`})}}),e.registerCommand(`maestria-status`,{description:`Show current maestria session state including handoff history`,handler:async(e,n)=>{let r=_(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 g(e,r,t);n&&(r.ui.notify(`Review mode: switched to ${n}`),e.events?.emit(k.REVIEW_ACTIVATED,{originalModel:t.originalModel,reviewModel:n,timestamp:Date.now()}))}e.setActiveTools(N),e.sendUserMessage([`[REVIEW: ${n}]`,``,`Review: ${n}. Use the reviewer prompt template.`,`Read only, no edits, report findings.`].join(`
14
+ `),{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 m(e,r,t),h(e,t),r.ui.notify(`Restored original model and tools.`),e.events?.emit(k.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(`
14
15
  `):`(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(`
15
- `);t.handoffHistory=[{from:`current`,to:`next`,task:i,timestamp:Date.now()},...t.handoffHistory??[]].slice(0,5),d(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,d(e,t),r.ui.notify(`Review model set to: ${i}`)}})}const O=[/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 k(e,t){e.on(`tool_call`,async(e,n)=>{if(!(!e||!e.toolName)){if(t.reviewMode&&(o(`edit`,e)||o(`write`,e)||o(`bash`,e)))return{block:!0,reason:`Review mode is active. Report findings, do not edit.`};if(o(`bash`,e)){if(!e.input||typeof e.input!=`object`)return;let t=e.input.command;if(t){for(let e of O)if(e.test(t))return n.hasUI&&await n.ui.confirm(`Dangerous Pattern Detected`,`This command matches a dangerous pattern:\n${t}\nProceed?`)?void 0:{block:!0,reason:`Command matches dangerous pattern: ${e}`}}}}})}function A(e){let t=s(),n=[];v(e,t);let r=b(t);e.on(`before_agent_start`,(e,t)=>r(e,t)),e.on(`session_start`,(e,n)=>{if(!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}}}),x(e,t),T(e,t,n),D(e,t),e.on(`session_shutdown`,()=>{for(let e of n)e();n.length=0}),k(e,t)}export{A as default};
16
+ `);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 F=[/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 I(e,t){e.on(`tool_call`,async(e,n)=>{if(!(!e||!e.toolName)){if(t.reviewMode&&(u(`edit`,e)||u(`write`,e)||u(`bash`,e)))return{block:!0,reason:`Review mode is active. Report findings, do not edit.`};if(u(`bash`,e)){if(!e.input||typeof e.input!=`object`)return;let t=e.input.command;if(t){for(let e of F)if(e.test(t))return n.hasUI&&await n.ui.confirm(`Dangerous Pattern Detected`,`This command matches a dangerous pattern:\n${t}\nProceed?`)?void 0:{block:!0,reason:`Command matches dangerous pattern: ${e}`}}}}})}function L(e){let t=d(),n=[];E(e,t);let r=D(t);e.on(`before_agent_start`,(e,t)=>r(e,t)),e.on(`session_start`,(e,n)=>{if(x(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}}}),O(e,t),M(e,t,n),P(e,t),e.on(`session_shutdown`,()=>{for(let e of n)e();n.length=0}),I(e,t)}export{L as default};
16
17
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../src/state.ts","../src/modes.ts","../src/rules.ts","../src/compaction.ts","../src/subagent.ts","../src/commands.ts","../src/tools.ts","../src/extension.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionCommandContext } from '@earendil-works/pi-coding-agent';\nimport type { ModeKeyword } from '@/modes.js';\n\nconst HANDOFF_HISTORY_CAP = 5;\nconst FILE_HISTORY_CAP = 10;\n\nexport interface HandoffEntry {\n from: string;\n to: string;\n task: string;\n timestamp: number;\n}\n\nexport interface SubagentStatusInfo {\n type: string;\n status: string;\n startedAt: number;\n completedAt?: number;\n}\n\nexport interface MaestriaState {\n mode: ModeKeyword | null;\n activeTask: string;\n completionPromise: string;\n specialistsDelegated: string[];\n blockers: string[];\n filesModified: string[];\n filesRead: string[];\n handoffHistory: HandoffEntry[];\n reviewMode: boolean;\n originalModel: string | null;\n originalTools: string[] | null;\n subagentStatus: Record<string, SubagentStatusInfo>;\n /** Model ID to use when entering review mode. Null = no preference. */\n reviewModel: string | null;\n}\n\nexport function createInitialState(): MaestriaState {\n return {\n mode: null,\n activeTask: '',\n completionPromise: '',\n specialistsDelegated: [],\n blockers: [],\n filesModified: [],\n filesRead: [],\n handoffHistory: [],\n reviewMode: false,\n originalModel: null,\n originalTools: null,\n subagentStatus: {},\n reviewModel: null,\n };\n}\n\nexport function recordHandoff(\n state: MaestriaState,\n from: string,\n to: string,\n task: string,\n): MaestriaState {\n const entry: HandoffEntry = { from, to, task, timestamp: Date.now() };\n const history = [entry, ...state.handoffHistory].slice(0, HANDOFF_HISTORY_CAP);\n return { ...state, handoffHistory: history };\n}\n\nfunction prependDeduped(files: string[], path: string, cap: number): string[] {\n const filtered = files.filter((f) => f !== path);\n return [path, ...filtered].slice(0, cap);\n}\n\nexport function recordFileModified(state: MaestriaState, path: string): MaestriaState {\n return { ...state, filesModified: prependDeduped(state.filesModified, path, FILE_HISTORY_CAP) };\n}\n\nexport function recordFileRead(state: MaestriaState, path: string): MaestriaState {\n return { ...state, filesRead: prependDeduped(state.filesRead, path, FILE_HISTORY_CAP) };\n}\n\nexport function recordSubagentStatus(\n state: MaestriaState,\n id: string,\n info: SubagentStatusInfo,\n): MaestriaState {\n return { ...state, subagentStatus: { ...state.subagentStatus, [id]: info } };\n}\n\nexport function setReviewMode(state: MaestriaState, active: boolean): MaestriaState {\n return {\n ...state,\n reviewMode: active,\n };\n}\n\n/**\n * Exit review mode and return the original model/tools for restoration.\n * Returns a new state (immutable) with review mode cleared and the\n * saved originals so the caller can pass them to pi.setModel/setActiveTools.\n */\nexport function exitReviewMode(state: MaestriaState): {\n state: MaestriaState;\n originalModel: string | null;\n originalTools: string[] | null;\n} {\n return {\n state: {\n ...state,\n reviewMode: false,\n originalModel: null,\n originalTools: null,\n },\n originalModel: state.originalModel,\n originalTools: state.originalTools,\n };\n}\n\n/**\n * Restore the original model and tools saved when review mode was entered.\n * Clears review mode from state and resets pi to the pre-review configuration.\n */\nexport async function restoreOriginalState(\n pi: ExtensionAPI,\n ctx: ExtensionCommandContext,\n state: MaestriaState,\n): Promise<void> {\n const { state: clearedState, originalModel, originalTools } = exitReviewMode(state);\n\n // Restore original tools first (makes full toolset available again)\n if (originalTools && originalTools.length > 0) {\n pi.setActiveTools(originalTools);\n }\n\n // Restore original model - best-effort, failures are non-fatal\n if (originalModel) {\n try {\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m: { id: string }) => m.id === originalModel);\n if (model) {\n await pi.setModel(model);\n }\n } catch {\n // Best-effort: model restoration is non-critical\n }\n }\n\n // Clear review mode state\n Object.assign(state, clearedState);\n}\n\n/**\n * Persist the current state to the session by appending a custom entry.\n * Creates a shallow copy to ensure appendEntry sees the latest snapshot.\n */\nexport function persistState(pi: ExtensionAPI, state: MaestriaState): void {\n pi.appendEntry('maestria_state', { ...state });\n}\n\n/**\n * If a review model is configured, switch to it.\n * Returns the model ID switched to, or null if no switch occurred.\n */\nexport async function cycleToReviewModel(\n pi: ExtensionAPI,\n ctx: ExtensionCommandContext,\n state: MaestriaState,\n): Promise<string | null> {\n const reviewModel = state.reviewModel;\n if (!reviewModel) {\n return null;\n }\n try {\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m) => m.id === reviewModel);\n if (model) {\n await pi.setModel(model);\n return reviewModel;\n } else {\n ctx.ui.notify(`Review model \"${reviewModel}\" not found in registry, staying on current.`);\n return null;\n }\n } catch {\n ctx.ui.notify(`Could not switch to review model \"${reviewModel}\", staying on current.`);\n return null;\n }\n}\n\nexport function renderMaestriaSummary(state: MaestriaState): string {\n const parts: string[] = [];\n\n if (state.mode) {\n parts.push(`**Mode:** ${state.mode.toUpperCase()}`);\n }\n\n if (state.reviewModel) {\n parts.push(`**Review Model:** ${state.reviewModel}`);\n }\n\n if (state.activeTask) {\n parts.push(`**Goal:** ${state.activeTask}`);\n }\n\n if (state.completionPromise) {\n parts.push(`**Completion Promise:** ${state.completionPromise}`);\n }\n\n if (state.specialistsDelegated.length > 0) {\n parts.push(`**Specialists Delegated:** ${state.specialistsDelegated.join(', ')}`);\n }\n\n if (state.blockers.length > 0) {\n parts.push('**Blockers:**');\n for (const blocker of state.blockers) {\n parts.push(`- ${blocker}`);\n }\n }\n\n const fileSubs: string[] = [];\n if (state.filesModified.length > 0) {\n fileSubs.push(`**Modified:** ${state.filesModified.join(', ')}`);\n }\n if (state.filesRead.length > 0) {\n fileSubs.push(`**Read:** ${state.filesRead.join(', ')}`);\n }\n if (fileSubs.length > 0) {\n parts.push(`**Files:** ${fileSubs.join('; ')}`);\n }\n\n if (state.handoffHistory.length > 0) {\n parts.push('**Recent Handoffs:**');\n for (const entry of state.handoffHistory) {\n parts.push(`- ${entry.from} → ${entry.to}: ${entry.task}`);\n }\n }\n\n return parts.join('\\n\\n');\n}\n\nexport { HANDOFF_HISTORY_CAP, FILE_HISTORY_CAP };\n","import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { persistState, restoreOriginalState } from '@/state.js';\n\nexport const MODE_KEYWORDS = ['fein', 'sonar', 'blitz'] as const;\nexport type ModeKeyword = (typeof MODE_KEYWORDS)[number];\n\nconst MODE_PROMPTS: Record<ModeKeyword, string> = {\n fein: [\n '## MODE: fein (Full Pipeline)',\n '',\n 'Execute the complete fein pipeline: mandatory reconnaissance',\n '(/adventurer) → design/plan (/architect or /planner) →',\n 'implementation (/builder) → review (/reviewer).',\n 'Do NOT skip any phase unless the user explicitly overrides',\n 'in the same turn.',\n ].join('\\n'),\n sonar: [\n '## MODE: sonar (Research Only)',\n '',\n 'Execute research only: /adventurer (recon) →',\n '/architect or /planner (design/plan) → STOP.',\n 'Do NOT implement anything. Return findings.',\n ].join('\\n'),\n blitz: [\n '## MODE: blitz (Fast Implementation)',\n '',\n 'Execute fast implementation via /builder directly.',\n 'Skip reconnaissance and design unless the codebase',\n 'is genuinely unknown. Skip review unless the result',\n 'needs validation.',\n ].join('\\n'),\n};\n\nconst MODE_MARKERS: Record<ModeKeyword, string> = {\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n blitz: '[MODE: blitz]',\n};\n\nexport function getModePrompt(keyword: ModeKeyword): string {\n return `${MODE_MARKERS[keyword]}\\n\\n${MODE_PROMPTS[keyword]}`;\n}\n\nexport function installModeCommands(pi: ExtensionAPI, state: MaestriaState): void {\n for (const keyword of MODE_KEYWORDS) {\n pi.registerCommand(keyword, {\n description: `Set workflow mode to ${keyword}`,\n handler: async (args, ctx) => {\n // Exit review mode if active (restore original model/tools)\n if (state.reviewMode) {\n await restoreOriginalState(pi, ctx, state);\n }\n\n state.mode = keyword;\n persistState(pi, state);\n\n if (args.trim()) {\n const modeMessage = [\n getModePrompt(keyword),\n '',\n `Run the maestria default pipeline on: ${args}`,\n ].join('\\n');\n pi.sendUserMessage(modeMessage, { deliverAs: 'steer' });\n } else {\n ctx.ui.notify(`Mode set to ${keyword}. Describe what you'd like to work on.`);\n }\n },\n });\n }\n}\n","import { readFileSync } from 'node:fs';\nimport { join, dirname } 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 '@/modes.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst RULES_CONTENT = readFileSync(join(__dirname, '..', 'rules', 'AGENTS.md'), 'utf-8');\n\nexport function createBeforeAgentStartHandler(state: MaestriaState) {\n return (\n event: BeforeAgentStartEvent,\n _ctx: ExtensionContext,\n ): BeforeAgentStartEventResult | void => {\n const parts: string[] = [RULES_CONTENT, '', event.systemPrompt];\n\n if (state.mode) {\n parts.push('', getModePrompt(state.mode));\n parts.push(\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\n return { systemPrompt: parts.join('\\n') };\n };\n}\n","import type {\n ExtensionAPI,\n SessionBeforeCompactEvent,\n SessionBeforeTreeEvent,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { renderMaestriaSummary } from '@/state.js';\n\nexport function installCompactionHandlers(pi: ExtensionAPI, state: MaestriaState): void {\n pi.on('session_before_compact', (event: SessionBeforeCompactEvent) => {\n return {\n compaction: {\n summary: renderMaestriaSummary(state),\n details: { ...state },\n firstKeptEntryId: event.preparation.firstKeptEntryId,\n tokensBefore: event.preparation.tokensBefore,\n },\n };\n });\n\n pi.on('session_before_tree', (event: SessionBeforeTreeEvent) => {\n if (event.preparation.userWantsSummary) {\n return {\n summary: {\n summary: renderMaestriaSummary(state),\n },\n };\n }\n return undefined;\n });\n}\n","import { Type } from 'typebox';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { SUBAGENT_EVENTS } from '@gotgenes/pi-subagents';\nimport type { MaestriaState } from '@/state.js';\nimport { persistState, recordHandoff } from '@/state.js';\n\n/**\n * Maestria cross-extension event names.\n * Other Pi extensions can subscribe via `pi.events?.on(...)`.\n * Convention: `maestria:<domain>:<action>`\n */\nexport const MAESTRIA_EVENTS = {\n REVIEW_ACTIVATED: 'maestria:review:activated',\n REVIEW_DEACTIVATED: 'maestria:review:deactivated',\n SUBAGENT_STARTED: 'maestria:subagent:started',\n SUBAGENT_COMPLETED: 'maestria:subagent:completed',\n SUBAGENT_FAILED: 'maestria:subagent:failed',\n} as const;\n\nconst ALLOWED_AGENTS = [\n 'adventurer',\n 'architect',\n 'builder',\n 'diagnose',\n 'planner',\n 'reviewer',\n 'writer',\n] as const;\ntype AllowedAgent = (typeof ALLOWED_AGENTS)[number];\n\n// The 6-field handoff contract\nconst HANDOFF_FIELDS = [\n 'Goal',\n 'Context',\n 'Requirements',\n 'Known problems',\n 'Success criteria',\n 'Next step',\n] as const;\n\n/** Terminal subagent statuses - agent will produce no more updates. */\nconst TERMINAL_STATUSES = new Set(['completed', 'steered', 'aborted', 'stopped', 'error']);\n\n/** Maximum time to wait for a subagent to complete, in milliseconds. */\nexport const POLL_TIMEOUT_MS = 60_000;\n\n/** Interval between subagent status checks, in milliseconds. */\nexport const POLL_INTERVAL_MS = 500;\n\n/** Maximum number of tasks allowed in parallel dispatch. */\nexport const MAX_PARALLEL_TASKS = 8;\n\nexport function validateHandoff(handoff: string): { valid: boolean; errors: string[] } {\n const errors: string[] = [];\n for (const field of HANDOFF_FIELDS) {\n const regex = new RegExp(`\\\\*\\\\*${field}:\\\\*\\\\*[\\\\s\\\\S]*?\\\\S`, 'i');\n if (!regex.test(handoff)) {\n errors.push(`Missing or empty field: \"${field}\"`);\n }\n }\n return { valid: errors.length === 0, errors };\n}\n\nexport function installSubagentTool(\n pi: ExtensionAPI,\n state: MaestriaState,\n cleanups?: Array<() => void>,\n): void {\n pi.registerTool({\n name: 'maestria_subagent',\n label: 'Maestria Subagent',\n description: 'Dispatch a task to a @maestria specialist subagent',\n promptSnippet:\n 'Delegate tasks to @maestria specialist subagents (adventurer, architect, builder, planner, diagnose, reviewer, writer)',\n promptGuidelines: [\n 'Use maestria_subagent when a task MUST be delegated to a specialist subagent rather than handled directly. Each specialist has focused capabilities: adventurer (recon), architect (design), builder (impl), planner (planning), diagnose (bugs), reviewer (QA), writer (docs).',\n ],\n prepareArguments(args: unknown) {\n return args;\n },\n parameters: Type.Object({\n agent: Type.Optional(Type.String({ description: 'Specialist agent name' })),\n task: Type.Optional(Type.String({ description: 'Task description for the subagent' })),\n tasks: Type.Optional(\n Type.Array(\n Type.Object({\n agent: Type.String(),\n task: Type.String(),\n }),\n { description: 'Array of task objects for parallel or chain dispatch' },\n ),\n ),\n mode: Type.Optional(\n Type.Union([Type.Literal('parallel'), Type.Literal('chain'), Type.Literal('single')]),\n ),\n }),\n async execute(\n _toolCallId: string,\n params: {\n agent?: string;\n task?: string;\n tasks?: Array<{ agent: string; task: string }>;\n mode?: 'parallel' | 'chain' | 'single';\n },\n signal: AbortSignal | undefined,\n onUpdate: ((result: { content: Array<{ type: string; text: string }> }) => void) | undefined,\n _ctx: ExtensionContext,\n ) {\n // Block subagent dispatch when in review mode\n if (state.reviewMode) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Subagent dispatch is not available during review mode. Use /restore-model to exit review mode first.',\n },\n ],\n };\n }\n\n // Determine dispatch mode (default to 'single' for backward compat)\n const mode = params.mode ?? 'single';\n\n // Validate parameters based on mode\n if (mode === 'single') {\n // Backward-compatible validation - must match original error messages exactly\n if (!ALLOWED_AGENTS.includes(params.agent as AllowedAgent)) {\n throw new Error(\n `Unknown agent: \"${params.agent}\". Allowed: ${ALLOWED_AGENTS.join(', ')}`,\n );\n }\n if (!params.task || !params.task.trim()) {\n throw new Error('Task description is required');\n }\n } else if (mode === 'parallel') {\n if (!params.tasks || params.tasks.length < 2) {\n throw new Error(`For parallel mode, tasks array is required with at least 2 items`);\n }\n if (params.tasks.length > MAX_PARALLEL_TASKS) {\n throw new Error(\n `For parallel mode, tasks array may have at most ${MAX_PARALLEL_TASKS} items (got ${params.tasks.length})`,\n );\n }\n for (const t of params.tasks) {\n if (!ALLOWED_AGENTS.includes(t.agent as AllowedAgent)) {\n throw new Error(`Unknown agent: \"${t.agent}\". Allowed: ${ALLOWED_AGENTS.join(', ')}`);\n }\n if (!t.task || !t.task.trim()) {\n throw new Error('Task description is required for all tasks');\n }\n }\n } else if (mode === 'chain') {\n if (!params.tasks || params.tasks.length < 2) {\n throw new Error('For chain mode, tasks array is required with at least 2 items');\n }\n for (const t of params.tasks) {\n if (!ALLOWED_AGENTS.includes(t.agent as AllowedAgent)) {\n throw new Error(`Unknown agent: \"${t.agent}\". Allowed: ${ALLOWED_AGENTS.join(', ')}`);\n }\n if (!t.task || !t.task.trim()) {\n throw new Error('Task description is required for all tasks');\n }\n }\n }\n\n // Attempt to dispatch via @gotgenes/pi-subagents; fallback gracefully\n try {\n const { getSubagentsService } = await import('@gotgenes/pi-subagents');\n const service = getSubagentsService()!;\n if (typeof service.spawn !== 'function') {\n throw new Error('Subagents service unavailable or incomplete');\n }\n\n // Helper: poll a single subagent until terminal or timeout\n async function pollSubagent(\n id: string,\n label: string,\n sendUpdates: boolean,\n ): Promise<{ status: string; result?: string; error?: string }> {\n const maxPolls = POLL_TIMEOUT_MS / POLL_INTERVAL_MS;\n let polls = 0;\n let record = service.getRecord(id);\n while (record && !TERMINAL_STATUSES.has(record.status) && polls < maxPolls) {\n if (signal?.aborted) throw new Error('Maestria subagent call aborted');\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n record = service.getRecord(id);\n polls++;\n if (sendUpdates) {\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `${label} running... (${Math.round((polls * POLL_INTERVAL_MS) / 1000)}s)`,\n },\n ],\n });\n }\n }\n if (record && !TERMINAL_STATUSES.has(record.status)) {\n throw new Error(`Subagent ${id} timed out after ${POLL_TIMEOUT_MS}ms`);\n }\n if (!record) {\n throw new Error(`Subagent ${id} was cleaned up before completion`);\n }\n return record;\n }\n\n // --- SINGLE MODE ---\n if (mode === 'single') {\n const agent = params.agent!;\n const task = params.task!;\n\n // Spawn in foreground - returns subagent ID synchronously\n const id = service.spawn(agent, task, {\n description: task.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n\n // Record handoff in state and persist (only after spawn succeeds)\n const updatedState = recordHandoff(state, 'orchestrator', agent, task);\n Object.assign(state, updatedState);\n pi.appendEntry('maestria_state', state);\n\n // Poll for completion\n const record = await pollSubagent(id, `Subagent ${agent}`, true);\n\n const resultText = record.result ?? record.error ?? 'No output.';\n\n return {\n content: [{ type: 'text' as const, text: resultText }],\n details: { subagentId: id },\n };\n }\n\n // --- PARALLEL MODE ---\n if (mode === 'parallel') {\n const taskList = params.tasks!;\n\n onUpdate?.({\n content: [\n { type: 'text' as const, text: `Spawning ${taskList.length} parallel subagents...` },\n ],\n });\n\n // Spawn all tasks\n const spawnedIds: string[] = [];\n for (const t of taskList) {\n const id = service.spawn(t.agent, t.task, {\n description: t.task.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n spawnedIds.push(id);\n\n // Record each handoff\n const updatedState = recordHandoff(state, 'orchestrator', t.agent, t.task);\n Object.assign(state, updatedState);\n }\n pi.appendEntry('maestria_state', state);\n\n // Poll all concurrently\n const records = await Promise.all(\n spawnedIds.map((id, i) =>\n pollSubagent(id, `${taskList[i].agent} (${i + 1}/${taskList.length})`, false),\n ),\n );\n\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `All ${taskList.length} parallel subagents completed.`,\n },\n ],\n });\n\n // Aggregate results\n const parts = [`## Parallel Results (${taskList.length} tasks)\\n`];\n for (let i = 0; i < taskList.length; i++) {\n const t = taskList[i];\n const rec = records[i];\n const resultText = rec.result ?? rec.error ?? 'No output.';\n parts.push(`### ${i + 1}: ${t.agent}`);\n parts.push(resultText);\n }\n\n return {\n content: [{ type: 'text' as const, text: parts.join('\\n\\n') }],\n details: { subagentIds: spawnedIds },\n };\n }\n\n // --- CHAIN MODE ---\n if (mode === 'chain') {\n const taskList = params.tasks!;\n let previousResult = '';\n\n for (let i = 0; i < taskList.length; i++) {\n const t = taskList[i];\n let taskText = t.task;\n\n // Substitute {previous} placeholder with previous result\n if (i > 0 && taskText.includes('{previous}')) {\n taskText = taskText.replace(/\\{previous\\}/g, previousResult);\n }\n\n const id = service.spawn(t.agent, taskText, {\n description: taskText.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n\n // Record handoff\n const updatedState = recordHandoff(state, 'orchestrator', t.agent, taskText);\n Object.assign(state, updatedState);\n pi.appendEntry('maestria_state', state);\n\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `Chain step ${i + 1}/${taskList.length}: ${t.agent} running...`,\n },\n ],\n });\n\n // Poll for completion\n const record = await pollSubagent(id, `Chain step ${i + 1}: ${t.agent}`, true);\n\n previousResult = record.result ?? record.error ?? 'No output.';\n\n if (i < taskList.length - 1) {\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `Chain step ${i + 1}/${taskList.length}: ${t.agent} completed. Moving to next step.`,\n },\n ],\n });\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: previousResult }],\n details: { subagentId: 'chain-completed' },\n };\n }\n\n // Should not reach here - all modes are handled above\n throw new Error('Unknown dispatch mode');\n } catch (err) {\n console.warn('[maestria] Subagent dispatch failed:', err);\n // Return handoff payload as structured text when dispatch fails\n const agentName = params.agent ?? params.tasks?.[0]?.agent ?? 'unknown';\n const taskDesc = params.task ?? params.tasks?.map((t) => t.task).join('; ') ?? 'unknown';\n const handoffInfo = [\n `## Subagent Handoff Required`,\n ``,\n `**From:** orchestrator`,\n `**To:** ${agentName}`,\n `**Task:** ${taskDesc}`,\n ``,\n `Subagent dispatch failed. Please delegate this work manually.`,\n ].join('\\n');\n\n return {\n content: [{ type: 'text' as const, text: handoffInfo }],\n };\n }\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any); // TypeBox inferred types don't match ToolDefinition exactly\n\n // Subscribe to subagent lifecycle events for accurate state tracking.\n // These subscriptions are set up once at extension init, not on every tool call.\n // pi.events is the shared EventBus - distinct from pi.on() lifecycle hooks.\n if (pi.events) {\n const unsubStarted = pi.events.on(SUBAGENT_EVENTS.STARTED, (data: unknown) => {\n const { id, type } = data as { id: string; type: string };\n state.subagentStatus[id] = { type, status: 'running', startedAt: Date.now() };\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_STARTED, {\n id,\n type,\n timestamp: Date.now(),\n });\n });\n\n const unsubCompleted = pi.events.on(SUBAGENT_EVENTS.COMPLETED, (data: unknown) => {\n const { id } = data as { id: string };\n const existing = state.subagentStatus[id];\n if (existing) {\n existing.status = 'completed';\n existing.completedAt = Date.now();\n }\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_COMPLETED, {\n id,\n type: existing?.type,\n timestamp: Date.now(),\n });\n });\n\n const unsubFailed = pi.events.on(SUBAGENT_EVENTS.FAILED, (data: unknown) => {\n const { id, status } = data as { id: string; status: string };\n const existing = state.subagentStatus[id];\n if (existing) {\n existing.status = status ?? 'error';\n existing.completedAt = Date.now();\n }\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_FAILED, {\n id,\n type: existing?.type,\n timestamp: Date.now(),\n });\n });\n\n const unsubSteered = pi.events.on(SUBAGENT_EVENTS.STEERED, (data: unknown) => {\n // Steering is informational - no status transition, but ensure\n // the agent is tracked as running if it wasn't already observed.\n const { id } = data as { id: string };\n if (!state.subagentStatus[id]) {\n state.subagentStatus[id] = { type: 'unknown', status: 'running', startedAt: Date.now() };\n }\n persistState(pi, state);\n });\n\n if (cleanups) {\n cleanups.push(unsubStarted, unsubCompleted, unsubFailed, unsubSteered);\n }\n }\n}\n","import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport {\n cycleToReviewModel,\n persistState,\n renderMaestriaSummary,\n restoreOriginalState,\n} from '@/state.js';\nimport { MAESTRIA_EVENTS } from '@/subagent.js';\n\n/**\n * Read-only tools that let a reviewer inspect code without making changes.\n *\n * - `read`, `grep`, `find`, `ls`, `glob` - all non-destructive.\n * - Excluded: `bash`, `edit`, `write` - these can modify the filesystem.\n *\n * `glob` is included for file pattern matching even though it's not a\n * built-in Pi tool - extensions may register it, and including it is a no-op\n * if absent.\n */\nconst READ_ONLY_TOOLS = ['read', 'grep', 'find', 'ls', 'glob'];\n\nexport function installCommands(pi: ExtensionAPI, state: MaestriaState): void {\n pi.registerCommand('orchestrate', {\n description: 'Start a full pipeline by delegating to the orchestrator',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /orchestrate <goal> - describe what to accomplish');\n return;\n }\n\n // Exit review mode if active (restore original model/tools)\n if (state.reviewMode) {\n const prevOriginalModel = state.originalModel;\n await restoreOriginalState(pi, ctx, state);\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.REVIEW_DEACTIVATED, {\n originalModel: prevOriginalModel,\n source: 'orchestrate',\n timestamp: Date.now(),\n });\n }\n\n pi.sendUserMessage(\n [\n `[ORCHESTRATE: ${args}]`,\n '',\n `Orchestrate the full maestria pipeline to: ${args}`,\n 'Use the orchestrator prompt template for subagent delegation.',\n ].join('\\n'),\n { deliverAs: 'steer' },\n );\n },\n });\n\n pi.registerCommand('maestria-status', {\n description: 'Show current maestria session state including handoff history',\n handler: async (_args: string, ctx) => {\n const summary = renderMaestriaSummary(state);\n if (!summary) {\n ctx.ui.notify('No active maestria state to report.');\n return;\n }\n ctx.ui.setEditorText(summary);\n },\n });\n\n pi.registerCommand('review', {\n description: 'Enter review mode. Blocks destructive tools, sets read-only toolset.',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /review <target> - describe what to review');\n return;\n }\n\n // 1. Save current model and tools for later restoration\n const currentModelId = ctx.model?.id ?? null;\n const currentTools = pi.getActiveTools();\n\n // 2. Update state: mark review mode, store originals\n const updatedState: MaestriaState = {\n ...state,\n reviewMode: true,\n originalModel: currentModelId,\n originalTools: currentTools,\n };\n Object.assign(state, updatedState);\n persistState(pi, state);\n\n // 3. Switch to review model if configured\n if (state.reviewModel) {\n const switched = await cycleToReviewModel(pi, ctx, state);\n if (switched) {\n ctx.ui.notify(`Review mode: switched to ${switched}`);\n pi.events?.emit(MAESTRIA_EVENTS.REVIEW_ACTIVATED, {\n originalModel: state.originalModel,\n reviewModel: switched,\n timestamp: Date.now(),\n });\n }\n }\n\n // 4. Restrict to read-only tools\n pi.setActiveTools(READ_ONLY_TOOLS);\n\n pi.sendUserMessage(\n [\n `[REVIEW: ${args}]`,\n '',\n `Review: ${args}. Use the reviewer prompt template.`,\n 'Read only, no edits, report findings.',\n ].join('\\n'),\n { deliverAs: 'steer' },\n );\n },\n });\n\n pi.registerCommand('restore-model', {\n description:\n 'Restore the original model and tools that were active before review mode was entered.',\n handler: async (_args: string, ctx) => {\n if (!state.reviewMode) {\n ctx.ui.notify('Not in review mode. Nothing to restore.');\n return;\n }\n const prevOriginalModel = state.originalModel;\n await restoreOriginalState(pi, ctx, state);\n persistState(pi, state);\n ctx.ui.notify('Restored original model and tools.');\n pi.events?.emit(MAESTRIA_EVENTS.REVIEW_DEACTIVATED, {\n originalModel: prevOriginalModel,\n timestamp: Date.now(),\n });\n },\n });\n\n pi.registerCommand('handoff', {\n description: 'Generate a structured handoff prompt for a new task context',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /handoff <goal> - describe the task context for handoff');\n return;\n }\n\n // Build a structured handoff document with 6 fields\n const goal = args.trim();\n const handoffPrompt = [\n '**Goal:** ' + goal,\n '',\n '**Context:**',\n '- Mode: ' + (state.mode ?? 'none'),\n '- Active task: ' + (state.activeTask || 'none'),\n '- Specialists delegated: ' +\n ((state.specialistsDelegated?.length ?? 0) > 0\n ? state.specialistsDelegated.join(', ')\n : 'none'),\n '- Recent handoffs: ' + (state.handoffHistory?.length ?? 0) + ' entries',\n '- Files modified: ' +\n ((state.filesModified?.length ?? 0) > 0 ? state.filesModified.join(', ') : 'none'),\n '',\n '**Requirements:**',\n '(fill in specific requirements)',\n '',\n '**Known problems:**',\n (state.blockers?.length ?? 0) > 0\n ? state.blockers.map((b: string) => '- ' + b).join('\\n')\n : '(no known problems documented)',\n '',\n '**Success criteria:**',\n '(fill in how to verify completion)',\n '',\n '**Next step:**',\n '(fill in what happens after this task)',\n '',\n '---',\n 'Complete the fields above before sending.',\n ].join('\\n');\n\n // Record in state\n state.handoffHistory = [\n { from: 'current', to: 'next', task: goal, timestamp: Date.now() },\n ...(state.handoffHistory ?? []),\n ].slice(0, 5);\n\n // Persist state\n persistState(pi, state);\n\n // Send as user message with steer delivery\n pi.sendUserMessage(handoffPrompt, { deliverAs: 'steer' });\n },\n });\n\n pi.registerCommand('review-model', {\n description: 'Set which model to use when entering review mode',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /review-model <model-id>');\n return;\n }\n const modelId = args.trim();\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m) => m.id === modelId);\n if (!model) {\n ctx.ui.notify(\n `Unknown model: \"${modelId}\". Available: ${models.map((m) => m.id).join(', ')}`,\n );\n return;\n }\n state.reviewModel = modelId;\n persistState(pi, state);\n ctx.ui.notify(`Review model set to: ${modelId}`);\n },\n });\n}\n","import {\n isToolCallEventType,\n type ExtensionAPI,\n type ToolCallEvent,\n type ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\n\nconst DANGEROUS_PATTERNS = [\n /rm\\s+-rf\\s+\\//,\n /dd\\s+if=/,\n />\\s*\\/dev\\/sd/,\n /chmod\\s+-R\\s+777\\s+\\//,\n /mkfs\\.\\w+/,\n /:(){ :\\|:& };:/, // fork bomb\n />\\s*\\/etc\\/(passwd|shadow|sudoers)/,\n /\\beval\\b/,\n /wget\\s+-O\\s*-\\s*\\|\\s*(bash|sh)/,\n /curl\\s+.*\\|\\s*(bash|sh)/,\n /crontab\\s+-r/,\n];\n\nexport function installToolInterceptors(pi: ExtensionAPI, state: MaestriaState): void {\n pi.on('tool_call', async (event: ToolCallEvent, ctx: ExtensionContext) => {\n if (!event || !event.toolName) return;\n\n // Block destructive tools in review mode\n if (state.reviewMode) {\n if (\n isToolCallEventType('edit', event) ||\n isToolCallEventType('write', event) ||\n isToolCallEventType('bash', event)\n ) {\n return {\n block: true,\n reason: 'Review mode is active. Report findings, do not edit.',\n };\n }\n }\n\n // Block dangerous bash patterns regardless of mode\n if (isToolCallEventType('bash', event)) {\n if (!event.input || typeof event.input !== 'object') return undefined;\n const command = event.input.command;\n if (command) {\n for (const pattern of DANGEROUS_PATTERNS) {\n if (pattern.test(command)) {\n if (ctx.hasUI) {\n const confirmed = await ctx.ui.confirm(\n 'Dangerous Pattern Detected',\n `This command matches a dangerous pattern:\\n${command}\\nProceed?`,\n );\n if (confirmed) return undefined;\n }\n return {\n block: true,\n reason: `Command matches dangerous pattern: ${pattern}`,\n };\n }\n }\n }\n }\n\n return undefined; // allow\n });\n}\n","import type { ExtensionAPI, SessionStartEvent } from '@earendil-works/pi-coding-agent';\nimport { createInitialState } from '@/state.js';\nimport { installModeCommands } from '@/modes.js';\nimport { createBeforeAgentStartHandler } from '@/rules.js';\nimport { installCompactionHandlers } from '@/compaction.js';\nimport { installSubagentTool } from '@/subagent.js';\nimport { installCommands } from '@/commands.js';\nimport { installToolInterceptors } from '@/tools.js';\n\nexport default function (pi: ExtensionAPI): void {\n const state = createInitialState();\n const cleanups: Array<() => void> = [];\n\n // Install mode commands: /fein, /sonar, /blitz\n installModeCommands(pi, state);\n\n // Inject mode prompts when a mode is active\n const handleBeforeAgentStart = createBeforeAgentStartHandler(state);\n\n pi.on('before_agent_start', (event, ctx) => {\n return handleBeforeAgentStart(event, ctx);\n });\n\n // Restore persisted state on session start (reload/resume/fork)\n pi.on('session_start', (_event: SessionStartEvent, ctx) => {\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":[],"sources":["../src/state.ts","../src/agents.ts","../src/modes.ts","../src/rules.ts","../src/compaction.ts","../src/subagent.ts","../src/commands.ts","../src/tools.ts","../src/extension.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionCommandContext } from '@earendil-works/pi-coding-agent';\nimport type { ModeKeyword } from '@/modes.js';\n\nconst HANDOFF_HISTORY_CAP = 5;\nconst FILE_HISTORY_CAP = 10;\n\nexport interface HandoffEntry {\n from: string;\n to: string;\n task: string;\n timestamp: number;\n}\n\nexport interface SubagentStatusInfo {\n type: string;\n status: string;\n startedAt: number;\n completedAt?: number;\n}\n\nexport interface MaestriaState {\n mode: ModeKeyword | null;\n activeTask: string;\n completionPromise: string;\n specialistsDelegated: string[];\n blockers: string[];\n filesModified: string[];\n filesRead: string[];\n handoffHistory: HandoffEntry[];\n reviewMode: boolean;\n originalModel: string | null;\n originalTools: string[] | null;\n subagentStatus: Record<string, SubagentStatusInfo>;\n /** Model ID to use when entering review mode. Null = no preference. */\n reviewModel: string | null;\n}\n\nexport function createInitialState(): MaestriaState {\n return {\n mode: null,\n activeTask: '',\n completionPromise: '',\n specialistsDelegated: [],\n blockers: [],\n filesModified: [],\n filesRead: [],\n handoffHistory: [],\n reviewMode: false,\n originalModel: null,\n originalTools: null,\n subagentStatus: {},\n reviewModel: null,\n };\n}\n\nexport function recordHandoff(\n state: MaestriaState,\n from: string,\n to: string,\n task: string,\n): MaestriaState {\n const entry: HandoffEntry = { from, to, task, timestamp: Date.now() };\n const history = [entry, ...state.handoffHistory].slice(0, HANDOFF_HISTORY_CAP);\n return { ...state, handoffHistory: history };\n}\n\nfunction prependDeduped(files: string[], path: string, cap: number): string[] {\n const filtered = files.filter((f) => f !== path);\n return [path, ...filtered].slice(0, cap);\n}\n\nexport function recordFileModified(state: MaestriaState, path: string): MaestriaState {\n return { ...state, filesModified: prependDeduped(state.filesModified, path, FILE_HISTORY_CAP) };\n}\n\nexport function recordFileRead(state: MaestriaState, path: string): MaestriaState {\n return { ...state, filesRead: prependDeduped(state.filesRead, path, FILE_HISTORY_CAP) };\n}\n\nexport function recordSubagentStatus(\n state: MaestriaState,\n id: string,\n info: SubagentStatusInfo,\n): MaestriaState {\n return { ...state, subagentStatus: { ...state.subagentStatus, [id]: info } };\n}\n\nexport function setReviewMode(state: MaestriaState, active: boolean): MaestriaState {\n return {\n ...state,\n reviewMode: active,\n };\n}\n\n/**\n * Exit review mode and return the original model/tools for restoration.\n * Returns a new state (immutable) with review mode cleared and the\n * saved originals so the caller can pass them to pi.setModel/setActiveTools.\n */\nexport function exitReviewMode(state: MaestriaState): {\n state: MaestriaState;\n originalModel: string | null;\n originalTools: string[] | null;\n} {\n return {\n state: {\n ...state,\n reviewMode: false,\n originalModel: null,\n originalTools: null,\n },\n originalModel: state.originalModel,\n originalTools: state.originalTools,\n };\n}\n\n/**\n * Restore the original model and tools saved when review mode was entered.\n * Clears review mode from state and resets pi to the pre-review configuration.\n */\nexport async function restoreOriginalState(\n pi: ExtensionAPI,\n ctx: ExtensionCommandContext,\n state: MaestriaState,\n): Promise<void> {\n const { state: clearedState, originalModel, originalTools } = exitReviewMode(state);\n\n // Restore original tools first (makes full toolset available again)\n if (originalTools && originalTools.length > 0) {\n pi.setActiveTools(originalTools);\n }\n\n // Restore original model - best-effort, failures are non-fatal\n if (originalModel) {\n try {\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m: { id: string }) => m.id === originalModel);\n if (model) {\n await pi.setModel(model);\n }\n } catch {\n // Best-effort: model restoration is non-critical\n }\n }\n\n // Clear review mode state\n Object.assign(state, clearedState);\n}\n\n/**\n * Persist the current state to the session by appending a custom entry.\n * Creates a shallow copy to ensure appendEntry sees the latest snapshot.\n */\nexport function persistState(pi: ExtensionAPI, state: MaestriaState): void {\n pi.appendEntry('maestria_state', { ...state });\n}\n\n/**\n * If a review model is configured, switch to it.\n * Returns the model ID switched to, or null if no switch occurred.\n */\nexport async function cycleToReviewModel(\n pi: ExtensionAPI,\n ctx: ExtensionCommandContext,\n state: MaestriaState,\n): Promise<string | null> {\n const reviewModel = state.reviewModel;\n if (!reviewModel) {\n return null;\n }\n try {\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m) => m.id === reviewModel);\n if (model) {\n await pi.setModel(model);\n return reviewModel;\n } else {\n ctx.ui.notify(`Review model \"${reviewModel}\" not found in registry, staying on current.`);\n return null;\n }\n } catch {\n ctx.ui.notify(`Could not switch to review model \"${reviewModel}\", staying on current.`);\n return null;\n }\n}\n\nexport function renderMaestriaSummary(state: MaestriaState): string {\n const parts: string[] = [];\n\n if (state.mode) {\n parts.push(`**Mode:** ${state.mode.toUpperCase()}`);\n }\n\n if (state.reviewModel) {\n parts.push(`**Review Model:** ${state.reviewModel}`);\n }\n\n if (state.activeTask) {\n parts.push(`**Goal:** ${state.activeTask}`);\n }\n\n if (state.completionPromise) {\n parts.push(`**Completion Promise:** ${state.completionPromise}`);\n }\n\n if (state.specialistsDelegated.length > 0) {\n parts.push(`**Specialists Delegated:** ${state.specialistsDelegated.join(', ')}`);\n }\n\n if (state.blockers.length > 0) {\n parts.push('**Blockers:**');\n for (const blocker of state.blockers) {\n parts.push(`- ${blocker}`);\n }\n }\n\n const fileSubs: string[] = [];\n if (state.filesModified.length > 0) {\n fileSubs.push(`**Modified:** ${state.filesModified.join(', ')}`);\n }\n if (state.filesRead.length > 0) {\n fileSubs.push(`**Read:** ${state.filesRead.join(', ')}`);\n }\n if (fileSubs.length > 0) {\n parts.push(`**Files:** ${fileSubs.join('; ')}`);\n }\n\n if (state.handoffHistory.length > 0) {\n parts.push('**Recent Handoffs:**');\n for (const entry of state.handoffHistory) {\n parts.push(`- ${entry.from} → ${entry.to}: ${entry.task}`);\n }\n }\n\n return parts.join('\\n\\n');\n}\n\nexport { HANDOFF_HISTORY_CAP, FILE_HISTORY_CAP };\n","import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { homedir } from 'node:os';\nimport type { ExtensionContext } from '@earendil-works/pi-coding-agent';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/** Source directory for bundled specialist agent files (synced from canonical) */\nconst AGENTS_SRC = join(__dirname, '..', 'agents');\n\n/** Destination directory where pi-subagents discovers agent types */\nconst AGENTS_DEST = join(homedir(), '.pi', 'agent', 'agents');\n\nconst SPECIALIST_NAMES = [\n 'adventurer',\n 'architect',\n 'builder',\n 'diagnose',\n 'planner',\n 'reviewer',\n 'writer',\n] as const;\n\n/**\n * Deploy bundled specialist agent .md files to the pi-subagents agents directory.\n *\n * pi-subagents discovers agent types from ~/.pi/agent/agents/*.md on every\n * registry.reload() call (which fires automatically on each tool invocation).\n * This function ensures the files are in place before the first subagent dispatch.\n *\n * Only creates files that don't already exist - never overwrites user-customized agents.\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function deploySpecialistAgents(_ctx?: ExtensionContext): void {\n const srcDir = AGENTS_SRC;\n\n if (!existsSync(srcDir)) {\n console.warn('[maestria] Agents source directory not found:', srcDir);\n return;\n }\n\n // Ensure destination directory exists\n try {\n mkdirSync(AGENTS_DEST, { recursive: true });\n } catch {\n console.warn('[maestria] Could not create agents directory:', AGENTS_DEST);\n return;\n }\n\n let deployed = 0;\n for (const name of SPECIALIST_NAMES) {\n const srcFile = join(srcDir, `${name}.md`);\n const destFile = join(AGENTS_DEST, `${name}.md`);\n\n if (!existsSync(srcFile)) {\n console.warn(`[maestria] Agent source not found: ${name}.md`);\n continue;\n }\n\n // Never overwrite existing files - user may have customized them\n if (existsSync(destFile)) continue;\n\n try {\n const content = readFileSync(srcFile, 'utf-8');\n writeFileSync(destFile, content, 'utf-8');\n deployed++;\n } catch (err) {\n console.warn(`[maestria] Failed to deploy agent ${name}:`, err);\n }\n }\n\n if (deployed > 0) {\n console.log(`[maestria] Deployed ${deployed} specialist agents to ${AGENTS_DEST}`);\n }\n}\n","import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { persistState, restoreOriginalState } from '@/state.js';\n\nexport const MODE_KEYWORDS = ['fein', 'sonar', 'blitz'] as const;\nexport type ModeKeyword = (typeof MODE_KEYWORDS)[number];\n\nconst MODE_PROMPTS: Record<ModeKeyword, string> = {\n fein: [\n '## MODE: fein (Full Pipeline)',\n '',\n 'Execute the complete fein pipeline: mandatory reconnaissance',\n '(/adventurer) → design/plan (/architect or /planner) →',\n 'implementation (/builder) → review (/reviewer).',\n 'Do NOT skip any phase unless the user explicitly overrides',\n 'in the same turn.',\n ].join('\\n'),\n sonar: [\n '## MODE: sonar (Research Only)',\n '',\n 'Execute research only: /adventurer (recon) →',\n '/architect or /planner (design/plan) → STOP.',\n 'Do NOT implement anything. Return findings.',\n ].join('\\n'),\n blitz: [\n '## MODE: blitz (Fast Implementation)',\n '',\n 'Execute fast implementation via /builder directly.',\n 'Skip reconnaissance and design unless the codebase',\n 'is genuinely unknown. Skip review unless the result',\n 'needs validation.',\n ].join('\\n'),\n};\n\nconst MODE_MARKERS: Record<ModeKeyword, string> = {\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n blitz: '[MODE: blitz]',\n};\n\nexport function getModePrompt(keyword: ModeKeyword): string {\n return `${MODE_MARKERS[keyword]}\\n\\n${MODE_PROMPTS[keyword]}`;\n}\n\nexport function installModeCommands(pi: ExtensionAPI, state: MaestriaState): void {\n for (const keyword of MODE_KEYWORDS) {\n pi.registerCommand(keyword, {\n description: `Set workflow mode to ${keyword}`,\n handler: async (args, ctx) => {\n // Exit review mode if active (restore original model/tools)\n if (state.reviewMode) {\n await restoreOriginalState(pi, ctx, state);\n }\n\n state.mode = keyword;\n persistState(pi, state);\n\n if (args.trim()) {\n const modeMessage = [\n getModePrompt(keyword),\n '',\n `Run the maestria default pipeline on: ${args}`,\n ].join('\\n');\n pi.sendUserMessage(modeMessage, { deliverAs: 'steer' });\n } else {\n ctx.ui.notify(`Mode set to ${keyword}. Describe what you'd like to work on.`);\n }\n },\n });\n }\n}\n","import type {\n BeforeAgentStartEvent,\n BeforeAgentStartEventResult,\n ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { getModePrompt } from '@/modes.js';\n\n/**\n * Creates a before_agent_start handler that injects workflow mode prompts.\n *\n * This is the only dynamic prompt injection needed from the extension.\n * Static behavioral content (orchestrator prompt + global rules) is\n * auto-injected by Pi's skill system via SKILL.md files registered in\n * the pi.skills manifest field - the standard Pi extension pattern.\n *\n * When no mode is active, the handler returns void (no modification),\n * letting Pi's built-in prompt assembly (skills + context files + tools)\n * stand as-is.\n */\nexport function createModePromptHandler(state: MaestriaState) {\n return (\n event: BeforeAgentStartEvent,\n _ctx: ExtensionContext,\n ): BeforeAgentStartEventResult | void => {\n if (!state.mode) return;\n\n const parts: string[] = [\n event.systemPrompt,\n '',\n getModePrompt(state.mode),\n '',\n `The user has set workflow mode to \"${state.mode}\". ` +\n 'Honor this mode throughout the session until changed via /command.',\n ];\n\n return { systemPrompt: parts.join('\\n') };\n };\n}\n","import type {\n ExtensionAPI,\n SessionBeforeCompactEvent,\n SessionBeforeTreeEvent,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport { renderMaestriaSummary } from '@/state.js';\n\nexport function installCompactionHandlers(pi: ExtensionAPI, state: MaestriaState): void {\n pi.on('session_before_compact', (event: SessionBeforeCompactEvent) => {\n return {\n compaction: {\n summary: renderMaestriaSummary(state),\n details: { ...state },\n firstKeptEntryId: event.preparation.firstKeptEntryId,\n tokensBefore: event.preparation.tokensBefore,\n },\n };\n });\n\n pi.on('session_before_tree', (event: SessionBeforeTreeEvent) => {\n if (event.preparation.userWantsSummary) {\n return {\n summary: {\n summary: renderMaestriaSummary(state),\n },\n };\n }\n return undefined;\n });\n}\n","import { Type } from 'typebox';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { SUBAGENT_EVENTS } from '@gotgenes/pi-subagents';\nimport type { MaestriaState } from '@/state.js';\nimport { persistState, recordHandoff } from '@/state.js';\n\n/**\n * Maestria cross-extension event names.\n * Other Pi extensions can subscribe via `pi.events?.on(...)`.\n * Convention: `maestria:<domain>:<action>`\n */\nexport const MAESTRIA_EVENTS = {\n REVIEW_ACTIVATED: 'maestria:review:activated',\n REVIEW_DEACTIVATED: 'maestria:review:deactivated',\n SUBAGENT_STARTED: 'maestria:subagent:started',\n SUBAGENT_COMPLETED: 'maestria:subagent:completed',\n SUBAGENT_FAILED: 'maestria:subagent:failed',\n} as const;\n\nconst ALLOWED_AGENTS = [\n 'adventurer',\n 'architect',\n 'builder',\n 'diagnose',\n 'planner',\n 'reviewer',\n 'writer',\n] as const;\ntype AllowedAgent = (typeof ALLOWED_AGENTS)[number];\n\n// The 6-field handoff contract\nconst HANDOFF_FIELDS = [\n 'Goal',\n 'Context',\n 'Requirements',\n 'Known problems',\n 'Success criteria',\n 'Next step',\n] as const;\n\n/** Terminal subagent statuses - agent will produce no more updates. */\nconst TERMINAL_STATUSES = new Set(['completed', 'steered', 'aborted', 'stopped', 'error']);\n\n/** Maximum time to wait for a subagent to complete, in milliseconds. */\nexport const POLL_TIMEOUT_MS = 60_000;\n\n/** Interval between subagent status checks, in milliseconds. */\nexport const POLL_INTERVAL_MS = 500;\n\n/** Maximum number of tasks allowed in parallel dispatch. */\nexport const MAX_PARALLEL_TASKS = 8;\n\nexport function validateHandoff(handoff: string): { valid: boolean; errors: string[] } {\n const errors: string[] = [];\n for (const field of HANDOFF_FIELDS) {\n const regex = new RegExp(`\\\\*\\\\*${field}:\\\\*\\\\*[\\\\s\\\\S]*?\\\\S`, 'i');\n if (!regex.test(handoff)) {\n errors.push(`Missing or empty field: \"${field}\"`);\n }\n }\n return { valid: errors.length === 0, errors };\n}\n\nexport function installSubagentTool(\n pi: ExtensionAPI,\n state: MaestriaState,\n cleanups?: Array<() => void>,\n): void {\n pi.registerTool({\n name: 'maestria_subagent',\n label: 'Maestria Subagent',\n description: 'Dispatch a task to a @maestria specialist subagent',\n promptSnippet:\n 'Delegate tasks to @maestria specialist subagents (adventurer, architect, builder, planner, diagnose, reviewer, writer)',\n promptGuidelines: [\n 'Use maestria_subagent when a task MUST be delegated to a specialist subagent rather than handled directly. Each specialist has focused capabilities: adventurer (recon), architect (design), builder (impl), planner (planning), diagnose (bugs), reviewer (QA), writer (docs).',\n ],\n prepareArguments(args: unknown) {\n return args;\n },\n parameters: Type.Object({\n agent: Type.Optional(Type.String({ description: 'Specialist agent name' })),\n task: Type.Optional(Type.String({ description: 'Task description for the subagent' })),\n tasks: Type.Optional(\n Type.Array(\n Type.Object({\n agent: Type.String(),\n task: Type.String(),\n }),\n { description: 'Array of task objects for parallel or chain dispatch' },\n ),\n ),\n mode: Type.Optional(\n Type.Union([Type.Literal('parallel'), Type.Literal('chain'), Type.Literal('single')]),\n ),\n }),\n async execute(\n _toolCallId: string,\n params: {\n agent?: string;\n task?: string;\n tasks?: Array<{ agent: string; task: string }>;\n mode?: 'parallel' | 'chain' | 'single';\n },\n signal: AbortSignal | undefined,\n onUpdate: ((result: { content: Array<{ type: string; text: string }> }) => void) | undefined,\n _ctx: ExtensionContext,\n ) {\n // Block subagent dispatch when in review mode\n if (state.reviewMode) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Subagent dispatch is not available during review mode. Use /restore-model to exit review mode first.',\n },\n ],\n };\n }\n\n // Determine dispatch mode (default to 'single' for backward compat)\n const mode = params.mode ?? 'single';\n\n // Validate parameters based on mode\n if (mode === 'single') {\n // Backward-compatible validation - must match original error messages exactly\n if (!ALLOWED_AGENTS.includes(params.agent as AllowedAgent)) {\n throw new Error(\n `Unknown agent: \"${params.agent}\". Allowed: ${ALLOWED_AGENTS.join(', ')}`,\n );\n }\n if (!params.task || !params.task.trim()) {\n throw new Error('Task description is required');\n }\n } else if (mode === 'parallel') {\n if (!params.tasks || params.tasks.length < 2) {\n throw new Error(`For parallel mode, tasks array is required with at least 2 items`);\n }\n if (params.tasks.length > MAX_PARALLEL_TASKS) {\n throw new Error(\n `For parallel mode, tasks array may have at most ${MAX_PARALLEL_TASKS} items (got ${params.tasks.length})`,\n );\n }\n for (const t of params.tasks) {\n if (!ALLOWED_AGENTS.includes(t.agent as AllowedAgent)) {\n throw new Error(`Unknown agent: \"${t.agent}\". Allowed: ${ALLOWED_AGENTS.join(', ')}`);\n }\n if (!t.task || !t.task.trim()) {\n throw new Error('Task description is required for all tasks');\n }\n }\n } else if (mode === 'chain') {\n if (!params.tasks || params.tasks.length < 2) {\n throw new Error('For chain mode, tasks array is required with at least 2 items');\n }\n for (const t of params.tasks) {\n if (!ALLOWED_AGENTS.includes(t.agent as AllowedAgent)) {\n throw new Error(`Unknown agent: \"${t.agent}\". Allowed: ${ALLOWED_AGENTS.join(', ')}`);\n }\n if (!t.task || !t.task.trim()) {\n throw new Error('Task description is required for all tasks');\n }\n }\n }\n\n // Attempt to dispatch via @gotgenes/pi-subagents; handle missing service\n const { getSubagentsService } = await import('@gotgenes/pi-subagents');\n const service = getSubagentsService();\n if (!service || typeof service.spawn !== 'function') {\n return {\n content: [\n {\n type: 'text' as const,\n text: [\n '## Subagent Dispatch Unavailable',\n '',\n 'The `@gotgenes/pi-subagents` extension is required for subagent dispatch but has not been loaded.',\n '',\n 'Install it as a Pi extension:',\n '',\n '```',\n 'pi install npm:@gotgenes/pi-subagents',\n '```',\n '',\n 'Then restart your Pi session.',\n ].join('\\n'),\n },\n ],\n };\n }\n\n try {\n // Helper: poll a single subagent until terminal or timeout\n async function pollSubagent(\n id: string,\n label: string,\n sendUpdates: boolean,\n ): Promise<{ status: string; result?: string; error?: string }> {\n const maxPolls = POLL_TIMEOUT_MS / POLL_INTERVAL_MS;\n let polls = 0;\n let record = service!.getRecord(id);\n while (record && !TERMINAL_STATUSES.has(record.status) && polls < maxPolls) {\n if (signal?.aborted) throw new Error('Maestria subagent call aborted');\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n record = service!.getRecord(id);\n polls++;\n if (sendUpdates) {\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `${label} running... (${Math.round((polls * POLL_INTERVAL_MS) / 1000)}s)`,\n },\n ],\n });\n }\n }\n if (record && !TERMINAL_STATUSES.has(record.status)) {\n throw new Error(`Subagent ${id} timed out after ${POLL_TIMEOUT_MS}ms`);\n }\n if (!record) {\n throw new Error(`Subagent ${id} was cleaned up before completion`);\n }\n return record;\n }\n\n // --- SINGLE MODE ---\n if (mode === 'single') {\n const agent = params.agent!;\n const task = params.task!;\n\n // Spawn in foreground - returns subagent ID synchronously\n const id = service.spawn(agent, task, {\n description: task.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n\n // Record handoff in state and persist (only after spawn succeeds)\n const updatedState = recordHandoff(state, 'orchestrator', agent, task);\n Object.assign(state, updatedState);\n pi.appendEntry('maestria_state', state);\n\n // Poll for completion\n const record = await pollSubagent(id, `Subagent ${agent}`, true);\n\n const resultText = record.result ?? record.error ?? 'No output.';\n\n return {\n content: [{ type: 'text' as const, text: resultText }],\n details: { subagentId: id },\n };\n }\n\n // --- PARALLEL MODE ---\n if (mode === 'parallel') {\n const taskList = params.tasks!;\n\n onUpdate?.({\n content: [\n { type: 'text' as const, text: `Spawning ${taskList.length} parallel subagents...` },\n ],\n });\n\n // Spawn all tasks\n const spawnedIds: string[] = [];\n for (const t of taskList) {\n const id = service.spawn(t.agent, t.task, {\n description: t.task.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n spawnedIds.push(id);\n\n // Record each handoff\n const updatedState = recordHandoff(state, 'orchestrator', t.agent, t.task);\n Object.assign(state, updatedState);\n }\n pi.appendEntry('maestria_state', state);\n\n // Poll all concurrently\n const records = await Promise.all(\n spawnedIds.map((id, i) =>\n pollSubagent(id, `${taskList[i].agent} (${i + 1}/${taskList.length})`, false),\n ),\n );\n\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `All ${taskList.length} parallel subagents completed.`,\n },\n ],\n });\n\n // Aggregate results\n const parts = [`## Parallel Results (${taskList.length} tasks)\\n`];\n for (let i = 0; i < taskList.length; i++) {\n const t = taskList[i];\n const rec = records[i];\n const resultText = rec.result ?? rec.error ?? 'No output.';\n parts.push(`### ${i + 1}: ${t.agent}`);\n parts.push(resultText);\n }\n\n return {\n content: [{ type: 'text' as const, text: parts.join('\\n\\n') }],\n details: { subagentIds: spawnedIds },\n };\n }\n\n // --- CHAIN MODE ---\n if (mode === 'chain') {\n const taskList = params.tasks!;\n let previousResult = '';\n\n for (let i = 0; i < taskList.length; i++) {\n const t = taskList[i];\n let taskText = t.task;\n\n // Substitute {previous} placeholder with previous result\n if (i > 0 && taskText.includes('{previous}')) {\n taskText = taskText.replace(/\\{previous\\}/g, previousResult);\n }\n\n const id = service.spawn(t.agent, taskText, {\n description: taskText.slice(0, 80),\n foreground: true,\n inheritContext: true,\n });\n\n // Record handoff\n const updatedState = recordHandoff(state, 'orchestrator', t.agent, taskText);\n Object.assign(state, updatedState);\n pi.appendEntry('maestria_state', state);\n\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `Chain step ${i + 1}/${taskList.length}: ${t.agent} running...`,\n },\n ],\n });\n\n // Poll for completion\n const record = await pollSubagent(id, `Chain step ${i + 1}: ${t.agent}`, true);\n\n previousResult = record.result ?? record.error ?? 'No output.';\n\n if (i < taskList.length - 1) {\n onUpdate?.({\n content: [\n {\n type: 'text' as const,\n text: `Chain step ${i + 1}/${taskList.length}: ${t.agent} completed. Moving to next step.`,\n },\n ],\n });\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: previousResult }],\n details: { subagentId: 'chain-completed' },\n };\n }\n\n // Should not reach here - all modes are handled above\n throw new Error('Unknown dispatch mode');\n } catch (err) {\n console.warn('[maestria] Subagent dispatch failed:', err);\n // Return handoff payload as structured text when dispatch fails\n const agentName = params.agent ?? params.tasks?.[0]?.agent ?? 'unknown';\n const taskDesc = params.task ?? params.tasks?.map((t) => t.task).join('; ') ?? 'unknown';\n const handoffInfo = [\n `## Subagent Handoff Required`,\n ``,\n `**From:** orchestrator`,\n `**To:** ${agentName}`,\n `**Task:** ${taskDesc}`,\n ``,\n `Subagent dispatch failed. Please delegate this work manually.`,\n ].join('\\n');\n\n return {\n content: [{ type: 'text' as const, text: handoffInfo }],\n };\n }\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any); // TypeBox inferred types don't match ToolDefinition exactly\n\n // Subscribe to subagent lifecycle events for accurate state tracking.\n // These subscriptions are set up once at extension init, not on every tool call.\n // pi.events is the shared EventBus - distinct from pi.on() lifecycle hooks.\n if (pi.events) {\n const unsubStarted = pi.events.on(SUBAGENT_EVENTS.STARTED, (data: unknown) => {\n const { id, type } = data as { id: string; type: string };\n state.subagentStatus[id] = { type, status: 'running', startedAt: Date.now() };\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_STARTED, {\n id,\n type,\n timestamp: Date.now(),\n });\n });\n\n const unsubCompleted = pi.events.on(SUBAGENT_EVENTS.COMPLETED, (data: unknown) => {\n const { id } = data as { id: string };\n const existing = state.subagentStatus[id];\n if (existing) {\n existing.status = 'completed';\n existing.completedAt = Date.now();\n }\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_COMPLETED, {\n id,\n type: existing?.type,\n timestamp: Date.now(),\n });\n });\n\n const unsubFailed = pi.events.on(SUBAGENT_EVENTS.FAILED, (data: unknown) => {\n const { id, status } = data as { id: string; status: string };\n const existing = state.subagentStatus[id];\n if (existing) {\n existing.status = status ?? 'error';\n existing.completedAt = Date.now();\n }\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.SUBAGENT_FAILED, {\n id,\n type: existing?.type,\n timestamp: Date.now(),\n });\n });\n\n const unsubSteered = pi.events.on(SUBAGENT_EVENTS.STEERED, (data: unknown) => {\n // Steering is informational - no status transition, but ensure\n // the agent is tracked as running if it wasn't already observed.\n const { id } = data as { id: string };\n if (!state.subagentStatus[id]) {\n state.subagentStatus[id] = { type: 'unknown', status: 'running', startedAt: Date.now() };\n }\n persistState(pi, state);\n });\n\n if (cleanups) {\n cleanups.push(unsubStarted, unsubCompleted, unsubFailed, unsubSteered);\n }\n }\n}\n","import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\nimport {\n cycleToReviewModel,\n persistState,\n renderMaestriaSummary,\n restoreOriginalState,\n} from '@/state.js';\nimport { MAESTRIA_EVENTS } from '@/subagent.js';\n\n/**\n * Read-only tools that let a reviewer inspect code without making changes.\n *\n * - `read`, `grep`, `find`, `ls`, `glob` - all non-destructive.\n * - Excluded: `bash`, `edit`, `write` - these can modify the filesystem.\n *\n * `glob` is included for file pattern matching even though it's not a\n * built-in Pi tool - extensions may register it, and including it is a no-op\n * if absent.\n */\nconst READ_ONLY_TOOLS = ['read', 'grep', 'find', 'ls', 'glob'];\n\nexport function installCommands(pi: ExtensionAPI, state: MaestriaState): void {\n pi.registerCommand('orchestrate', {\n description: 'Start a full pipeline by delegating to the orchestrator',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /orchestrate <goal> - describe what to accomplish');\n return;\n }\n\n // Exit review mode if active (restore original model/tools)\n if (state.reviewMode) {\n const prevOriginalModel = state.originalModel;\n await restoreOriginalState(pi, ctx, state);\n persistState(pi, state);\n pi.events?.emit(MAESTRIA_EVENTS.REVIEW_DEACTIVATED, {\n originalModel: prevOriginalModel,\n source: 'orchestrate',\n timestamp: Date.now(),\n });\n }\n\n pi.sendUserMessage(\n [\n `[ORCHESTRATE: ${args}]`,\n '',\n `Orchestrate the full maestria pipeline to: ${args}`,\n 'Use the orchestrator prompt template for subagent delegation.',\n ].join('\\n'),\n { deliverAs: 'steer' },\n );\n },\n });\n\n pi.registerCommand('maestria-status', {\n description: 'Show current maestria session state including handoff history',\n handler: async (_args: string, ctx) => {\n const summary = renderMaestriaSummary(state);\n if (!summary) {\n ctx.ui.notify('No active maestria state to report.');\n return;\n }\n ctx.ui.setEditorText(summary);\n },\n });\n\n pi.registerCommand('review', {\n description: 'Enter review mode. Blocks destructive tools, sets read-only toolset.',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /review <target> - describe what to review');\n return;\n }\n\n // 1. Save current model and tools for later restoration\n const currentModelId = ctx.model?.id ?? null;\n const currentTools = pi.getActiveTools();\n\n // 2. Update state: mark review mode, store originals\n const updatedState: MaestriaState = {\n ...state,\n reviewMode: true,\n originalModel: currentModelId,\n originalTools: currentTools,\n };\n Object.assign(state, updatedState);\n persistState(pi, state);\n\n // 3. Switch to review model if configured\n if (state.reviewModel) {\n const switched = await cycleToReviewModel(pi, ctx, state);\n if (switched) {\n ctx.ui.notify(`Review mode: switched to ${switched}`);\n pi.events?.emit(MAESTRIA_EVENTS.REVIEW_ACTIVATED, {\n originalModel: state.originalModel,\n reviewModel: switched,\n timestamp: Date.now(),\n });\n }\n }\n\n // 4. Restrict to read-only tools\n pi.setActiveTools(READ_ONLY_TOOLS);\n\n pi.sendUserMessage(\n [\n `[REVIEW: ${args}]`,\n '',\n `Review: ${args}. Use the reviewer prompt template.`,\n 'Read only, no edits, report findings.',\n ].join('\\n'),\n { deliverAs: 'steer' },\n );\n },\n });\n\n pi.registerCommand('restore-model', {\n description:\n 'Restore the original model and tools that were active before review mode was entered.',\n handler: async (_args: string, ctx) => {\n if (!state.reviewMode) {\n ctx.ui.notify('Not in review mode. Nothing to restore.');\n return;\n }\n const prevOriginalModel = state.originalModel;\n await restoreOriginalState(pi, ctx, state);\n persistState(pi, state);\n ctx.ui.notify('Restored original model and tools.');\n pi.events?.emit(MAESTRIA_EVENTS.REVIEW_DEACTIVATED, {\n originalModel: prevOriginalModel,\n timestamp: Date.now(),\n });\n },\n });\n\n pi.registerCommand('handoff', {\n description: 'Generate a structured handoff prompt for a new task context',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /handoff <goal> - describe the task context for handoff');\n return;\n }\n\n // Build a structured handoff document with 6 fields\n const goal = args.trim();\n const handoffPrompt = [\n '**Goal:** ' + goal,\n '',\n '**Context:**',\n '- Mode: ' + (state.mode ?? 'none'),\n '- Active task: ' + (state.activeTask || 'none'),\n '- Specialists delegated: ' +\n ((state.specialistsDelegated?.length ?? 0) > 0\n ? state.specialistsDelegated.join(', ')\n : 'none'),\n '- Recent handoffs: ' + (state.handoffHistory?.length ?? 0) + ' entries',\n '- Files modified: ' +\n ((state.filesModified?.length ?? 0) > 0 ? state.filesModified.join(', ') : 'none'),\n '',\n '**Requirements:**',\n '(fill in specific requirements)',\n '',\n '**Known problems:**',\n (state.blockers?.length ?? 0) > 0\n ? state.blockers.map((b: string) => '- ' + b).join('\\n')\n : '(no known problems documented)',\n '',\n '**Success criteria:**',\n '(fill in how to verify completion)',\n '',\n '**Next step:**',\n '(fill in what happens after this task)',\n '',\n '---',\n 'Complete the fields above before sending.',\n ].join('\\n');\n\n // Record in state\n state.handoffHistory = [\n { from: 'current', to: 'next', task: goal, timestamp: Date.now() },\n ...(state.handoffHistory ?? []),\n ].slice(0, 5);\n\n // Persist state\n persistState(pi, state);\n\n // Send as user message with steer delivery\n pi.sendUserMessage(handoffPrompt, { deliverAs: 'steer' });\n },\n });\n\n pi.registerCommand('review-model', {\n description: 'Set which model to use when entering review mode',\n handler: async (args: string, ctx) => {\n if (!args.trim()) {\n ctx.ui.notify('Usage: /review-model <model-id>');\n return;\n }\n const modelId = args.trim();\n const models = ctx.modelRegistry.getAll();\n const model = models.find((m) => m.id === modelId);\n if (!model) {\n ctx.ui.notify(\n `Unknown model: \"${modelId}\". Available: ${models.map((m) => m.id).join(', ')}`,\n );\n return;\n }\n state.reviewModel = modelId;\n persistState(pi, state);\n ctx.ui.notify(`Review model set to: ${modelId}`);\n },\n });\n}\n","import {\n isToolCallEventType,\n type ExtensionAPI,\n type ToolCallEvent,\n type ExtensionContext,\n} from '@earendil-works/pi-coding-agent';\nimport type { MaestriaState } from '@/state.js';\n\nconst DANGEROUS_PATTERNS = [\n /rm\\s+-rf\\s+\\//,\n /dd\\s+if=/,\n />\\s*\\/dev\\/sd/,\n /chmod\\s+-R\\s+777\\s+\\//,\n /mkfs\\.\\w+/,\n /:(){ :\\|:& };:/, // fork bomb\n />\\s*\\/etc\\/(passwd|shadow|sudoers)/,\n /\\beval\\b/,\n /wget\\s+-O\\s*-\\s*\\|\\s*(bash|sh)/,\n /curl\\s+.*\\|\\s*(bash|sh)/,\n /crontab\\s+-r/,\n];\n\nexport function installToolInterceptors(pi: ExtensionAPI, state: MaestriaState): void {\n pi.on('tool_call', async (event: ToolCallEvent, ctx: ExtensionContext) => {\n if (!event || !event.toolName) return;\n\n // Block destructive tools in review mode\n if (state.reviewMode) {\n if (\n isToolCallEventType('edit', event) ||\n isToolCallEventType('write', event) ||\n isToolCallEventType('bash', event)\n ) {\n return {\n block: true,\n reason: 'Review mode is active. Report findings, do not edit.',\n };\n }\n }\n\n // Block dangerous bash patterns regardless of mode\n if (isToolCallEventType('bash', event)) {\n if (!event.input || typeof event.input !== 'object') return undefined;\n const command = event.input.command;\n if (command) {\n for (const pattern of DANGEROUS_PATTERNS) {\n if (pattern.test(command)) {\n if (ctx.hasUI) {\n const confirmed = await ctx.ui.confirm(\n 'Dangerous Pattern Detected',\n `This command matches a dangerous pattern:\\n${command}\\nProceed?`,\n );\n if (confirmed) return undefined;\n }\n return {\n block: true,\n reason: `Command matches dangerous pattern: ${pattern}`,\n };\n }\n }\n }\n }\n\n return undefined; // allow\n });\n}\n","import type { ExtensionAPI, SessionStartEvent } from '@earendil-works/pi-coding-agent';\nimport { createInitialState } from '@/state.js';\nimport { deploySpecialistAgents } from '@/agents.js';\nimport { installModeCommands } from '@/modes.js';\nimport { createModePromptHandler } from '@/rules.js';\nimport { installCompactionHandlers } from '@/compaction.js';\nimport { installSubagentTool } from '@/subagent.js';\nimport { installCommands } from '@/commands.js';\nimport { installToolInterceptors } from '@/tools.js';\n\nexport default function (pi: ExtensionAPI): void {\n const state = createInitialState();\n const cleanups: Array<() => void> = [];\n\n // Install mode commands: /fein, /sonar, /blitz\n installModeCommands(pi, state);\n\n // Inject mode prompt when a workflow mode is active\n const handleModePrompt = createModePromptHandler(state);\n\n pi.on('before_agent_start', (event, ctx) => {\n return handleModePrompt(event, ctx);\n });\n\n // Deploy specialist agent files for pi-subagents discovery\n pi.on('session_start', (_event: SessionStartEvent, ctx) => {\n deploySpecialistAgents(ctx);\n\n // Restore persisted state on session start (reload/resume/fork)\n if (!ctx.sessionManager?.getEntries) return;\n const entries = ctx.sessionManager.getEntries();\n // Walk from newest to oldest, find the last persisted maestria_state entry\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n if (entry.type === 'custom' && entry.customType === 'maestria_state') {\n const data = entry.data;\n if (data && typeof data === 'object') {\n Object.assign(state, data);\n }\n break;\n }\n }\n });\n\n // Install compaction preservation handlers\n installCompactionHandlers(pi, state);\n\n // Install orchestration hooks: subagent tool and commands\n installSubagentTool(pi, state, cleanups);\n installCommands(pi, state);\n\n // Cleanup subscriptions on shutdown\n pi.on('session_shutdown', () => {\n for (const cleanup of cleanups) cleanup();\n cleanups.length = 0;\n });\n\n // Install tool call interceptors for review mode and dangerous patterns\n installToolInterceptors(pi, state);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maestria/pi",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "Maestria extension for the Pi coding agent",
5
5
  "keywords": [
6
6
  "agent-orchestration",
@@ -15,9 +15,8 @@
15
15
  "directory": "packages/pi"
16
16
  },
17
17
  "files": [
18
+ "agents",
18
19
  "dist",
19
- "prompts",
20
- "rules",
21
20
  "skills",
22
21
  "README.md",
23
22
  "LICENSE"
@@ -27,9 +26,7 @@
27
26
  "access": "public",
28
27
  "provenance": true
29
28
  },
30
- "dependencies": {
31
- "@gotgenes/pi-subagents": "^17.0.0"
32
- },
29
+ "dependencies": {},
33
30
  "devDependencies": {
34
31
  "@types/node": "^24",
35
32
  "typescript": "^6.0.3",
@@ -38,6 +35,7 @@
38
35
  "peerDependencies": {
39
36
  "@earendil-works/pi-ai": "*",
40
37
  "@earendil-works/pi-coding-agent": "*",
38
+ "@gotgenes/pi-subagents": "^17.0.0",
41
39
  "typebox": "*"
42
40
  },
43
41
  "engines": {
@@ -47,9 +45,6 @@
47
45
  "extensions": [
48
46
  "./dist/extension.mjs"
49
47
  ],
50
- "prompts": [
51
- "./prompts"
52
- ],
53
48
  "skills": [
54
49
  "./skills"
55
50
  ]
@@ -58,8 +53,8 @@
58
53
  "build": "vp pack",
59
54
  "test": "vp test",
60
55
  "lint": "vp check",
61
- "validate-prompts": "node --experimental-strip-types scripts/validate-prompts.ts",
56
+ "sync": "npx tsx ../core/scripts/sync.ts --config sync.config.ts",
62
57
  "validate-skills": "node --experimental-strip-types scripts/validate-skills.ts",
63
- "validate": "npm run validate-prompts && npm run validate-skills"
58
+ "validate": "npm run validate-skills"
64
59
  }
65
60
  }
File without changes
@@ -1,3 +1,12 @@
1
+ ---
2
+ name: global-rules
3
+ description: >-
4
+ Global behavioral constraints and best practices for maestria-powered
5
+ Pi agents. Covers orchestration conventions, delegation rules, context
6
+ management, commit policy, pipeline patterns, and branch discipline.
7
+ ---
8
+
9
+
1
10
  <!-- Auto-generated from @maestria/core. Do not edit directly.
2
11
  Edit the canonical file at packages/core/agent-directives/ instead. -->
3
12
 
File without changes
@@ -1,3 +1,13 @@
1
+ ---
2
+ name: orchestrator
3
+ description: >-
4
+ Maestria agent orchestration dispatcher. Delegates work to 7 specialist
5
+ subagents (adventurer, architect, builder, diagnose, planner, reviewer, writer)
6
+ using spec-driven handoffs. Enforces maker/checker split, commit protocol,
7
+ and role-based pipeline sequencing.
8
+ ---
9
+
10
+
1
11
  <!-- Auto-generated from @maestria/core. Do not edit directly.
2
12
  Edit the canonical file at packages/core/agent-directives/ instead. -->
3
13
 
@@ -18,6 +28,7 @@ These apply on every invocation without exception:
18
28
  - **!!! Git commands MUST be delegated to `/builder`.** Running `git add`, `git commit`, or `git push` yourself is not allowed. /builder's bash permission is the execution gate.
19
29
  - **Delegate validation (`check`, `test`) to `/builder` before the commit lands**, not to yourself.
20
30
  - **Push is conditional on branch.** Automatic on feature branches. Ask `question()` only on `main`/`master`. See the COMMIT PROTOCOL section below for the exact flow.
31
+ - **Keep PR and docs in sync with actual changes** - When pushed to a feature branch, update the PR title, description, and any documentation (changelogs, changesets, docs site) to reflect the cumulative state of the branch. Do not ask. Always.
21
32
  4. **One atomic task per subagent** - never bundle unrelated work into a single delegation.
22
33
  5. **!!! Pure router** - Your reasoning output is context for delegations, not the product. Keep analysis to what's needed for a good delegation decision. Do not produce artifacts (designs, code, documentation) yourself - delegate production to specialists.
23
34
  6. **Maker/checker split** - the agent that wrote code must not QA it. Always use a different specialist for review.
@@ -39,9 +50,11 @@ These apply on every invocation without exception:
39
50
 
40
51
  11. **!!! Don't anthropomorphize effort** - You are a dispatcher, not an implementer. Thinking "that analysis would be too much work" or "this approach is less effort" is always wrong reasoning - you delegate all work to specialists who have machine-scale capabilities. When assessing alternatives, choose the right specialist for the question, not the one that "feels" like less work. Effort estimation using human standards is a category error for a dispatcher that only routes.
41
52
 
42
- 12. **!!! Ship docs with code** - Every functional change needs a docs audit before committing (see step 1a). Don't wait to be asked.
53
+ 12. **!!! Ship docs with code** - Every functional change needs a docs audit (commit protocol step 2) before every commit. This applies without exception. Don't wait to be asked.
43
54
  13. **!!! Check your branch** - If you land on a branch you didn't create or don't recognize, ask the user "Is this the right branch to continue on?" before doing any work. Never assume intent. (Exception: worktrees are isolated by design - proceed directly.)
44
55
 
56
+ 14. **!!! Use the Work Results table format after every builder task** - After every builder task that lands a code change, present the summary using the table format defined in the Work Results section below (step 5 of the commit protocol). This overrides any "write for humans" guidance for this specific output.
57
+
45
58
  ## COMMIT PROTOCOL
46
59
 
47
60
  These steps apply per commit. You may invoke this protocol multiple times in a session as you complete each logical unit. Commit incrementally - group by logical context, not by file count. Each invocation goes through the full flow.
@@ -50,20 +63,26 @@ When a logical unit of work is complete (implementation done, tests pass, valida
50
63
 
51
64
  1. **Inspect** - `maestria_subagent(adventurer, "show git status + last 10 commits")`
52
65
  - **Learn from corrections:** Read the commit log and look for patterns in the user's past corrections. Did they change `feat` to `chore`? Correct a scope? Reject a push? Apply those conventions to this commit without asking.
53
- 2. **Docs audit** - Check what documentation, changelogs, changesets, or ADRs might need updating for the changes in this diff. Include findings in the commit or note them for follow-up. Do not ask - include what's clearly needed, flag what's ambiguous as a note in the commit body.
66
+ 2. **!!! Docs audit** - Audit ALL documentation categories for needed updates. "User-facing" means docs published for project consumers, not internal development notes:
67
+ - **Internal project docs** (docs/ directory, guides, ADRs, references)
68
+ - **User-facing docs site** (documentation site, published docs, user guides)
69
+ - **User-facing changelog** (changelog on the docs site, release notes - not the auto-generated CHANGELOG.md files)
70
+ - **Changeset** (if the project uses changesets) Include findings in the commit or note them for follow-up. Do not ask - include what's clearly needed, flag what's ambiguous as a note in the commit body.
54
71
 
55
72
  3. **Compose** - Write the commit message using Conventional Commits format, applying conventions learned from the inspect step. The commit message must be based on the actual diff contents.
56
73
 
57
74
  4. **Execute** - delegate to /builder with exact message, files to stage, and instructions to run validation (`check`, `test`) before committing. Include the commit message in the delegation.
58
75
 
59
- 5. **Stop** - report result. Do not chain another commit or start new implementation work. Dispatch /reviewer per rule #9 if needed.
76
+ 5. **Stop** - report result using the Work Results table format below. Do not chain another commit or start new implementation work. Dispatch /reviewer per rule #9 if needed.
60
77
 
61
78
  6. **Push** - Check current branch name first: `git branch --show-current`
62
79
  - If on `main` or `master`: ask via `question()` - primary branch only.
63
80
  - If on any other branch (feature branch): push automatically after successful validation. Do not ask.
64
81
  - Do not push every intermediate commit - push when a meaningful batch is ready or before creating a PR.
65
82
 
66
- 7. **PR** - After pushing to a feature branch (non-main/master) where no PR exists yet, create a PR automatically. Check the remote URL (`git remote -v`) to detect the platform (GitHub → `gh`, GitLab → `glab`, Bitbucket → `bb`), then use the appropriate CLI or API. Do not ask - just create it. The user can edit after creation.
83
+ 7. **PR** - After pushing to a feature branch where no PR exists yet, create one automatically. Check the remote URL (`git remote -v`) to detect the platform (GitHub → `gh`, GitLab → `glab`, Bitbucket → `bb`), then use the appropriate CLI or API. Do not ask - just create it.
84
+
85
+ **On subsequent pushes to the same branch**: update the PR title and description to reflect the cumulative changes. Add a "## Changes" section with a file-by-file table (same format as Work Results). Keep docs, changelogs, and changesets in sync with what the PR actually contains.
67
86
 
68
87
  ## Workflow Mode Override
69
88
 
@@ -253,7 +272,9 @@ Examples:
253
272
 
254
273
  ## Work Results
255
274
 
256
- After each builder task completes, present a structured summary of what changed. Synthesize builder output. Use this table format:
275
+ This format is mandatory after every builder task that lands a code change (see CRITICAL RULE #14). Overrides "write for humans" guidance for this specific output.
276
+
277
+ After each builder task completes, present a structured summary of what changed. Synthesize builder output. Use exactly this table format:
257
278
 
258
279
  ```
259
280
  ## Changes