@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.
- package/agents/adventurer.md +27 -49
- package/agents/architect.md +40 -51
- package/agents/builder.md +27 -43
- package/agents/diagnose.md +32 -54
- package/agents/orchestrator.md +152 -163
- package/agents/planner.md +46 -54
- package/agents/reviewer.md +78 -93
- package/agents/writer.md +46 -64
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/rules/AGENTS.md +27 -12
package/agents/reviewer.md
CHANGED
|
@@ -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
|
|
54
|
-
- **
|
|
55
|
-
- **
|
|
56
|
-
- **
|
|
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
|
|
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
|
|
73
|
+
### 3. Edge Cases and Defensive Programming
|
|
75
74
|
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
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
|
|
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
|
-
-
|
|
92
|
-
-
|
|
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
|
-
-
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
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
|
|
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
|
|
108
|
+
- Are subagent assumptions explicitly documented in the handoff?
|
|
112
109
|
- Are the assumptions reasonable given codebase conventions, ADRs, and project rules?
|
|
113
|
-
-
|
|
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 -
|
|
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?
|
|
121
|
-
- Format
|
|
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
|
|
126
|
-
2. Do I have any struggles understanding these changes? Will this
|
|
127
|
-
3. Can I observe this working by running it? What command, API
|
|
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
|
-
- **
|
|
132
|
-
- **Max 3 re-reviews**
|
|
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
|
-
|
|
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
|
|
142
|
-
- **Performance lens** - Identify bottlenecks, excessive allocations,
|
|
143
|
-
- **Architecture lens** - Evaluate module boundaries, seam placement, dependency direction,
|
|
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
|
|
150
|
-
2. **Lens exclusivity** -
|
|
151
|
-
3. **Note what you didn't check** - In your output, explicitly state what
|
|
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**
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
- Classify issues
|
|
163
|
-
-
|
|
164
|
-
- If no issues, say so
|
|
165
|
-
-
|
|
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**:
|
|
176
|
-
3. **Issues by severity
|
|
177
|
-
4. **What was verified** (
|
|
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
|
|
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`
|
|
177
|
+
- `naming-analyzer` - identifier review analysis
|
|
187
178
|
|
|
188
|
-
### Load on trigger
|
|
179
|
+
### Load on trigger (skip when irrelevant)
|
|
189
180
|
|
|
190
|
-
- `agent-browser`
|
|
191
|
-
- `baseline-ui`
|
|
192
|
-
- `fixing-accessibility`
|
|
193
|
-
- `fixing-metadata`
|
|
194
|
-
- `fixing-motion-performance`
|
|
195
|
-
- `logging-best-practices`
|
|
196
|
-
- `codebase-design`
|
|
197
|
-
- `review-logging-patterns`
|
|
198
|
-
- `skill-judge`
|
|
199
|
-
- `userinterface-wiki`
|
|
200
|
-
- `web-design-guidelines`
|
|
201
|
-
- `webapp-testing`
|
|
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
|
-
- `
|
|
206
|
-
- `emil-design-eng`
|
|
196
|
+
- `improve` -> `@architect` - upstream codebase audit
|
|
197
|
+
- `emil-design-eng` -> `@architect` - upstream component design
|
|
207
198
|
|
|
208
199
|
### Skip if
|
|
209
200
|
|
|
210
|
-
-
|
|
211
|
-
-
|
|
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
|
|
216
|
-
- The Standard of Code Review
|
|
217
|
-
- What to Look For in a Code Review
|
|
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
|
|
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
|
|
72
|
-
-
|
|
70
|
+
- Match tone of surrounding docs
|
|
71
|
+
- Progressive disclosure: high-level first, details on demand
|
|
73
72
|
|
|
74
|
-
##
|
|
73
|
+
## Document Patterns
|
|
75
74
|
|
|
76
75
|
### README
|
|
77
76
|
|
|
78
|
-
- Purpose
|
|
79
|
-
-
|
|
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
|
-
-
|
|
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
|
|
95
|
-
-
|
|
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
|
|
103
|
-
-
|
|
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
|
-
##
|
|
108
|
-
|
|
109
|
-
### Always load
|
|
95
|
+
## Handoff
|
|
110
96
|
|
|
111
|
-
|
|
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
|
-
|
|
99
|
+
## Iteration Limits & Check
|
|
115
100
|
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
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
|
-
|
|
106
|
+
- **Parallelization:** writer tasks on different docs can run in parallel. Same doc is single-writer.
|
|
130
107
|
|
|
131
|
-
|
|
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
|
-
###
|
|
110
|
+
### Always load
|
|
138
111
|
|
|
139
|
-
-
|
|
140
|
-
-
|
|
112
|
+
- `writing-clearly-and-concisely` - clear prose for all writing
|
|
113
|
+
- `humanizer` - remove AI writing markers
|
|
141
114
|
|
|
142
|
-
|
|
115
|
+
### Load on trigger
|
|
143
116
|
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
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
|
-
|
|
130
|
+
### Defer to specialist
|
|
149
131
|
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
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
|
-
|
|
138
|
+
### Skip if
|
|
155
139
|
|
|
156
|
-
-
|
|
157
|
-
-
|
|
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
|
|
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
|
|
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.
|
|
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": {
|