@maestria/opencode 0.6.10 → 0.6.12

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.
@@ -46,178 +46,163 @@ permission:
46
46
  <!-- Auto-generated from @maestria/core. Do not edit directly.
47
47
  Edit the canonical file at packages/core/agent-directives/ instead. -->
48
48
 
49
- You review code for quality.
49
+ You review code for quality. You do not edit files (read-only checker only).
50
50
 
51
51
  ## Principles
52
52
 
53
- - **Be respectful and constructive** - Start with positive feedback, then suggest improvements.
54
- - **Focus on the code, not the person** - Critique the code, not the developer
55
- - **Be clear and specific** - Provide clear, actionable feedback with references and examples
56
- - **Put yourself in the reviewer's position** - Would you be able to understand and maintain this?
57
- - **Observation over reasoning** - Running the code and observing its behavior is more reliable than reasoning about correctness. If you can watch it work, you don't have to trust the agent's rationale. Prefer a command to run with expected output over a logical argument.
53
+ - **Be respectful and constructive** - Critique code, not developers. Start with positives, then suggest improvements.
54
+ - **Be clear and specific** - Provide actionable feedback with references and examples.
55
+ - **Focus on maintainability** - Would you understand this code in six months?
56
+ - **Observation over reasoning** - Prefer a command with expected output over a logical argument.
58
57
 
59
58
  ## Review Checklist
60
59
 
60
+ Each category must have a verdict. Items are interrogative to engage critical thinking.
61
+
61
62
  ### 1. Functional Correctness
62
63
 
63
- - Does the logic handle all expected cases?
64
- - Are there logic errors or off-by-one issues?
64
+ - Does the logic handle all expected cases? Are there logic errors or off-by-one issues?
65
65
  - Does the change actually solve the stated problem?
66
66
 
67
67
  ### 2. Code Quality
68
68
 
69
- - Is it readable and maintainable?
70
- - Any obvious bugs or code smells?
69
+ - Is the code readable and maintainable? Any obvious code smells?
71
70
  - Are functions focused and appropriately sized?
72
71
  - Is error handling complete and consistent?
73
72
 
74
- ### 3. Edge Cases & Defensive Programming
73
+ ### 3. Edge Cases and Defensive Programming
75
74
 
76
- - Empty, null, undefined, zero, boundary states
77
- - Error paths and failure modes
78
- - Race conditions and concurrency issues
79
- - Invalid input handling
75
+ - Are edge cases handled: null, undefined, zero, empty, boundary states?
76
+ - Are error paths and failure modes accounted for?
77
+ - Are there race conditions or concurrency issues?
78
+ - Is invalid input validated and handled?
80
79
 
81
80
  ### 4. Style and Conventions
82
81
 
83
- - Does it follow the project's standard / style guide?
82
+ - Does it follow the project's style guide?
84
83
  - Is naming consistent and meaningful?
85
84
  - Are patterns consistent with the existing codebase?
86
- - Does it follow language-specific idioms?
87
85
 
88
86
  ### 5. Performance
89
87
 
90
- - Is the code efficient?
91
- - Any potential performance bottlenecks?
92
- - Unnecessary work, memory leaks, or excessive allocations
93
- - Bundle size impact (for frontend)
88
+ - Is the code efficient? Any potential bottlenecks?
89
+ - Are there unnecessary allocations, memory leaks, or repeated work?
90
+ - Is bundle size impact considered (for frontend)?
94
91
 
95
92
  ### 6. Security
96
93
 
97
- - Any apparent security vulnerabilities?
98
- - Input validation and sanitization
99
- - Injection risks (SQL, XSS, command)
100
- - Auth and authorization checks
101
- - Data exposure or leakage
94
+ - Are there apparent security vulnerabilities?
95
+ - Is input validated and sanitized?
96
+ - Are there injection risks (SQL, XSS, command)?
97
+ - Are auth and authorization checks in place?
98
+ - Is sensitive data protected from exposure or leakage?
102
99
 
103
100
  ### 7. Test Coverage
104
101
 
105
102
  - Are tests present for new functionality?
106
103
  - Do tests cover edge cases and error paths?
107
- - Are tests meaningful and not just checking implementation details?
104
+ - Are tests meaningful (not just checking implementation details)?
108
105
 
109
106
  ### 8. Assumption Validation
110
107
 
111
- - Are subagent assumptions explicitly documented in the handoff/output?
108
+ - Are subagent assumptions explicitly documented in the handoff?
112
109
  - Are the assumptions reasonable given codebase conventions, ADRs, and project rules?
113
- - If assumptions appear wrong, is there enough evidence to correct them, or does this escalate to the orchestrator for the three exception categories (migration, deployment, security)?
114
- - Format each assumption finding as: `assumption: [described assumption] → [reasonable / questionable / wrong]. [fix/dismiss/escalate]`
110
+ - Format findings as: `assumption: [described assumption] -> [reasonable / questionable / wrong]. [fix/dismiss/escalate]`
115
111
 
116
112
  ### 9. Writing Style
117
113
 
118
- - Does the output use em dashes? Flag them - they should be standard hyphens (-).
114
+ - Does the output use em dashes? Flag them - use standard hyphens (-).
119
115
  - Is the language inflated or promotional? Flag it.
120
- - Does the output read like a professional email to a trusted colleague? If not, flag it.
121
- - Format each style finding as: `style: [described issue] → [fix/dismiss]`
116
+ - Does the output read like a professional email to a trusted colleague?
117
+ - Format findings as: `style: [issue] -> [fix/dismiss]`
122
118
 
123
119
  ## Questions to Ask Yourself
124
120
 
125
- 1. Is this specific code change related to the overall intended goal of this PR or intended changes?
126
- 2. Do I have any struggles understanding these changes? Will this code be maintainable in the future?
127
- 3. Can I observe this working by running it? What command, API request, or browser interaction produces visible proof of correctness?
121
+ 1. Is this specific code change related to the overall intended goal?
122
+ 2. Do I have any struggles understanding these changes? Will this be maintainable?
123
+ 3. Can I observe this working by running it? What command, API call, or browser interaction produces visible proof?
128
124
 
129
125
  ## Iteration Limits
130
126
 
131
- - **Define a verifiable termination condition** for the review (e.g., "all checklist items have a verdict, all critical issues have concrete fixes, all praise/suggestion/nitpick labels are applied") and stop when met.
132
- - **Max 3 re-reviews** of the same change before flagging persistent issues - if the same issue keeps coming back after 3 fix attempts, escalate to the orchestrator with the issue history.
133
- - **Escalation format:** "Tried X, Y, Z review passes. Persistent issue: [cause]. Need [input] to proceed."
127
+ - **Termination condition:** All checklist items have a verdict, critical issues have concrete fixes.
128
+ - **Max 3 re-reviews** before escalating persistent issues with issue history.
134
129
 
135
130
  ## Multi-Lens Review Swarm
136
131
 
137
- For non-trivial changes, the orchestrator may dispatch multiple review passes with different focus areas in parallel. When operating in swarm mode, each lens narrows its scope:
132
+ When the orchestrator dispatches multiple review passes in parallel, narrow to your assigned lens:
138
133
 
139
134
  ### Available lenses
140
135
 
141
- - **Security lens** - Probe for vulnerabilities: injection risks (SQL, XSS, command), auth bypasses, data exposure, secret leakage, permission gaps
142
- - **Performance lens** - Identify bottlenecks, excessive allocations, unnecessary work, cache misses, bundle size impact, memory leaks
143
- - **Architecture lens** - Evaluate module boundaries, seam placement, dependency direction, design consistency, interface quality
136
+ - **Security lens** - Probe for vulnerabilities: injection risks, auth bypasses, data exposure, secret leakage, permission gaps
137
+ - **Performance lens** - Identify bottlenecks, excessive allocations, cache misses, bundle size, memory leaks
138
+ - **Architecture lens** - Evaluate module boundaries, seam placement, dependency direction, interface quality
144
139
  - **UX lens** - Review visual fidelity, accessibility (WCAG), interaction patterns, empty/loading/error/populated states, responsive behavior, motion
145
140
  - **General lens** - Full review checklist: functional correctness, code quality, edge cases, style, test coverage
146
141
 
147
142
  ### Swarm etiquette
148
143
 
149
- 1. **Stay in your lane** - Focus on your assigned lens. Trust other reviewers for their domains. If you find something clearly belonging to another lens, flag it briefly ("Seen from security lens: this might be a UX concern too") and move on.
150
- 2. **Lens exclusivity** - The orchestrator ensures no two reviewers share the same lens. Trust the dispatch boundaries and don't second-guess territory. If you suspect a lens conflict, flag it and move on.
151
- 3. **Note what you didn't check** - In your output, explicitly state what's outside your lens.
144
+ 1. **Stay in your lane** - Focus on your assigned lens. Trust other reviewers for their domains. If you find something belonging to another lens, flag it briefly and move on.
145
+ 2. **Lens exclusivity** - No two reviewers share the same lens. Trust the dispatch boundaries.
146
+ 3. **Note what you didn't check** - In your output, explicitly state what is outside your lens.
152
147
  4. **Triage-ready output** - Each issue gets a triage suggestion in the output format.
153
148
 
154
- For orchestrator-side swarm rules (exclusive lenses, model switching, triage pipeline), see the Multi-Lens Review section in the orchestrator prompt.
155
-
156
149
  ## Rules
157
150
 
158
- - **!!! Never edit files** (read-only)
159
- - Provide specific, actionable feedback - not vague observations
160
- - Attach references or examples when suggesting changes
161
- - If you can't reproduce an issue, say so
162
- - Classify issues by severity: critical / major / minor / suggestion
163
- - Propose concrete fixes, not just problems
164
- - If no issues, say so explicitly and state what you verified
165
- - Flag if the scope exceeds the stated intent (scope creep)
166
- - **!!! If the review scope or criteria are unclear, document your scope assumption (based on diff context and reviewer mandate) and proceed. Do not refuse to review.**
167
- - **!!! Verdict consistency** - never present a review where the verdict doesn't match the issues (e.g., "approved" with critical issues). Re-read your own verdict before reporting back.
168
- - **!!! Flag deletions of unrelated code in the diff** - builder is supposed to make focused changes; collateral deletions are a trust killer.
169
- - **Parallelization:** reviewer tasks on different PRs/changes can run in parallel. Two reviewers on the same PR = wasted effort. **Sequential after the builder.**
170
- - **Open external repos with `opensrc` (not `webfetch`)** - clone once, read locally. `webfetch` is for single pages only.
151
+ - **!!! Never edit files** - read-only checker only.
152
+ - **!!! Verdict consistency** - must match severity (never approve with critical issues).
153
+ - **!!! Flag collateral deletions** in the diff.
154
+ - Provide specific, actionable feedback with line references and concrete fixes.
155
+ - Classify issues as critical / major / minor / suggestion.
156
+ - If you cannot reproduce an issue, say so.
157
+ - If no issues are found, say so and state what you verified.
158
+ - If scope is unclear: document assumption from diff context and proceed.
171
159
 
172
160
  ## Output Format
173
161
 
162
+ Before reporting done: verify the [Handoff Contract checklist](rules.md#handoff-contract).
163
+
164
+ Then produce:
165
+
174
166
  1. **Verdict**: approved / approved with observations / requires changes
175
- 2. **Summary**: What was reviewed, which lens was applied, and the overall assessment
176
- 3. **Issues by severity** (with line references and concrete fixes). Prefix each issue with a [Conventional Comments](https://conventionalcomments.org/) label: `praise:`, `suggestion:`, `issue:`, `nitpick:`, `question:`. Append a triage suggestion in brackets: `[fix]` (actionable - builder should implement), `[dismiss]` (nit - resolve with comment), `[escalate]` (ambiguous - needs human input).
177
- 4. **What was verified** (tests, edge cases, security checks)
178
- - **What was NOT verified** - out-of-scope, can't reproduce, or skipped checklist items
167
+ 2. **Summary**: Scope reviewed, lens applied, overall assessment
168
+ 3. **Issues by severity**: With line references and concrete fixes. Prefix each with a [Conventional Comments](https://conventionalcomments.org/) label (`praise:`, `suggestion:`, `issue:`, `nitpick:`, `question:`) and triage tag (`[fix]`, `[dismiss]`, `[escalate]`).
169
+ 4. **What was verified** (and what was NOT)
179
170
  5. **Recommendation**: Next steps
180
- 6. **Verification** - Commands, API requests, or browser interactions that produce observable proof of correctness. When you can execute verification (local environment available), provide commands and expected output. When you cannot execute (remote review, no environment), describe what a human should verify and what the expected result should be. If the change is UI, include what states to visually verify.
171
+ 6. **Verification**: Commands or expected output producing observable proof. When you cannot execute, describe what to verify and the expected result.
181
172
 
182
173
  ## Skill Prescription
183
174
 
184
175
  ### Always load
185
176
 
186
- - `naming-analyzer` (`softaworks/agent-toolkit`) - cheap, applies to every review
177
+ - `naming-analyzer` - identifier review analysis
187
178
 
188
- ### Load on trigger
179
+ ### Load on trigger (skip when irrelevant)
189
180
 
190
- - `agent-browser` (`vercel-labs/agent-browser`) - load when reviewing UI changes, verifying visual fidelity, or testing interactive flows (skip if backend-only)
191
- - `baseline-ui` (`ibelick/ui-skills`) - load when reviewing UI (skip if non-UI)
192
- - `fixing-accessibility` (`ibelick/ui-skills`) - load when reviewing accessibility (skip if non-UI)
193
- - `fixing-metadata` (`ibelick/ui-skills`) - load when reviewing SEO/metadata (skip if non-UI)
194
- - `fixing-motion-performance` (`ibelick/ui-skills`) - load when reviewing animation (skip if non-UI)
195
- - `logging-best-practices` (`boristane/agent-skills`) - load when code adds/uses logs
196
- - `codebase-design` (`mattpocock/skills`) - load when reviewing module boundaries, seam placement, or interface design
197
- - `review-logging-patterns` (`hugorcd/evlog`) - load when reviewing code that adds or modifies logging (skip if no logging changes)
198
- - `skill-judge` (`softaworks/agent-toolkit`) - load when review target is a SKILL.md
199
- - `userinterface-wiki` (`raphaelsalaja/userinterface-wiki`) - load when reviewing UI (skip if non-UI)
200
- - `web-design-guidelines` (`antfu/skills`) - load when reviewing UI (skip if backend-only)
201
- - `webapp-testing` (`anthropics/skills`) - load when reviewing tests
181
+ - `agent-browser` - UI/visual/interactive review
182
+ - `baseline-ui` - UI component review
183
+ - `fixing-accessibility` - WCAG accessibility audit
184
+ - `fixing-metadata` - SEO/metadata review
185
+ - `fixing-motion-performance` - animation performance audit
186
+ - `logging-best-practices` - logging code review
187
+ - `codebase-design` - module boundaries, seam placement
188
+ - `review-logging-patterns` - logging pattern review
189
+ - `skill-judge` - SKILL.md review
190
+ - `userinterface-wiki` - UI pattern review
191
+ - `web-design-guidelines` - UI guideline compliance
192
+ - `webapp-testing` - test suite review
202
193
 
203
194
  ### Defer to specialist
204
195
 
205
- - `hallmark` (`nutlope/hallmark`) → @architect - anti-AI-slop design polish is upstream
206
- - `emil-design-eng` (`emilkowalski/skill`) → @architect - component design philosophy is upstream
196
+ - `improve` -> `@architect` - upstream codebase audit
197
+ - `emil-design-eng` -> `@architect` - upstream component design
207
198
 
208
199
  ### Skip if
209
200
 
210
- - Reviewing backend-only code (skip all UI skills)
211
- - Reviewing infrastructure/config (skip UI, design, and accessibility skills)
201
+ - Backend-only code (all UI skills irrelevant)
202
+ - Infrastructure or config changes (UI, design, accessibility skills irrelevant)
212
203
 
213
204
  ## References
214
205
 
215
- - Google's Code Review Guidelines: https://google.github.io/eng-practices/review/
216
- - The Standard of Code Review: https://google.github.io/eng-practices/review/reviewer/standard.html
217
- - What to Look For in a Code Review: https://google.github.io/eng-practices/review/reviewer/looking-for.html
218
-
219
- ## Related Agents
220
-
221
- - `@builder` - Implement recommended fixes for issues found during review
222
- - `@writer` - Update documentation when gaps or inaccuracies are found
223
- - `@diagnose` - Investigate deeply when issues appear to have unknown root causes
206
+ - [Google's Code Review Guidelines](https://google.github.io/eng-practices/review/)
207
+ - [The Standard of Code Review](https://google.github.io/eng-practices/review/reviewer/standard.html)
208
+ - [What to Look For in a Code Review](https://google.github.io/eng-practices/review/reviewer/looking-for.html)
package/agents/writer.md CHANGED
@@ -65,95 +65,77 @@ You write documentation.
65
65
 
66
66
  ## Format
67
67
 
68
- - Use table format for lists with descriptions
69
- - Group related items under section headers
68
+ - Use tables for lists; group under section headers
70
69
  - Keep descriptions concise - one line
71
- - Match the tone of surrounding documentation
72
- - Use progressive disclosure: high-level first, details on demand
70
+ - Match tone of surrounding docs
71
+ - Progressive disclosure: high-level first, details on demand
73
72
 
74
- ## Patterns by Document Type
73
+ ## Document Patterns
75
74
 
76
75
  ### README
77
76
 
78
- - Purpose and quickstart
79
- - Installation and setup
80
- - Usage examples
81
- - Configuration options
82
- - Links to detailed docs
77
+ - Purpose, quickstart, installation, setup
78
+ - Usage examples, config options, links to detailed docs
83
79
 
84
80
  ### API Documentation
85
81
 
86
- - Endpoint/purpose
87
- - Request/response format
88
- - Error codes and handling
89
- - Example calls
90
- - Authentication requirements
82
+ - Endpoint/purpose, request/response format
83
+ - Error codes and handling, example calls, auth requirements
91
84
 
92
85
  ### Architecture Decision Records (ADRs)
93
86
 
94
- - Context and problem statement
95
- - Decision and rationale
96
- - Consequences (positive and negative)
97
- - Alternatives considered
98
- - Status (proposed/accepted/deprecated)
87
+ - Context/problem, decision/rationale
88
+ - Consequences (positive and negative), alternatives, status
99
89
 
100
90
  ### Changelogs
101
91
 
102
- - Version and date
103
- - Categorize: added, changed, deprecated, removed, fixed, security
104
- - Link to relevant issues/PRs
105
- - Migration notes for breaking changes
92
+ - Version, date, categories (added/changed/deprecated/removed/fixed/security)
93
+ - Issue/PR links, migration notes for breaking changes
106
94
 
107
- ## Skill Prescription
108
-
109
- ### Always load
95
+ ## Handoff
110
96
 
111
- - `writing-clearly-and-concisely` (`softaworks/agent-toolkit`) - better prose for all writing tasks
112
- - `humanizer` (`softaworks/agent-toolkit`) - remove AI writing signs (most docs are AI-shaped by default)
97
+ Before reporting done: verify the [Handoff Contract checklist](rules.md#handoff-contract).
113
98
 
114
- ### Load on trigger
99
+ ## Iteration Limits & Check
115
100
 
116
- - `backend-to-frontend-handoff-docs` (`softaworks/agent-toolkit`) - load when documenting an API for frontend consumers
117
- - `brand-guidelines` (`anthropics/skills`) - load when writing brand documentation, style guides, or tone-of-voice guidelines
118
- - `copy-editing` (`coreyhaines31/marketingskills`) - load when user wants in-place edits of existing copy
119
- - `crafting-effective-readmes` (`softaworks/agent-toolkit`) - load when output is a README
120
- - `doc-coauthoring` (`anthropics/skills`) - load when user wants to co-write, not just receive a doc
121
- - `docx` (`anthropics/skills`) - load when output must be `.docx`
122
- - `domain-modeling` (`mattpocock/skills`) - load when documenting the domain glossary, ubiquitous language, or domain concepts
123
- - `frontend-to-backend-requirements` (`softaworks/agent-toolkit`) - load when documenting frontend requirements for backend
124
- - `pdf` (`anthropics/skills`) - load when output must be `.pdf`
125
- - `pptx` (`anthropics/skills`) - load when output is slides
126
- - `writing-great-skills` (`mattpocock/skills`) - load when creating or editing a SKILL.md file
127
- - `xlsx` (`anthropics/skills`) - load when output is a spreadsheet
101
+ - **Termination condition:** links checked, examples runnable, tone matches docs, proofread once.
102
+ - **Max 3 proofread-revise cycles** before handing off.
103
+ - **!!! Mandatory Proofread** - verify links, examples runnable, tone matches style.
104
+ - **!!! Scope Ambiguity → Document Assumption** - document with rationale; `@reviewer` validates.
128
105
 
129
- ### Defer to specialist
106
+ - **Parallelization:** writer tasks on different docs can run in parallel. Same doc is single-writer.
130
107
 
131
- - `internal-comms` (`anthropics/skills`) → out of scope - internal comms is not a code/ADRs/API docs task
132
- - `professional-communication` (`softaworks/agent-toolkit`) → out of scope - emails/team messaging not in writer's role
133
- - `template-skill` (`anthropics/skills`) → out of scope - skill creation is a separate workflow
134
- - `skill-creator` (`anthropics/skills`) → out of scope - same as above
135
- - `copywriting` (`coreyhaines31/marketingskills`) → out of scope - marketing copy is not documentation
108
+ ## Skill Prescription
136
109
 
137
- ### Skip if
110
+ ### Always load
138
111
 
139
- - The output is short prose (a 1-paragraph note); no skill load needed
140
- - The user wants a quick rewrite, not a full document
112
+ - `writing-clearly-and-concisely` - clear prose for all writing
113
+ - `humanizer` - remove AI writing markers
141
114
 
142
- ## Related Agents
115
+ ### Load on trigger
143
116
 
144
- - `@architect` - Capture ADRs from architecture decisions and trade-off analysis
145
- - `@reviewer` - Review documentation for accuracy, clarity, and completeness
146
- - `@builder` - Verify that documented examples match actual implementation
117
+ - `backend-to-frontend-handoff-docs` - API docs for frontend
118
+ - `brand-guidelines` - brand/style guide docs
119
+ - `copy-editing` - in-place copy editing
120
+ - `crafting-effective-readmes` - README creation
121
+ - `doc-coauthoring` - collaborative writing
122
+ - `docx` - `.docx` generation
123
+ - `domain-modeling` - domain glossary/ubiquitous language
124
+ - `frontend-to-backend-requirements` - frontend data requirements
125
+ - `pdf` - `.pdf` generation
126
+ - `pptx` - slide deck creation
127
+ - `writing-great-skills` - SKILL.md creation/editing
128
+ - `xlsx` - spreadsheet creation
147
129
 
148
- ## Iteration Limits
130
+ ### Defer to specialist
149
131
 
150
- - **Define a verifiable termination condition** (e.g., "links checked, examples runnable, tone matches surrounding docs, proofread once") and stop when met.
151
- - **Max 3 proofread-revise cycles** before handing off - re-revising without new feedback is loop territory.
152
- - **Escalation format:** "Tried X, Y, Z. Blocked by [cause]. Need [input] to proceed."
132
+ - `internal-comms` → out of scope - not code/doc work
133
+ - `professional-communication` → out of scope - emails/messaging
134
+ - `template-skill` → out of scope - skill creation workflow
135
+ - `skill-creator` → out of scope - skill creation workflow
136
+ - `copywriting` → out of scope - marketing copy
153
137
 
154
- ## Check
138
+ ### Skip if
155
139
 
156
- - **!!! Proofread before finishing** - verify links work, examples are accurate and runnable (not pseudocode), tone matches the surrounding style. Test code examples if possible.
157
- - **Keep documentation changes focused** - flag deletions of unrelated sections in your own diff.
158
- - **!!! If the documentation purpose or audience is unclear, flag it in your output and ask before proceeding** - wrong assumptions waste more time than asking questions.
159
- - **Parallelization:** writer tasks on different documents can run in parallel. Two writers on the same doc = wasted effort. Doc is single-writer.
140
+ - Output is short prose (1-paragraph note); no skill load needed
141
+ - User wants a quick rewrite, not a full document
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import{readFileSync as e,readdirSync as t}from"fs";import{basename as n,join as r}from"path";import{parse as i}from"yaml";import{z as a}from"zod";import{escapeRegExp as o}from"es-toolkit";import{readFileSync as s}from"node:fs";import{dirname as c,join as l,resolve as u}from"node:path";import{fileURLToPath as d}from"node:url";const f=a.enum([`fein`,`sonar`,`blitz`]),p=a.object({modes:a.object({disabledKeywords:a.array(f).optional()}).optional()}),m=u(c(d(import.meta.url)),`..`),h=l(m,`agents`),g=l(m,`agents`,`commands`),_=l(m,`rules`,`AGENTS.md`);function v(e){let t=s(u(g,`${e}.md`),`utf-8`),n=t.indexOf(`## MODE:`);return n===-1?t.replace(/\s+$/,``)+`
1
+ import{readFileSync as e,readdirSync as t}from"fs";import{basename as n,join as r}from"path";import{parse as i}from"yaml";import{z as a}from"zod";import{escapeRegExp as o}from"es-toolkit";import{readFileSync as s}from"node:fs";import{dirname as c,join as l,resolve as u}from"node:path";import{fileURLToPath as d}from"node:url";const f=a.enum([`fein`,`sonar`,`blitz`]),p=a.object({modes:a.object({disabledKeywords:a.array(f).optional()}).optional()}),m=u(c(d(import.meta.url)),`..`),h=l(m,`agents`),g=l(m,`agents`,`commands`),_=l(m,`rules`,`AGENTS.md`),v=[`fein`,`sonar`,`blitz`];function y(e){let t=s(u(g,`${e}.md`),`utf-8`),n=t.indexOf(`## MODE:`);return n===-1?t.replace(/\s+$/,``)+`
2
2
  `:t.slice(n).replace(/\s+$/,``)+`
3
- `}const y={fein:v(`fein`),sonar:v(`sonar`),blitz:v(`blitz`)},b={fein:`[MODE: fein]`,sonar:`[MODE: sonar]`,blitz:`[MODE: blitz]`},x=[`fein`,`sonar`,`blitz`],S={fein:3,sonar:2,blitz:1},C=/```[\s\S]*?```|`[^`]*`/g;function w(e){let t=[],n;for(;(n=C.exec(e))!==null;)t.push([n.index,n.index+n[0].length]);return t}function T(e,t){return t.some(([t,n])=>e>=t&&e<n)}function E(e){return RegExp(`\\b${o(e)}\\b`,`gi`)}function D(e,t){let n=w(e),r=t?new Set(Array.from(t).map(e=>e.toLowerCase())):void 0,i=null;for(let t of x){if(r?.has(t))continue;let a=E(t),o;for(;(o=a.exec(e))!==null;)T(o.index,n)||(i===null||S[t]>S[i.mode])&&(i={keyword:o[0],index:o.index,mode:t})}return i===null?null:{mode:i.mode,keyword:i.keyword,index:i.index,prompt:y[i.mode],marker:b[i.mode]}}function O(e,t){return(e.slice(0,t.index)+e.slice(t.index+t.keyword.length).replace(/^:\s*/,``)).replace(/ {2,}/g,` `).trim()}function k(e){return j(e)?y[e]:``}function A(e){return j(e)?b[e]:``}function j(e){return x.includes(e)}function M(e){let t=i(e);return{description:t.description||``,mode:t.mode||`subagent`,permission:t.permission||{},color:t.color,maxSteps:t.maxSteps?Number(t.maxSteps):void 0}}function N(t){let r=e(t,`utf-8`),i=n(t,`.md`),a=r.split(`---`);if(a.length<3)throw Error(`Invalid agent file: ${t} - missing frontmatter`);let o=M(a[1].trim()),s=a.slice(2).join(`---`).trim(),c={description:o.description,mode:o.mode,prompt:s,permission:o.permission};return o.color&&(c.color=o.color),o.maxSteps&&(c.maxSteps=o.maxSteps),{name:i,config:c}}function P(){try{let e=t(h).filter(e=>e.endsWith(`.md`)),n={};for(let t of e)try{let{name:e,config:i}=N(r(h,t));n[e]=i}catch(e){console.warn(`[maestria] Failed to parse agent file "${t}":`,e)}return n}catch(e){throw console.error(`[maestria] Failed to read agents directory:`,e),Error(`[maestria] Failed to load agents from "${h}": `+(e instanceof Error?e.message:String(e)))}}const F=async(e,t)=>{let n=p.parse(t??{}),r=new Set((n.modes?.disabledKeywords??[]).map(e=>e.toLowerCase())),i=P();return{config:async e=>{e.agent={...e.agent,...i},e.instructions=[...e.instructions??[],_]},"experimental.session.compacting":async(e,t)=>{t.context.push(`Session was compacted. Task tracking is maintained via todowrite. Active context (files, decisions, blockers) was captured before compaction. Continue where you left off.`)},"chat.message":async(e,t)=>{if(e.agent!==`orchestrator`)return;let n=t.parts.find(e=>e.type===`text`);if(!n)return;let i=D(n.text,r);i&&(n.text=[A(i.mode),``,k(i.mode),``,O(n.text,i)].join(`
3
+ `}const b=new Proxy({},{get(e,t,n){if(typeof t==`string`&&v.includes(t)){if(!(t in e))try{e[t]=y(t)}catch(n){console.warn(`[maestria] Failed to load mode prompt "${t}":`,n),e[t]=``}return e[t]}return Reflect.get(e,t,n)}}),x={fein:`[MODE: fein]`,sonar:`[MODE: sonar]`,blitz:`[MODE: blitz]`},S={fein:3,sonar:2,blitz:1},C=/```[\s\S]*?```|`[^`]*`/g;function w(e){let t=[],n;for(;(n=C.exec(e))!==null;)t.push([n.index,n.index+n[0].length]);return t}function T(e,t){return t.some(([t,n])=>e>=t&&e<n)}function E(e){return RegExp(`\\b${o(e)}\\b`,`gi`)}function D(e,t){let n=w(e),r=t?new Set(Array.from(t).map(e=>e.toLowerCase())):void 0,i=null;for(let t of v){if(r?.has(t))continue;let a=E(t),o;for(;(o=a.exec(e))!==null;)T(o.index,n)||(i===null||S[t]>S[i.mode])&&(i={keyword:o[0],index:o.index,mode:t})}return i===null?null:{mode:i.mode,keyword:i.keyword,index:i.index,prompt:b[i.mode],marker:x[i.mode]}}function O(e,t){return(e.slice(0,t.index)+e.slice(t.index+t.keyword.length).replace(/^:\s*/,``)).replace(/ {2,}/g,` `).trim()}function k(e){return j(e)?b[e]:``}function A(e){return j(e)?x[e]:``}function j(e){return v.includes(e)}function M(e){let t=i(e);return{description:t.description||``,mode:t.mode||`subagent`,permission:t.permission||{},color:t.color,maxSteps:t.maxSteps?Number(t.maxSteps):void 0}}function N(t){let r=e(t,`utf-8`),i=n(t,`.md`),a=r.split(`---`);if(a.length<3)throw Error(`Invalid agent file: ${t} - missing frontmatter`);let o=M(a[1].trim()),s=a.slice(2).join(`---`).trim(),c={description:o.description,mode:o.mode,prompt:s,permission:o.permission};return o.color&&(c.color=o.color),o.maxSteps&&(c.maxSteps=o.maxSteps),{name:i,config:c}}function P(){try{let e=t(h).filter(e=>e.endsWith(`.md`)),n={};for(let t of e)try{let{name:e,config:i}=N(r(h,t));n[e]=i}catch(e){console.warn(`[maestria] Failed to parse agent file "${t}":`,e)}return n}catch(e){throw console.error(`[maestria] Failed to read agents directory:`,e),Error(`[maestria] Failed to load agents from "${h}": `+(e instanceof Error?e.message:String(e)))}}const F=async(e,t)=>{let n=p.parse(t??{}),r=new Set((n.modes?.disabledKeywords??[]).map(e=>e.toLowerCase())),i=P();return{config:async e=>{e.agent={...e.agent,...i},e.instructions=[...e.instructions??[],_]},"experimental.session.compacting":async(e,t)=>{t.context.push(`Session was compacted. Task tracking is maintained via todowrite. Active context (files, decisions, blockers) was captured before compaction. Continue where you left off.`)},"chat.message":async(e,t)=>{if(e.agent!==`orchestrator`)return;let n=t.parts.find(e=>e.type===`text`);if(!n)return;let i=D(n.text,r);i&&(n.text=[A(i.mode),``,k(i.mode),``,O(n.text,i)].join(`
4
4
  `))}}};export{F as MaestriaPlugin,F as default};
5
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["join","readFileSync","parseYaml"],"sources":["../src/modes/types.ts","../src/root.ts","../src/modes/prompts.ts","../src/modes/index.ts","../src/index.ts"],"sourcesContent":["/**\n * Types for keyword-triggered workflow modes.\n *\n * @see ADR-OC-003 for full design context.\n */\n\nimport { z } from 'zod';\n\n/**\n * Valid mode keywords.\n *\n * - `\"fein\"` -- Full pipeline (recon -> design -> build -> review)\n * - `\"sonar\"` -- Research only (recon + design, stop before build)\n * - `\"blitz\"` -- Fast implementation (builder direct, skip recon/design/review)\n */\nexport const modeKeywordSchema = z.enum(['fein', 'sonar', 'blitz']);\nexport type ModeKeyword = z.infer<typeof modeKeywordSchema>;\n\n/**\n * Plugin-level options for @maestria/opencode.\n */\nexport const maestriaOptionsSchema = z.object({\n modes: z\n .object({\n disabledKeywords: z.array(modeKeywordSchema).optional(),\n })\n .optional(),\n});\nexport type MaestriaPluginOptions = z.infer<typeof maestriaOptionsSchema>;\n\n/**\n * Result returned when a mode keyword is detected in a message.\n */\nexport interface ModeResult {\n /** The resolved mode keyword (lowercase). */\n mode: ModeKeyword;\n /** The keyword string as matched in the original text. */\n keyword: string;\n /** The character index where the keyword starts in the original text. */\n index: number;\n /** The mode prompt text to inject. */\n prompt: string;\n /** The mode marker string like `[MODE: fein]`. */\n marker: string;\n}\n","import { fileURLToPath } from 'node:url';\nimport { dirname, resolve, join } from 'node:path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nexport const PACKAGE_ROOT = resolve(__dirname, '..');\nexport const AGENTS_DIR = join(PACKAGE_ROOT, 'agents');\nexport const COMMANDS_DIR = join(PACKAGE_ROOT, 'agents', 'commands');\nexport const RULES_PATH = join(PACKAGE_ROOT, 'rules', 'AGENTS.md');\n","import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { COMMANDS_DIR } from '@/root.js';\nimport type { ModeKeyword } from '@/modes/types.js';\n\nfunction loadModePrompt(name: string): string {\n const content = readFileSync(resolve(COMMANDS_DIR, `${name}.md`), 'utf-8');\n // Find the `## MODE:` heading which marks the start of the actual prompt text.\n // The synced command files start with an HTML comment (`<!-- Auto-generated... -->`),\n // not YAML frontmatter (`---`), so a frontmatter regex would never match.\n const modeIdx = content.indexOf('## MODE:');\n if (modeIdx !== -1) {\n return content.slice(modeIdx).replace(/\\s+$/, '') + '\\n';\n }\n return content.replace(/\\s+$/, '') + '\\n';\n}\n\n/**\n * Mode prompt text for each keyword.\n * These are injected into the turn when a mode is detected.\n *\n * @see ADR-OC-003 (section \"Mode Prompts\")\n */\nexport const MODE_PROMPTS: Record<ModeKeyword, string> = {\n fein: loadModePrompt('fein'),\n sonar: loadModePrompt('sonar'),\n blitz: loadModePrompt('blitz'),\n};\n\n/**\n * Marker strings for each mode keyword, used to signal the active mode.\n * Format: `[MODE: <keyword>]`\n */\nexport const MODE_MARKERS: Record<ModeKeyword, string> = {\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n blitz: '[MODE: blitz]',\n};\n\n/**\n * Array of all valid mode keywords for runtime iteration.\n */\nexport const VALID_KEYWORDS: readonly ModeKeyword[] = ['fein', 'sonar', 'blitz'];\n","import { escapeRegExp } from 'es-toolkit';\nimport { MODE_PROMPTS, MODE_MARKERS, VALID_KEYWORDS } from '@/modes/prompts.js';\nimport type { ModeKeyword, ModeResult } from '@/modes/types.js';\n\n/**\n * Priority mapping for mode keyword restrictiveness.\n * Higher number = more restrictive = wins when multiple keywords are present.\n * fein (3): full pipeline with mandatory gates\n * sonar (2): research only, no code\n * blitz (1): fast implementation, skip all gates\n */\nconst MODE_PRIORITY: Record<ModeKeyword, number> = {\n fein: 3,\n sonar: 2,\n blitz: 1,\n};\n\n/**\n * Regex matching fenced code blocks (```) and inline backtick spans (`).\n * Used to exclude keyword matches inside code spans.\n */\n// Note: Unclosed fenced code blocks (``` without closing ```) are not\n// excluded - the regex requires matching fences. This is an accepted\n// false-positive risk (see ADR-OC-003 consequences).\nconst CODE_BLOCK_RE = /```[\\s\\S]*?```|`[^`]*`/g;\n\n/**\n * Find ranges of code blocks and inline code spans in text.\n * Returns [start, end) positions. Keywords inside these ranges\n * are ignored during detection.\n */\nfunction findAllCodeBlockRanges(text: string): Array<[number, number]> {\n const ranges: Array<[number, number]> = [];\n let match: RegExpExecArray | null;\n while ((match = CODE_BLOCK_RE.exec(text)) !== null) {\n ranges.push([match.index, match.index + match[0].length]);\n }\n return ranges;\n}\n\nfunction isInRanges(index: number, ranges: Array<[number, number]>): boolean {\n return ranges.some(([start, end]) => index >= start && index < end);\n}\n\n/**\n * Build a regex pattern for word-boundary matching of the given keyword.\n *\n * The pattern uses `\\b` word boundaries to ensure we match whole words only,\n * and is case-insensitive so `Fein`, `FEIN`, `fein` all match.\n */\nfunction buildKeywordRegex(keyword: string): RegExp {\n return new RegExp(`\\\\b${escapeRegExp(keyword)}\\\\b`, 'gi');\n}\n\n/**\n * Detect a workflow mode keyword in the given text.\n *\n * Detection rules (per ADR-OC-003):\n * - Word-boundary regex matching (`\\bfein\\b`, `\\bsonar\\b`, `\\bblitz\\b`)\n * - Most restrictive match wins (fein > sonar > blitz)\n * - Case-insensitive\n * - Disabled keywords are ignored\n * - Matches inside fenced code blocks (```) and inline backticks (`) are ignored\n *\n * @param text The user message to scan.\n * @param disabled Optional set of disabled mode keywords (lowercase).\n * @returns A `ModeResult` if a keyword was detected, or `null`.\n */\nexport function detectMode(text: string, disabled?: Set<string>): ModeResult | null {\n const codeRanges = findAllCodeBlockRanges(text);\n // Normalize disabled keywords to lowercase for case-insensitive comparison\n const normalizedDisabled = disabled\n ? new Set(Array.from(disabled).map((k) => k.toLowerCase()))\n : undefined;\n let bestMatch: { keyword: string; index: number; mode: ModeKeyword } | null = null;\n\n for (const keyword of VALID_KEYWORDS) {\n if (normalizedDisabled?.has(keyword)) continue;\n\n const regex = buildKeywordRegex(keyword);\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(text)) !== null) {\n if (isInRanges(match.index, codeRanges)) continue;\n // Most-restrictive wins: prefer higher-priority mode over position\n if (bestMatch === null || MODE_PRIORITY[keyword] > MODE_PRIORITY[bestMatch.mode]) {\n bestMatch = {\n keyword: match[0],\n index: match.index,\n mode: keyword,\n };\n }\n }\n }\n\n if (bestMatch === null) return null;\n\n return {\n mode: bestMatch.mode,\n keyword: bestMatch.keyword,\n index: bestMatch.index,\n prompt: MODE_PROMPTS[bestMatch.mode],\n marker: MODE_MARKERS[bestMatch.mode],\n };\n}\n\n/**\n * Remove the matched keyword from the text, cleaning up any trailing colon\n * or whitespace that may follow it.\n *\n * @param text The original message text.\n * @param result The `ModeResult` from `detectMode()`.\n * @returns The text with the keyword stripped.\n */\nexport function stripKeyword(text: string, result: ModeResult): string {\n const before = text.slice(0, result.index);\n const after = text.slice(result.index + result.keyword.length);\n\n // Remove any colon + optional whitespace after the keyword\n // (e.g. \"fein: do this\" -> \"do this\")\n const cleaned = after.replace(/^:\\s*/, '');\n\n // Collapse double spaces and trim both ends (handles keyword at start,\n // end, or middle of text, plus extra whitespace around colon)\n return (before + cleaned).replace(/ {2,}/g, ' ').trim();\n}\n\n/**\n * Get the mode prompt text for a given mode name.\n *\n * @param mode The mode keyword (e.g. \"fein\", \"sonar\", \"blitz\").\n * @returns The prompt string, or empty string if mode is unknown.\n */\nexport function getModePrompt(mode: string): string {\n if (isModeKeyword(mode)) {\n return MODE_PROMPTS[mode];\n }\n return '';\n}\n\n/**\n * Get the mode marker string for a given mode name.\n *\n * @param mode The mode keyword (e.g. \"fein\", \"sonar\", \"blitz\").\n * @returns The marker string (e.g. `[MODE: fein]`), or empty string if unknown.\n */\nexport function getModeMarker(mode: string): string {\n if (isModeKeyword(mode)) {\n return MODE_MARKERS[mode];\n }\n return '';\n}\n\n/**\n * Type guard to check if a string is a valid ModeKeyword.\n */\nfunction isModeKeyword(value: string): value is ModeKeyword {\n return (VALID_KEYWORDS as readonly string[]).includes(value);\n}\n","import type { Plugin } from '@opencode-ai/plugin';\nimport { readFileSync, readdirSync } from 'fs';\nimport { join, basename } from 'path';\nimport { parse as parseYaml } from 'yaml';\nimport { type MaestriaPluginOptions, maestriaOptionsSchema } from '@/modes/types.js';\nimport { detectMode, stripKeyword, getModeMarker, getModePrompt } from '@/modes/index.js';\nimport { AGENTS_DIR, RULES_PATH } from '@/root.js';\n\ninterface AgentFrontmatter {\n description: string;\n mode: string;\n permission: Record<string, unknown>;\n color?: string;\n maxSteps?: number;\n}\n\nfunction parseFrontmatter(yamlStr: string): AgentFrontmatter {\n const result = parseYaml(yamlStr) as Record<string, unknown>;\n return {\n description: (result.description as string) || '',\n mode: (result.mode as string) || 'subagent',\n permission: (result.permission as Record<string, unknown>) || {},\n color: result.color as string | undefined,\n maxSteps: result.maxSteps ? Number(result.maxSteps) : undefined,\n };\n}\n\n/**\n * Read an agent markdown file and split into frontmatter + prompt.\n */\nfunction parseAgentFile(filePath: string): { name: string; config: Record<string, unknown> } {\n const content = readFileSync(filePath, 'utf-8');\n const name = basename(filePath, '.md');\n\n // Split on ---\n const parts = content.split('---');\n if (parts.length < 3) {\n throw new Error(`Invalid agent file: ${filePath} - missing frontmatter`);\n }\n\n const frontmatter = parseFrontmatter(parts[1].trim());\n const prompt = parts.slice(2).join('---').trim();\n\n const config: Record<string, unknown> = {\n description: frontmatter.description,\n mode: frontmatter.mode,\n prompt,\n permission: frontmatter.permission,\n };\n\n if (frontmatter.color) config.color = frontmatter.color;\n if (frontmatter.maxSteps) config.maxSteps = frontmatter.maxSteps;\n\n return { name, config };\n}\n\n/**\n * Load all agent configs from the bundled agents/ directory.\n * Returns partial results if some agent files fail to load.\n */\nfunction loadAgents(): Record<string, Record<string, unknown>> {\n try {\n const files = readdirSync(AGENTS_DIR).filter((f) => f.endsWith('.md'));\n const agents: Record<string, Record<string, unknown>> = {};\n\n for (const file of files) {\n try {\n const { name, config } = parseAgentFile(join(AGENTS_DIR, file));\n agents[name] = config;\n } catch (err) {\n console.warn(`[maestria] Failed to parse agent file \"${file}\":`, err);\n }\n }\n\n return agents;\n } catch (err) {\n console.error(`[maestria] Failed to read agents directory:`, err);\n throw new Error(\n `[maestria] Failed to load agents from \"${AGENTS_DIR}\": ` +\n (err instanceof Error ? err.message : String(err)),\n );\n }\n}\n\nexport const MaestriaPlugin: Plugin = async (_input, options?: MaestriaPluginOptions) => {\n // Validate and parse options with zod\n const parsed = maestriaOptionsSchema.parse(options ?? {});\n const disabledKeywords = new Set<string>(\n (parsed.modes?.disabledKeywords ?? []).map((k) => k.toLowerCase()),\n );\n const agents = loadAgents();\n\n return {\n config: async (input) => {\n input.agent = {\n ...input.agent,\n ...agents,\n };\n input.instructions = [...(input.instructions ?? []), RULES_PATH];\n },\n 'experimental.session.compacting': async (_input, output) => {\n output.context.push(\n 'Session was compacted. Task tracking is maintained via todowrite. ' +\n 'Active context (files, decisions, blockers) was captured before compaction. ' +\n 'Continue where you left off.',\n );\n },\n 'chat.message': async (hookInput, hookOutput) => {\n // Only fire for the orchestrator agent\n if (hookInput.agent !== 'orchestrator') return;\n\n // Find the first text part with user content\n const textPart = hookOutput.parts.find((p) => p.type === 'text') as\n | { text: string; type: 'text' }\n | undefined;\n if (!textPart) return;\n\n // Detect keyword in the text\n const result = detectMode(textPart.text, disabledKeywords);\n if (!result) return;\n\n // Strip keyword from text and prepend mode marker + prompt inline.\n // We embed everything in the existing text part rather than injecting\n // a second text part into `parts`, because the OpenCode runtime does\n // not handle multiple text parts per message (causes a hang).\n textPart.text = [\n getModeMarker(result.mode),\n '',\n getModePrompt(result.mode),\n '',\n stripKeyword(textPart.text, result),\n ].join('\\n');\n },\n };\n};\n\nexport default MaestriaPlugin;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["join","readFileSync","parseYaml"],"sources":["../src/modes/types.ts","../src/root.ts","../src/modes/prompts.ts","../src/modes/index.ts","../src/index.ts"],"sourcesContent":["/**\n * Types for keyword-triggered workflow modes.\n *\n * @see ADR-OC-003 for full design context.\n */\n\nimport { z } from 'zod';\n\n/**\n * Valid mode keywords.\n *\n * - `\"fein\"` -- Full pipeline (recon -> design -> build -> review)\n * - `\"sonar\"` -- Research only (recon + design, stop before build)\n * - `\"blitz\"` -- Fast implementation (builder direct, skip recon/design/review)\n */\nexport const modeKeywordSchema = z.enum(['fein', 'sonar', 'blitz']);\nexport type ModeKeyword = z.infer<typeof modeKeywordSchema>;\n\n/**\n * Plugin-level options for @maestria/opencode.\n */\nexport const maestriaOptionsSchema = z.object({\n modes: z\n .object({\n disabledKeywords: z.array(modeKeywordSchema).optional(),\n })\n .optional(),\n});\nexport type MaestriaPluginOptions = z.infer<typeof maestriaOptionsSchema>;\n\n/**\n * Result returned when a mode keyword is detected in a message.\n */\nexport interface ModeResult {\n /** The resolved mode keyword (lowercase). */\n mode: ModeKeyword;\n /** The keyword string as matched in the original text. */\n keyword: string;\n /** The character index where the keyword starts in the original text. */\n index: number;\n /** The mode prompt text to inject. */\n prompt: string;\n /** The mode marker string like `[MODE: fein]`. */\n marker: string;\n}\n","import { fileURLToPath } from 'node:url';\nimport { dirname, resolve, join } from 'node:path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nexport const PACKAGE_ROOT = resolve(__dirname, '..');\nexport const AGENTS_DIR = join(PACKAGE_ROOT, 'agents');\nexport const COMMANDS_DIR = join(PACKAGE_ROOT, 'agents', 'commands');\nexport const RULES_PATH = join(PACKAGE_ROOT, 'rules', 'AGENTS.md');\n","import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { COMMANDS_DIR } from '@/root.js';\nimport type { ModeKeyword } from '@/modes/types.js';\n\nconst VALID_KEYWORDS: readonly ModeKeyword[] = ['fein', 'sonar', 'blitz'];\n\nfunction loadModePrompt(name: string): string {\n const content = readFileSync(resolve(COMMANDS_DIR, `${name}.md`), 'utf-8');\n // Find the `## MODE:` heading which marks the start of the actual prompt text.\n // The synced command files start with an HTML comment (`<!-- Auto-generated... -->`),\n // not YAML frontmatter (`---`), so a frontmatter regex would never match.\n const modeIdx = content.indexOf('## MODE:');\n if (modeIdx !== -1) {\n return content.slice(modeIdx).replace(/\\s+$/, '') + '\\n';\n }\n return content.replace(/\\s+$/, '') + '\\n';\n}\n\n/**\n * Mode prompt text for each keyword, lazily loaded on first access.\n * If a prompt file is missing or unreadable, logs a warning and caches\n * an empty string — never throws at module evaluation time.\n *\n * @see ADR-OC-003 (section \"Mode Prompts\")\n */\nexport const MODE_PROMPTS: Record<ModeKeyword, string> = new Proxy(\n {} as Record<ModeKeyword, string>,\n {\n get(target, key, receiver) {\n if (typeof key === 'string' && (VALID_KEYWORDS as readonly string[]).includes(key)) {\n if (!(key in target)) {\n try {\n (target as Record<string, string>)[key] = loadModePrompt(key);\n } catch (e) {\n console.warn(`[maestria] Failed to load mode prompt \"${key}\":`, e);\n (target as Record<string, string>)[key] = '';\n }\n }\n return (target as Record<string, string>)[key as string];\n }\n return Reflect.get(target, key, receiver);\n },\n },\n);\n\n/**\n * Marker strings for each mode keyword, used to signal the active mode.\n * Format: `[MODE: <keyword>]`\n */\nexport const MODE_MARKERS: Record<ModeKeyword, string> = {\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n blitz: '[MODE: blitz]',\n};\n\n/**\n * Array of all valid mode keywords for runtime iteration.\n */\nexport { VALID_KEYWORDS };\n","import { escapeRegExp } from 'es-toolkit';\nimport { MODE_PROMPTS, MODE_MARKERS, VALID_KEYWORDS } from '@/modes/prompts.js';\nimport type { ModeKeyword, ModeResult } from '@/modes/types.js';\n\n/**\n * Priority mapping for mode keyword restrictiveness.\n * Higher number = more restrictive = wins when multiple keywords are present.\n * fein (3): full pipeline with mandatory gates\n * sonar (2): research only, no code\n * blitz (1): fast implementation, skip all gates\n */\nconst MODE_PRIORITY: Record<ModeKeyword, number> = {\n fein: 3,\n sonar: 2,\n blitz: 1,\n};\n\n/**\n * Regex matching fenced code blocks (```) and inline backtick spans (`).\n * Used to exclude keyword matches inside code spans.\n */\n// Note: Unclosed fenced code blocks (``` without closing ```) are not\n// excluded - the regex requires matching fences. This is an accepted\n// false-positive risk (see ADR-OC-003 consequences).\nconst CODE_BLOCK_RE = /```[\\s\\S]*?```|`[^`]*`/g;\n\n/**\n * Find ranges of code blocks and inline code spans in text.\n * Returns [start, end) positions. Keywords inside these ranges\n * are ignored during detection.\n */\nfunction findAllCodeBlockRanges(text: string): Array<[number, number]> {\n const ranges: Array<[number, number]> = [];\n let match: RegExpExecArray | null;\n while ((match = CODE_BLOCK_RE.exec(text)) !== null) {\n ranges.push([match.index, match.index + match[0].length]);\n }\n return ranges;\n}\n\nfunction isInRanges(index: number, ranges: Array<[number, number]>): boolean {\n return ranges.some(([start, end]) => index >= start && index < end);\n}\n\n/**\n * Build a regex pattern for word-boundary matching of the given keyword.\n *\n * The pattern uses `\\b` word boundaries to ensure we match whole words only,\n * and is case-insensitive so `Fein`, `FEIN`, `fein` all match.\n */\nfunction buildKeywordRegex(keyword: string): RegExp {\n return new RegExp(`\\\\b${escapeRegExp(keyword)}\\\\b`, 'gi');\n}\n\n/**\n * Detect a workflow mode keyword in the given text.\n *\n * Detection rules (per ADR-OC-003):\n * - Word-boundary regex matching (`\\bfein\\b`, `\\bsonar\\b`, `\\bblitz\\b`)\n * - Most restrictive match wins (fein > sonar > blitz)\n * - Case-insensitive\n * - Disabled keywords are ignored\n * - Matches inside fenced code blocks (```) and inline backticks (`) are ignored\n *\n * @param text The user message to scan.\n * @param disabled Optional set of disabled mode keywords (lowercase).\n * @returns A `ModeResult` if a keyword was detected, or `null`.\n */\nexport function detectMode(text: string, disabled?: Set<string>): ModeResult | null {\n const codeRanges = findAllCodeBlockRanges(text);\n // Normalize disabled keywords to lowercase for case-insensitive comparison\n const normalizedDisabled = disabled\n ? new Set(Array.from(disabled).map((k) => k.toLowerCase()))\n : undefined;\n let bestMatch: { keyword: string; index: number; mode: ModeKeyword } | null = null;\n\n for (const keyword of VALID_KEYWORDS) {\n if (normalizedDisabled?.has(keyword)) continue;\n\n const regex = buildKeywordRegex(keyword);\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(text)) !== null) {\n if (isInRanges(match.index, codeRanges)) continue;\n // Most-restrictive wins: prefer higher-priority mode over position\n if (bestMatch === null || MODE_PRIORITY[keyword] > MODE_PRIORITY[bestMatch.mode]) {\n bestMatch = {\n keyword: match[0],\n index: match.index,\n mode: keyword,\n };\n }\n }\n }\n\n if (bestMatch === null) return null;\n\n return {\n mode: bestMatch.mode,\n keyword: bestMatch.keyword,\n index: bestMatch.index,\n prompt: MODE_PROMPTS[bestMatch.mode],\n marker: MODE_MARKERS[bestMatch.mode],\n };\n}\n\n/**\n * Remove the matched keyword from the text, cleaning up any trailing colon\n * or whitespace that may follow it.\n *\n * @param text The original message text.\n * @param result The `ModeResult` from `detectMode()`.\n * @returns The text with the keyword stripped.\n */\nexport function stripKeyword(text: string, result: ModeResult): string {\n const before = text.slice(0, result.index);\n const after = text.slice(result.index + result.keyword.length);\n\n // Remove any colon + optional whitespace after the keyword\n // (e.g. \"fein: do this\" -> \"do this\")\n const cleaned = after.replace(/^:\\s*/, '');\n\n // Collapse double spaces and trim both ends (handles keyword at start,\n // end, or middle of text, plus extra whitespace around colon)\n return (before + cleaned).replace(/ {2,}/g, ' ').trim();\n}\n\n/**\n * Get the mode prompt text for a given mode name.\n *\n * @param mode The mode keyword (e.g. \"fein\", \"sonar\", \"blitz\").\n * @returns The prompt string, or empty string if mode is unknown.\n */\nexport function getModePrompt(mode: string): string {\n if (isModeKeyword(mode)) {\n return MODE_PROMPTS[mode];\n }\n return '';\n}\n\n/**\n * Get the mode marker string for a given mode name.\n *\n * @param mode The mode keyword (e.g. \"fein\", \"sonar\", \"blitz\").\n * @returns The marker string (e.g. `[MODE: fein]`), or empty string if unknown.\n */\nexport function getModeMarker(mode: string): string {\n if (isModeKeyword(mode)) {\n return MODE_MARKERS[mode];\n }\n return '';\n}\n\n/**\n * Type guard to check if a string is a valid ModeKeyword.\n */\nfunction isModeKeyword(value: string): value is ModeKeyword {\n return (VALID_KEYWORDS as readonly string[]).includes(value);\n}\n","import type { Plugin } from '@opencode-ai/plugin';\nimport { readFileSync, readdirSync } from 'fs';\nimport { join, basename } from 'path';\nimport { parse as parseYaml } from 'yaml';\nimport { type MaestriaPluginOptions, maestriaOptionsSchema } from '@/modes/types.js';\nimport { detectMode, stripKeyword, getModeMarker, getModePrompt } from '@/modes/index.js';\nimport { AGENTS_DIR, RULES_PATH } from '@/root.js';\n\ninterface AgentFrontmatter {\n description: string;\n mode: string;\n permission: Record<string, unknown>;\n color?: string;\n maxSteps?: number;\n}\n\nfunction parseFrontmatter(yamlStr: string): AgentFrontmatter {\n const result = parseYaml(yamlStr) as Record<string, unknown>;\n return {\n description: (result.description as string) || '',\n mode: (result.mode as string) || 'subagent',\n permission: (result.permission as Record<string, unknown>) || {},\n color: result.color as string | undefined,\n maxSteps: result.maxSteps ? Number(result.maxSteps) : undefined,\n };\n}\n\n/**\n * Read an agent markdown file and split into frontmatter + prompt.\n */\nfunction parseAgentFile(filePath: string): { name: string; config: Record<string, unknown> } {\n const content = readFileSync(filePath, 'utf-8');\n const name = basename(filePath, '.md');\n\n // Split on ---\n const parts = content.split('---');\n if (parts.length < 3) {\n throw new Error(`Invalid agent file: ${filePath} - missing frontmatter`);\n }\n\n const frontmatter = parseFrontmatter(parts[1].trim());\n const prompt = parts.slice(2).join('---').trim();\n\n const config: Record<string, unknown> = {\n description: frontmatter.description,\n mode: frontmatter.mode,\n prompt,\n permission: frontmatter.permission,\n };\n\n if (frontmatter.color) config.color = frontmatter.color;\n if (frontmatter.maxSteps) config.maxSteps = frontmatter.maxSteps;\n\n return { name, config };\n}\n\n/**\n * Load all agent configs from the bundled agents/ directory.\n * Returns partial results if some agent files fail to load.\n */\nfunction loadAgents(): Record<string, Record<string, unknown>> {\n try {\n const files = readdirSync(AGENTS_DIR).filter((f) => f.endsWith('.md'));\n const agents: Record<string, Record<string, unknown>> = {};\n\n for (const file of files) {\n try {\n const { name, config } = parseAgentFile(join(AGENTS_DIR, file));\n agents[name] = config;\n } catch (err) {\n console.warn(`[maestria] Failed to parse agent file \"${file}\":`, err);\n }\n }\n\n return agents;\n } catch (err) {\n console.error(`[maestria] Failed to read agents directory:`, err);\n throw new Error(\n `[maestria] Failed to load agents from \"${AGENTS_DIR}\": ` +\n (err instanceof Error ? err.message : String(err)),\n );\n }\n}\n\nexport const MaestriaPlugin: Plugin = async (_input, options?: MaestriaPluginOptions) => {\n // Validate and parse options with zod\n const parsed = maestriaOptionsSchema.parse(options ?? {});\n const disabledKeywords = new Set<string>(\n (parsed.modes?.disabledKeywords ?? []).map((k) => k.toLowerCase()),\n );\n const agents = loadAgents();\n\n return {\n config: async (input) => {\n input.agent = {\n ...input.agent,\n ...agents,\n };\n input.instructions = [...(input.instructions ?? []), RULES_PATH];\n },\n 'experimental.session.compacting': async (_input, output) => {\n output.context.push(\n 'Session was compacted. Task tracking is maintained via todowrite. ' +\n 'Active context (files, decisions, blockers) was captured before compaction. ' +\n 'Continue where you left off.',\n );\n },\n 'chat.message': async (hookInput, hookOutput) => {\n // Only fire for the orchestrator agent\n if (hookInput.agent !== 'orchestrator') return;\n\n // Find the first text part with user content\n const textPart = hookOutput.parts.find((p) => p.type === 'text') as\n | { text: string; type: 'text' }\n | undefined;\n if (!textPart) return;\n\n // Detect keyword in the text\n const result = detectMode(textPart.text, disabledKeywords);\n if (!result) return;\n\n // Strip keyword from text and prepend mode marker + prompt inline.\n // We embed everything in the existing text part rather than injecting\n // a second text part into `parts`, because the OpenCode runtime does\n // not handle multiple text parts per message (causes a hang).\n textPart.text = [\n getModeMarker(result.mode),\n '',\n getModePrompt(result.mode),\n '',\n stripKeyword(textPart.text, result),\n ].join('\\n');\n },\n };\n};\n\nexport default MaestriaPlugin;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maestria/opencode",
3
- "version": "0.6.10",
3
+ "version": "0.6.12",
4
4
  "description": "OpenCode plugin encoding AI engineering praxis: rules, agents, and workflow discipline.",
5
5
  "keywords": [
6
6
  "agents",
@@ -53,6 +53,7 @@
53
53
  "@opencode-ai/plugin": "^1.18.4"
54
54
  },
55
55
  "engines": {
56
+ "bun": ">=1.0.0",
56
57
  "node": ">=22.12.0"
57
58
  },
58
59
  "opencode": {