@maestria/opencode 0.7.3 → 0.7.5
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 +9 -9
- package/agents/architect.md +8 -8
- package/agents/builder.md +20 -8
- package/agents/diagnose.md +11 -12
- package/agents/orchestrator.md +9 -9
- package/agents/planner.md +12 -8
- package/agents/reviewer.md +9 -9
- package/agents/writer.md +11 -10
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/rules/AGENTS.md +2 -1
package/agents/adventurer.md
CHANGED
|
@@ -8,15 +8,6 @@ description: |-
|
|
|
8
8
|
One role per session: exploration only - never implement or design.
|
|
9
9
|
mode: subagent
|
|
10
10
|
permission:
|
|
11
|
-
read: allow
|
|
12
|
-
glob: allow
|
|
13
|
-
grep: allow
|
|
14
|
-
lsp: allow
|
|
15
|
-
webfetch: allow
|
|
16
|
-
websearch: ask
|
|
17
|
-
skill: allow
|
|
18
|
-
todowrite: allow
|
|
19
|
-
edit: deny
|
|
20
11
|
bash:
|
|
21
12
|
"*": ask
|
|
22
13
|
ls*: allow
|
|
@@ -48,6 +39,15 @@ permission:
|
|
|
48
39
|
opensrc*: allow
|
|
49
40
|
agent-browser*: allow
|
|
50
41
|
rtk*: allow
|
|
42
|
+
edit: deny
|
|
43
|
+
glob: allow
|
|
44
|
+
grep: allow
|
|
45
|
+
lsp: allow
|
|
46
|
+
read: allow
|
|
47
|
+
skill: allow
|
|
48
|
+
todowrite: allow
|
|
49
|
+
webfetch: allow
|
|
50
|
+
websearch: ask
|
|
51
51
|
---
|
|
52
52
|
|
|
53
53
|
<!-- Auto-generated from @maestria/core. Do not edit directly.
|
package/agents/architect.md
CHANGED
|
@@ -5,14 +5,6 @@ description: |-
|
|
|
5
5
|
Use for: technology choices, implementation approaches, trade-off analysis.
|
|
6
6
|
mode: subagent
|
|
7
7
|
permission:
|
|
8
|
-
read: allow
|
|
9
|
-
glob: allow
|
|
10
|
-
grep: allow
|
|
11
|
-
lsp: allow
|
|
12
|
-
webfetch: allow
|
|
13
|
-
websearch: ask
|
|
14
|
-
skill: allow
|
|
15
|
-
edit: deny
|
|
16
8
|
bash:
|
|
17
9
|
"*": ask
|
|
18
10
|
ls*: allow
|
|
@@ -39,6 +31,14 @@ permission:
|
|
|
39
31
|
pnpm*: allow
|
|
40
32
|
npm*: allow
|
|
41
33
|
npm view *: allow
|
|
34
|
+
edit: deny
|
|
35
|
+
glob: allow
|
|
36
|
+
grep: allow
|
|
37
|
+
lsp: allow
|
|
38
|
+
read: allow
|
|
39
|
+
skill: allow
|
|
40
|
+
webfetch: allow
|
|
41
|
+
websearch: ask
|
|
42
42
|
---
|
|
43
43
|
|
|
44
44
|
<!-- Auto-generated from @maestria/core. Do not edit directly.
|
package/agents/builder.md
CHANGED
|
@@ -5,14 +5,6 @@ description: |-
|
|
|
5
5
|
Use for: targeted fixes, feature implementation, refactors, adding tests.
|
|
6
6
|
mode: subagent
|
|
7
7
|
permission:
|
|
8
|
-
read: allow
|
|
9
|
-
glob: allow
|
|
10
|
-
grep: allow
|
|
11
|
-
lsp: allow
|
|
12
|
-
edit: allow
|
|
13
|
-
webfetch: allow
|
|
14
|
-
todowrite: allow
|
|
15
|
-
skill: allow
|
|
16
8
|
bash:
|
|
17
9
|
ls*: allow
|
|
18
10
|
cat*: allow
|
|
@@ -43,6 +35,14 @@ permission:
|
|
|
43
35
|
eslint*: allow
|
|
44
36
|
prettier*: allow
|
|
45
37
|
"*": ask
|
|
38
|
+
edit: allow
|
|
39
|
+
glob: allow
|
|
40
|
+
grep: allow
|
|
41
|
+
lsp: allow
|
|
42
|
+
read: allow
|
|
43
|
+
skill: allow
|
|
44
|
+
todowrite: allow
|
|
45
|
+
webfetch: allow
|
|
46
46
|
---
|
|
47
47
|
|
|
48
48
|
<!-- Auto-generated from @maestria/core. Do not edit directly.
|
|
@@ -73,6 +73,18 @@ If the task is not atomic - if it spans multiple unrelated concerns - document t
|
|
|
73
73
|
|
|
74
74
|
Start with the smallest change that satisfies acceptance. Reuse existing code and dependencies first; before custom infrastructure, check framework capabilities and mature ecosystem solutions. Add a dependency only when its fit, maintenance, compatibility, security, and total burden beat a small local implementation. Add layers only when the product requires them.
|
|
75
75
|
|
|
76
|
+
At trust boundaries, validate and normalize inputs once into a stable internal shape; client or convenience checks never replace authoritative security enforcement. Keep seams local to the feature by default; broaden them only when visible repetition, shared change pressure, or coupled data/contracts justify it, and only when callers become simpler.
|
|
77
|
+
|
|
78
|
+
When changing a shared interface, trace every caller and supported usage mode; preserve or deliberately migrate them, then verify through the highest practical consumer. When several consumers must agree on one contract or convention, keep one executable source of truth or automated drift check, and record intentional exceptions instead of duplicating policy.
|
|
79
|
+
|
|
80
|
+
Keep mechanical chores separate from behavior changes, and prefer many small reviewable increments over one large change.
|
|
81
|
+
|
|
82
|
+
Prefer deny by default, keep secrets in the trusted runtime, and fail closed on missing or invalid configuration.
|
|
83
|
+
|
|
84
|
+
When superseding code, mark the old path as do-not-extend, keep it until migration completes, then remove it in an isolated change.
|
|
85
|
+
|
|
86
|
+
Never hand-edit generated outputs; change the single source and regenerate.
|
|
87
|
+
|
|
76
88
|
## Skills
|
|
77
89
|
|
|
78
90
|
Load on trigger: `agent-browser` (UI verification), `tdd` (explicit TDD requests), `pnpm` (package/lockfile changes), `mcp-builder` (MCP servers), `webapp-testing` (browser-level testing), `frontend-design` (UI build tasks), `commit-work` (staging and commit messages). Skip skill loads for mechanical one-line fixes.
|
package/agents/diagnose.md
CHANGED
|
@@ -5,15 +5,6 @@ description: |-
|
|
|
5
5
|
Use for: cryptic errors, regressions, production bugs.
|
|
6
6
|
mode: subagent
|
|
7
7
|
permission:
|
|
8
|
-
read: allow
|
|
9
|
-
glob: allow
|
|
10
|
-
grep: allow
|
|
11
|
-
lsp: allow
|
|
12
|
-
webfetch: allow
|
|
13
|
-
websearch: ask
|
|
14
|
-
skill: allow
|
|
15
|
-
todowrite: allow
|
|
16
|
-
edit: allow
|
|
17
8
|
bash:
|
|
18
9
|
ls*: allow
|
|
19
10
|
cat*: allow
|
|
@@ -38,6 +29,15 @@ permission:
|
|
|
38
29
|
env: allow
|
|
39
30
|
pwd: allow
|
|
40
31
|
"*": ask
|
|
32
|
+
edit: allow
|
|
33
|
+
glob: allow
|
|
34
|
+
grep: allow
|
|
35
|
+
lsp: allow
|
|
36
|
+
read: allow
|
|
37
|
+
skill: allow
|
|
38
|
+
todowrite: allow
|
|
39
|
+
webfetch: allow
|
|
40
|
+
websearch: ask
|
|
41
41
|
---
|
|
42
42
|
|
|
43
43
|
<!-- Auto-generated from @maestria/core. Do not edit directly.
|
|
@@ -100,9 +100,9 @@ Fix the root cause with minimal changes:
|
|
|
100
100
|
|
|
101
101
|
Prevent similar bugs:
|
|
102
102
|
|
|
103
|
-
-
|
|
103
|
+
- Consider regression tests where a durable contract or plausible recurrence justifies them (per Global Rules testing judgment)
|
|
104
104
|
- Consider linting rules to catch the pattern
|
|
105
|
-
-
|
|
105
|
+
- **!!! Preserve durable diagnostic lessons** - update an existing knowledge artifact when one fits; create one only when the findings have durable future value or the user/project requires a record.
|
|
106
106
|
|
|
107
107
|
## Step 6: Verify Fix
|
|
108
108
|
|
|
@@ -115,7 +115,6 @@ Confirm it works:
|
|
|
115
115
|
|
|
116
116
|
## Rules
|
|
117
117
|
|
|
118
|
-
- **!!! Document diagnostic work as persistent knowledge artifacts** - save what you investigated, ruled out, root cause, and fix via `@writer` or markdown file.
|
|
119
118
|
- **!!! Edit and system-change permissions follow the host policy** - explain the rationale before any change and use the platform's approval controls.
|
|
120
119
|
- **!!! Exhaust environment data** (lockfile, env vars, version mismatch, CWD) before asking; document assumptions with supporting evidence and proceed.
|
|
121
120
|
- **Parallelization:** different bugs in parallel; same bug = consolidate.
|
package/agents/orchestrator.md
CHANGED
|
@@ -5,17 +5,16 @@ description: |-
|
|
|
5
5
|
Use for: multi-file features, cross-domain tasks, 3+ step workflows.
|
|
6
6
|
mode: all
|
|
7
7
|
permission:
|
|
8
|
-
read: deny
|
|
9
|
-
glob: deny
|
|
10
|
-
grep: deny
|
|
11
|
-
lsp: deny
|
|
12
|
-
webfetch: deny
|
|
13
|
-
edit: deny
|
|
14
8
|
bash:
|
|
15
9
|
"*": deny
|
|
16
10
|
"* npx --yes skills@latest *": allow
|
|
11
|
+
edit: deny
|
|
12
|
+
glob: deny
|
|
13
|
+
grep: deny
|
|
14
|
+
lsp: deny
|
|
17
15
|
question: allow
|
|
18
|
-
|
|
16
|
+
read: deny
|
|
17
|
+
skill: allow
|
|
19
18
|
task:
|
|
20
19
|
"*": deny
|
|
21
20
|
adventurer: allow
|
|
@@ -25,7 +24,8 @@ permission:
|
|
|
25
24
|
planner: allow
|
|
26
25
|
reviewer: allow
|
|
27
26
|
writer: allow
|
|
28
|
-
|
|
27
|
+
todowrite: allow
|
|
28
|
+
webfetch: deny
|
|
29
29
|
---
|
|
30
30
|
|
|
31
31
|
<!-- Auto-generated from @maestria/core. Do not edit directly.
|
|
@@ -97,7 +97,7 @@ Modes are case-insensitive and per-turn.
|
|
|
97
97
|
|
|
98
98
|
For implementation work, own the delivery path: inspect -> plan -> implement -> validate -> one independent review -> repair material blockers only when required -> targeted validation of repaired scope -> final verification -> commit -> push -> PR.
|
|
99
99
|
|
|
100
|
-
**Routine delivery is autonomous.** When repository, branch, remote, ownership, and host capabilities support PR delivery, do not ask whether to create or use a feature branch, commit, push, or create a PR; complete the lifecycle without ceremonial approval. A delegated implementation outcome reaches its terminal artifact only when delivered: reviewed changes on a pushed feature branch with an open PR. Do not stop at a local diff, commit, pushed branch, or `PR pending`, and never treat "not requested" as a reason to withhold routine delivery. Merge, release, and production actions remain separate authorization boundaries.
|
|
100
|
+
**Routine delivery is autonomous.** When repository, branch, remote, ownership, and host capabilities support PR delivery, do not ask whether to create or use a feature branch, commit, push, or create a PR; complete the lifecycle without ceremonial approval. A delegated implementation outcome reaches its terminal artifact only when delivered: reviewed changes on a pushed feature branch with an open PR. Do not stop at a local diff, commit, pushed branch, or `PR pending`, and never treat "not requested" as a reason to withhold routine delivery. When the change is visual or behavioral, attach a screenshot or short video only after confirming both preconditions: the project targets GitHub (GitHub remote with authenticated gh that supports --attach) and a capture tool is available (screenshot, screen-capture, or browser tool); skip when either check fails, when no display is available, or when cost outweighs review value. Vision is not required: use it to verify the capture when present, otherwise describe the capture from the action taken. Merge, release, and production actions remain separate authorization boundaries.
|
|
101
101
|
|
|
102
102
|
The parent session owns continuation until the selected implementation outcome reaches its terminal artifact. Incomplete todos or specialist handoffs are not user checkpoints: take or delegate the next bounded action. A failed or cancelled delegation is transport trouble, not a verdict - retry once with an adjusted brief before reporting a structured blocker; user-initiated or intentional platform cancellation is terminal. Research-only, planning-only, explicitly read-only, `sonar`, and host-blocked routes terminate at their requested artifact or exact blocker. Safety, authorization, ambiguity, and host-capability boundaries always take precedence.
|
|
103
103
|
|
package/agents/planner.md
CHANGED
|
@@ -5,11 +5,6 @@ description: |-
|
|
|
5
5
|
Use for: complex features requiring multi-phase execution, when the plan needs review before building.
|
|
6
6
|
mode: subagent
|
|
7
7
|
permission:
|
|
8
|
-
read: allow
|
|
9
|
-
glob: allow
|
|
10
|
-
grep: allow
|
|
11
|
-
lsp: allow
|
|
12
|
-
edit: ask
|
|
13
8
|
bash:
|
|
14
9
|
"*": ask
|
|
15
10
|
ls*: allow
|
|
@@ -36,9 +31,14 @@ permission:
|
|
|
36
31
|
mkdir*: allow
|
|
37
32
|
pnpm*: allow
|
|
38
33
|
npm*: allow
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
edit: ask
|
|
35
|
+
glob: allow
|
|
36
|
+
grep: allow
|
|
37
|
+
lsp: allow
|
|
38
|
+
read: allow
|
|
41
39
|
skill: allow
|
|
40
|
+
todowrite: allow
|
|
41
|
+
webfetch: allow
|
|
42
42
|
---
|
|
43
43
|
|
|
44
44
|
<!-- Auto-generated from @maestria/core. Do not edit directly.
|
|
@@ -58,6 +58,8 @@ You create implementation plans.
|
|
|
58
58
|
4. **Verification** - Criteria to confirm phase completion
|
|
59
59
|
5. **Rollback Points** - Safe stopping points between phases
|
|
60
60
|
|
|
61
|
+
Deliver each increment as a runnable slice including its wiring, not as a single layer.
|
|
62
|
+
|
|
61
63
|
## Rules
|
|
62
64
|
|
|
63
65
|
Planning briefs state the outcome, phases, dependencies, acceptance evidence, assumptions, rollback points, and next step.
|
|
@@ -67,7 +69,9 @@ Planning briefs state the outcome, phases, dependencies, acceptance evidence, as
|
|
|
67
69
|
- **!!! Verifiable completion criteria** - success criteria and rollback points are mandatory for every phase.
|
|
68
70
|
- **!!! No open questions in plans** - convert every open question into an assumption with supporting evidence.
|
|
69
71
|
|
|
70
|
-
**Guard rails:** follow existing conventions; don't change architecture unasked
|
|
72
|
+
**Guard rails:** follow existing conventions; don't change architecture unasked; don't add dependencies without approval; don't bundle unrelated cleanup. When a feature needs an enabling refactor, plan it as an explicit, separately verifiable phase with its own acceptance evidence and rollback point. Don't skip verification.
|
|
73
|
+
|
|
74
|
+
For migrations spanning many call sites or modules, name the current and target states, prove the target on a representative slice, and migrate in separately verifiable batches. Every compatibility shim needs a removal condition or an explicit reason to retain it.
|
|
71
75
|
|
|
72
76
|
## Handoff
|
|
73
77
|
|
package/agents/reviewer.md
CHANGED
|
@@ -6,12 +6,6 @@ description: |-
|
|
|
6
6
|
Use for: PR review, pre-commit review, architecture document review.
|
|
7
7
|
mode: subagent
|
|
8
8
|
permission:
|
|
9
|
-
read: allow
|
|
10
|
-
glob: allow
|
|
11
|
-
grep: allow
|
|
12
|
-
lsp: allow
|
|
13
|
-
skill: allow
|
|
14
|
-
edit: deny
|
|
15
9
|
bash:
|
|
16
10
|
"*": ask
|
|
17
11
|
ls*: allow
|
|
@@ -40,6 +34,12 @@ permission:
|
|
|
40
34
|
vp*: allow
|
|
41
35
|
rtk*: allow
|
|
42
36
|
node*: allow
|
|
37
|
+
edit: deny
|
|
38
|
+
glob: allow
|
|
39
|
+
grep: allow
|
|
40
|
+
lsp: allow
|
|
41
|
+
read: allow
|
|
42
|
+
skill: allow
|
|
43
43
|
webfetch: allow
|
|
44
44
|
---
|
|
45
45
|
|
|
@@ -101,9 +101,9 @@ The initial general reviewer must give a verdict for every category. A specializ
|
|
|
101
101
|
|
|
102
102
|
### 7. Test Coverage
|
|
103
103
|
|
|
104
|
-
-
|
|
105
|
-
- Do tests cover edge cases and error paths?
|
|
106
|
-
- Are tests meaningful (
|
|
104
|
+
- Is meaningful regression risk covered proportionate to stakes (per Global Rules testing judgment)?
|
|
105
|
+
- Do tests cover edge cases and error paths where the contract demands it?
|
|
106
|
+
- Are tests meaningful (observable behavior, not implementation details)?
|
|
107
107
|
|
|
108
108
|
### 8. Assumption Validation
|
|
109
109
|
|
package/agents/writer.md
CHANGED
|
@@ -5,14 +5,6 @@ description: |-
|
|
|
5
5
|
Use for: README files, API docs, architecture docs, changelogs, decision records.
|
|
6
6
|
mode: subagent
|
|
7
7
|
permission:
|
|
8
|
-
read: allow
|
|
9
|
-
glob: allow
|
|
10
|
-
grep: allow
|
|
11
|
-
lsp: allow
|
|
12
|
-
edit: allow
|
|
13
|
-
webfetch: allow
|
|
14
|
-
skill: allow
|
|
15
|
-
todowrite: allow
|
|
16
8
|
bash:
|
|
17
9
|
"*": ask
|
|
18
10
|
ls*: allow
|
|
@@ -41,6 +33,14 @@ permission:
|
|
|
41
33
|
npm view *: allow
|
|
42
34
|
vp*: allow
|
|
43
35
|
mkdir*: allow
|
|
36
|
+
edit: allow
|
|
37
|
+
glob: allow
|
|
38
|
+
grep: allow
|
|
39
|
+
lsp: allow
|
|
40
|
+
read: allow
|
|
41
|
+
skill: allow
|
|
42
|
+
todowrite: allow
|
|
43
|
+
webfetch: allow
|
|
44
44
|
---
|
|
45
45
|
|
|
46
46
|
<!-- Auto-generated from @maestria/core. Do not edit directly.
|
|
@@ -66,6 +66,7 @@ You write documentation.
|
|
|
66
66
|
- Follow the project's existing doc style
|
|
67
67
|
- One concept per section
|
|
68
68
|
- Document guard rails and constraints explicitly
|
|
69
|
+
- Verify factual claims against current code/config; for operator-critical instructions, link to the authoritative source and include a runnable check with the expected success or failure signal
|
|
69
70
|
- Don't invent isolation, lifecycle, or enforcement guarantees the adapter does not provide.
|
|
70
71
|
|
|
71
72
|
## Format
|
|
@@ -99,8 +100,8 @@ You write documentation.
|
|
|
99
100
|
|
|
100
101
|
## Check
|
|
101
102
|
|
|
102
|
-
- **Termination condition:** links
|
|
103
|
-
- **!!! Mandatory Proofread** - verify
|
|
103
|
+
- **Termination condition:** factual claims match current code/config; links work; examples and operator checks run with the expected signals; tone matches surrounding docs.
|
|
104
|
+
- **!!! Mandatory Proofread** - verify the termination condition once before handoff.
|
|
104
105
|
- **!!! Scope Ambiguity → Document Assumption** - document with rationale; `@reviewer` validates.
|
|
105
106
|
|
|
106
107
|
- **Parallelization:** writer tasks on different docs can run in parallel. Same doc is single-writer.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import{merge as e}from"es-toolkit";import{readFileSync as t,readdirSync as n}from"fs";import
|
|
2
|
-
|
|
3
|
-
`}function x(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function S(e){return RegExp(`\\b${x(e)}\\b`,`gi`)}function C(e){return m.includes(e)}function w(e){return C(e)?h[e]:``}function T(e,t){if(!e)return null;let n=v(e),r=t?new Set(Array.from(t).map(e=>e.toLowerCase())):void 0,i=null;for(let t of m){if(r?.has(t))continue;let a=S(t),o;for(;(o=a.exec(e))!==null;)y(o.index,n)||(i===null||g[t]>g[i.mode])&&(i={mode:t,keyword:o[0],index:o.index})}return i}function E(e,t){return(e.slice(0,t.index)+e.slice(t.index+t.keyword.length).replace(/^:\s*/,``)).replace(/ {2,}/g,` `).trim()}const D=c(d(import.meta.url)),O=u(D,`..`),k=l(O,`agents`),A=l(O,`agents`,`commands`),j=l(O,`rules`,`AGENTS.md`),M=[`fein`,`sonar`,`blitz`];function N(e){return b(s(u(A,`${e}.md`),`utf-8`))}const P=new Proxy({},{get(e,t,n){if(typeof t==`string`&&M.includes(t)){if(!(t in e))try{e[t]=N(t)}catch(n){console.warn(`[maestria] Failed to load mode prompt "${t}":`,n),e[t]=``}return e[t]}return Reflect.get(e,t,n)}}),F={fein:`[MODE: fein]`,sonar:`[MODE: sonar]`,blitz:`[MODE: blitz]`};function I(e,t){let n=T(e,t);return n===null?null:{mode:n.mode,keyword:n.keyword,index:n.index,prompt:P[n.mode],marker:F[n.mode]}}function L(e,t){return E(e,t)}function R(e){return B(e)?P[e]:``}function z(e){return B(e)?w(e):``}function B(e){return M.includes(e)}function V(e){let t=a(e);return{description:t.description||``,mode:t.mode||`subagent`,permission:t.permission||{},color:t.color,maxSteps:t.maxSteps?Number(t.maxSteps):void 0}}function H(e){let n=t(e,`utf-8`),i=r(e,`.md`),a=n.split(`---`);if(a.length<3)throw Error(`Invalid agent file: ${e} - missing frontmatter`);let o=V(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 U(){try{let e=n(k).filter(e=>e.endsWith(`.md`)),t={};for(let n of e)try{let{name:e,config:r}=H(i(k,n));t[e]=r}catch(e){console.warn(`[maestria] Failed to parse agent file "${n}":`,e)}return t}catch(e){throw console.error(`[maestria] Failed to read agents directory:`,e),Error(`[maestria] Failed to load agents from "${k}": `+(e instanceof Error?e.message:String(e)))}}const W=async(t,n)=>{let r=p.parse(n??{}),i=new Set((r.modes?.disabledKeywords??[]).map(e=>e.toLowerCase())),a=U();return{config:async t=>{t.agent=e(t.agent??{},a),t.instructions=[...t.instructions??[],j]},"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 r=I(n.text,i);r&&(n.text=[z(r.mode),``,R(r.mode),``,L(n.text,r)].join(`
|
|
4
|
-
`))}}};export{W as MaestriaPlugin,W as default};
|
|
1
|
+
import{merge as e}from"es-toolkit";import{readFileSync as t,readdirSync as n}from"node:fs";import r from"node:path";import{parse as i}from"yaml";import{z as a}from"zod";const o=[`fein`,`sonar`,`blitz`],s={blitz:`[MODE: blitz]`,fein:`[MODE: fein]`,sonar:`[MODE: sonar]`},c={blitz:1,fein:3,sonar:2},l=/```[\s\S]*?```|`[^`]*`/gu,u=e=>{let t=[],n;for(l.lastIndex=0;(n=l.exec(e))!==null;)t.push([n.index,n.index+n[0].length]);return t},d=(e,t)=>t.some(([t,n])=>e>=t&&e<n),f=e=>{let t=e.indexOf(`## MODE:`);return t===-1?`${e.replace(/\s+$/u,``)}\n`:`${e.slice(t).replace(/\s+$/u,``)}\n`},p=e=>e.replaceAll(/[.*+?^${}()|[\]\\]/gu,`\\$&`),m=e=>RegExp(`\\b${p(e)}\\b`,`giu`),h=e=>o.includes(e),g=e=>h(e)?s[e]:``,_=(e,t)=>{if(!e)return null;let n=u(e),r=t?new Set([...t].map(e=>e.toLowerCase())):void 0,i=null;for(let t of o){if(r!=null&&r.has(t))continue;let a=m(t),o;for(;(o=a.exec(e))!==null;)d(o.index,n)||(i===null||c[t]>c[i.mode])&&(i={index:o.index,keyword:o[0],mode:t})}return i},v=(e,t)=>(e.slice(0,t.index)+e.slice(t.index+t.keyword.length).replace(/^:\s*/u,``)).replaceAll(/ {2,}/gu,` `).trim(),y=import.meta.dirname,b=r.resolve(y,`..`),x=r.join(b,`agents`),S=r.join(b,`agents`,`commands`),C=r.join(b,`rules`,`AGENTS.md`),w=[`fein`,`sonar`,`blitz`],T=e=>{let n=t(r.resolve(S,`${e}.md`),`utf-8`);return f(n)},E=e=>w.some(t=>t===e),D=new Proxy({},{get(e,t,n){if(typeof t==`string`&&E(t)){if(!(t in e))try{e[t]=T(t)}catch(n){console.warn(`[maestria] Failed to load mode prompt "${t}":`,n),e[t]=``}return e[t]}return Reflect.get(e,t,n)}}),O={blitz:`[MODE: blitz]`,fein:`[MODE: fein]`,sonar:`[MODE: sonar]`},k=e=>w.includes(e),A=(e,t)=>{let n=_(e,t);return n===null?null:{index:n.index,keyword:n.keyword,marker:O[n.mode],mode:n.mode,prompt:D[n.mode]}},j=(e,t)=>v(e,t),M=e=>k(e)?D[e]:``,N=e=>k(e)?g(e):``,P=a.enum([`fein`,`sonar`,`blitz`]),F=a.object({modes:a.object({disabledKeywords:a.array(P).optional()}).optional()}),I=e=>typeof e==`object`&&!!e&&!Array.isArray(e),L=e=>e===`all`||e===`primary`||e===`subagent`,R=e=>{if(!I(e))return{};let t={};for(let[n,r]of Object.entries(e))t[n]=r;return t},z=e=>{let t=i(e),n=I(t)?t:{};return{color:typeof n.color==`string`?n.color:void 0,description:typeof n.description==`string`?n.description:``,maxSteps:n.maxSteps!==void 0&&n.maxSteps!==null&&n.maxSteps!==``?Number(n.maxSteps):void 0,mode:L(n.mode)?n.mode:`subagent`,permission:R(n.permission)}},B=e=>{let n=t(e,`utf-8`),i=r.basename(e,`.md`),a=n.split(`---`);if(a.length<3)throw Error(`Invalid agent file: ${e} - missing frontmatter`);let o=z(a[1].trim()),s=a.slice(2).join(`---`).trim(),c={description:o.description,mode:o.mode,permission:o.permission,prompt:s};return o.color!==void 0&&o.color!==null&&o.color!==``&&(c.color=o.color),o.maxSteps!==void 0&&o.maxSteps!==null&&o.maxSteps!==0&&(c.maxSteps=o.maxSteps),{config:c,name:i}},V=()=>{try{let e=n(x).filter(e=>e.endsWith(`.md`)),t={};for(let n of e)try{let{name:e,config:i}=B(r.join(x,n));t[e]=i}catch(e){console.warn(`[maestria] Failed to parse agent file "${n}":`,e)}return t}catch(e){throw console.error(`[maestria] Failed to read agents directory:`,e),Error(`[maestria] Failed to load agents from "${x}": ${e instanceof Error?e.message:String(e)}`,{cause:e})}},H=(e,t,n)=>{if(e.agent!==`orchestrator`)return;let r=t.parts.find(e=>e.type===`text`);if(r===void 0)return;let i=A(r.text,n);i!==null&&(r.text=[N(i.mode),``,M(i.mode),``,j(r.text,i)].join(`
|
|
2
|
+
`))},U=(t,n)=>{t.agent=e(t.agent??{},n),t.instructions=[...t.instructions??[],C]},W=e=>{e.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.`)},G=async(e,t)=>{let n=F.parse(t??{}),r=new Set((n.modes?.disabledKeywords??[]).map(e=>e.toLowerCase())),i=V();return await Promise.resolve(),{"chat.message":async(e,t)=>{H(e,t,r),await Promise.resolve()},config:async e=>{U(e,i),await Promise.resolve()},"experimental.session.compacting":async(e,t)=>{W(t),await Promise.resolve()}}};export{G as MaestriaPlugin,G as default};
|
|
5
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["MODE_MARKERS","isModeKeyword","getModeMarker","detectMode","stripKeyword","join","readFileSync","sharedDetectMode","sharedStripKeyword","sharedGetMarker","parseYaml"],"sources":["../src/modes/types.ts","../../shared/mode/src/index.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 optional recon/design; required review remains)\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","/**\n * Private runtime-neutral shared mode mechanics.\n *\n * Pure TypeScript - no host SDKs, no filesystem APIs.\n * Consolidates the duplicated pure logic from:\n * - `packages/opencode/src/modes/index.ts`\n * - `packages/shared/pi/src/modes-core.ts`\n *\n * Consumers (OpenCode, shared-pi) delegate pure detection,\n * stripping, and mode-section extraction to this module while\n * keeping host-specific concerns (lazy file loading, session\n * state, command registration) locally.\n *\n * @module @maestria/shared-mode\n */\n\n// ── Constants ──\n\nexport const MODE_KEYWORDS = ['fein', 'sonar', 'blitz'] as const;\nexport type ModeKeyword = (typeof MODE_KEYWORDS)[number];\n\n/**\n * Backward-compatible alias used by OpenCode (`VALID_KEYWORDS`).\n */\nexport const VALID_KEYWORDS: readonly ModeKeyword[] = MODE_KEYWORDS;\n\nexport const MODE_MARKERS: Record<ModeKeyword, string> = {\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n blitz: '[MODE: blitz]',\n};\n\n/**\n * Priority mapping for mode keyword restrictiveness.\n * Higher number = more restrictive = wins when multiple keywords are present.\n * fein (3): full pipeline with mandatory gates\n * sonar (2): research only, no code\n * blitz (1): fast implementation, skip optional ceremony; required review remains\n */\nexport const MODE_PRIORITY: Record<ModeKeyword, number> = {\n fein: 3,\n sonar: 2,\n blitz: 1,\n};\n\n// ── Types ──\n\n/**\n * Result of pure mode detection (no prompt/marker).\n * Host wrappers add prompt/marker and stripped text as needed.\n */\nexport interface ModeDetectPure {\n mode: ModeKeyword;\n keyword: string;\n index: number;\n}\n\n// ── Pure helpers ──\n\n/**\n * Regex matching fenced code blocks (```) and inline backtick spans (`).\n * Unclosed fenced blocks (``` without closing ```) are intentionally not\n * excluded - the regex requires matching fences. This is the accepted\n * false-positive behavior documented in ADR-OC-003.\n */\nexport const CODE_BLOCK_RE: RegExp = /```[\\s\\S]*?```|`[^`]*`/g;\n\nexport function findCodeBlockRanges(text: string): Array<[number, number]> {\n const ranges: Array<[number, number]> = [];\n let match: RegExpExecArray | null;\n // Reset lastIndex in case callers reuse the exported regex\n CODE_BLOCK_RE.lastIndex = 0;\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\nexport function isInRanges(index: number, ranges: Array<[number, number]>): boolean {\n return ranges.some(([start, end]) => index >= start && index < end);\n}\n\n/**\n * Extract the `## MODE:` section from a command file's content.\n * Mirrors the logic in both original modules: find `## MODE:`, slice\n * from there, trim trailing whitespace, append a single newline; if\n * no marker is found, normalize trailing whitespace the same way.\n */\nexport function extractModeSection(content: string): string {\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\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction buildKeywordRegex(keyword: string): RegExp {\n return new RegExp(`\\\\b${escapeRegExp(keyword)}\\\\b`, 'gi');\n}\n\nfunction isModeKeyword(value: string): value is ModeKeyword {\n return (MODE_KEYWORDS as readonly string[]).includes(value);\n}\n\nexport function getModeMarker(mode: string): string {\n if (isModeKeyword(mode)) return MODE_MARKERS[mode];\n return '';\n}\n\n// ── Detection ──\n\n/**\n * Detect a workflow mode keyword in the given text.\n *\n * Rules (per ADR-OC-003):\n * - Word-boundary regex matching (`\\bfein\\b`, etc.), case-insensitive\n * - Most restrictive match wins (fein > sonar > blitz) regardless of position\n * - Disabled keywords (case-insensitive) are ignored\n * - Matches inside fenced code blocks (```) and inline backticks (`) are ignored\n * - Unclosed fences are not excluded (accepted false-positive)\n *\n * Returns the pure detection result (mode, matched keyword text, index) or null.\n */\nexport function detectMode(text: string, disabled?: Set<string>): ModeDetectPure | null {\n if (!text) return null;\n const codeRanges = findCodeBlockRanges(text);\n const normalizedDisabled = disabled\n ? new Set(Array.from(disabled).map((k) => k.toLowerCase()))\n : undefined;\n let best: ModeDetectPure | null = null;\n\n for (const keyword of MODE_KEYWORDS) {\n if (normalizedDisabled?.has(keyword)) continue;\n const regex = buildKeywordRegex(keyword);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n if (isInRanges(match.index, codeRanges)) continue;\n if (best === null || MODE_PRIORITY[keyword] > MODE_PRIORITY[best.mode]) {\n best = { mode: keyword, keyword: match[0], index: match.index };\n }\n }\n }\n\n return best;\n}\n\n/**\n * Remove the matched keyword from the text, cleaning up a trailing colon\n * and collapsing double spaces.\n *\n * Mirrors both originals: strip a leading colon and surrounding whitespace\n * after the keyword, then collapse double spaces and trim.\n */\nexport function stripKeyword(text: string, result: { index: number; keyword: string }): string {\n const before = text.slice(0, result.index);\n const after = text.slice(result.index + result.keyword.length);\n const cleaned = after.replace(/^:\\s*/, '');\n return (before + cleaned).replace(/ {2,}/g, ' ').trim();\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 { extractModeSection } from '@maestria/shared-mode';\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 return extractModeSection(content);\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 {\n detectMode as sharedDetectMode,\n stripKeyword as sharedStripKeyword,\n getModeMarker as sharedGetMarker,\n} from '@maestria/shared-mode';\nimport { MODE_PROMPTS, MODE_MARKERS, VALID_KEYWORDS } from '@/modes/prompts.js';\nimport type { ModeKeyword, ModeResult } from '@/modes/types.js';\n\n/**\n * Detect a workflow mode keyword in the given text.\n *\n * Delegates pure detection (word-boundary, priority, code-block\n * exclusion, disabled-keyword handling, case-insensitivity) to\n * `@maestria/shared-mode` and augments with prompt/marker so the\n * existing public API and result shape are preserved.\n *\n * Behavior (per ADR-OC-003) is unchanged: most restrictive wins\n * (fein > sonar > blitz), code spans are excluded, unclosed fences\n * are not excluded (accepted false-positive).\n */\nexport function detectMode(text: string, disabled?: Set<string>): ModeResult | null {\n const pure = sharedDetectMode(text, disabled);\n if (pure === null) return null;\n return {\n mode: pure.mode,\n keyword: pure.keyword,\n index: pure.index,\n prompt: MODE_PROMPTS[pure.mode],\n marker: MODE_MARKERS[pure.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 */\nexport function stripKeyword(text: string, result: ModeResult): string {\n return sharedStripKeyword(text, result);\n}\n\n/**\n * Get the mode prompt text for a given mode name.\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 */\nexport function getModeMarker(mode: string): string {\n if (isModeKeyword(mode)) {\n return sharedGetMarker(mode);\n }\n return '';\n}\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 { merge } from 'es-toolkit';\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 // Deep-merge plugin agent defaults over the user's agent entries. A\n // shallow `{ ...input.agent, ...agents }` would replace each entry\n // wholesale, dropping user-set keys (model, variant, temperature) for\n // the 8 maestria agent names. Plugin defaults win on conflict; user\n // keys the plugin does not set survive.\n input.agent = merge(input.agent ?? {}, agents);\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":["MODE_MARKERS","isModeKeyword","getModeMarker","detectMode","stripKeyword","isModeKeyword","sharedDetectMode","sharedStripKeyword","sharedGetMarker","parseYaml"],"sources":["../../shared/mode/src/index.ts","../src/root.ts","../src/modes/prompts.ts","../src/modes/index.ts","../src/modes/types.ts","../src/index.ts"],"sourcesContent":["/**\n * Private runtime-neutral shared mode mechanics.\n *\n * Pure TypeScript - no host SDKs, no filesystem APIs.\n * Consolidates the duplicated pure logic from:\n * - `packages/opencode/src/modes/index.ts`\n * - `packages/shared/pi/src/modes-core.ts`\n *\n * Consumers (OpenCode, shared-pi) delegate pure detection,\n * stripping, and mode-section extraction to this module while\n * keeping host-specific concerns (lazy file loading, session\n * state, command registration) locally.\n *\n * @module @maestria/shared-mode\n */\n\n// ── Constants ──\n\nexport const MODE_KEYWORDS = ['fein', 'sonar', 'blitz'] as const;\nexport type ModeKeyword = (typeof MODE_KEYWORDS)[number];\n\n/**\n * Backward-compatible alias used by OpenCode (`VALID_KEYWORDS`).\n */\nexport const VALID_KEYWORDS: readonly ModeKeyword[] = MODE_KEYWORDS;\n\nexport const MODE_MARKERS: Record<ModeKeyword, string> = {\n blitz: '[MODE: blitz]',\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n};\n\n/**\n * Priority mapping for mode keyword restrictiveness.\n * Higher number = more restrictive = wins when multiple keywords are present.\n * fein (3): full pipeline with mandatory gates\n * sonar (2): research only, no code\n * blitz (1): fast implementation, skip optional ceremony; required review remains\n */\nexport const MODE_PRIORITY: Record<ModeKeyword, number> = {\n blitz: 1,\n fein: 3,\n sonar: 2,\n};\n\n// ── Types ──\n\n/**\n * Result of pure mode detection (no prompt/marker).\n * Host wrappers add prompt/marker and stripped text as needed.\n */\nexport interface ModeDetectPure {\n mode: ModeKeyword;\n keyword: string;\n index: number;\n}\n\n// ── Pure helpers ──\n\n/**\n * Regex matching fenced code blocks (```) and inline backtick spans (`).\n * Unclosed fenced blocks (``` without closing ```) are intentionally not\n * excluded - the regex requires matching fences. This is the accepted\n * false-positive behavior documented in ADR-OC-003.\n */\nexport const CODE_BLOCK_RE = /```[\\s\\S]*?```|`[^`]*`/gu;\n\nexport const findCodeBlockRanges = (text: string): [number, number][] => {\n const ranges: [number, number][] = [];\n let match: RegExpExecArray | null;\n // Reset lastIndex in case callers reuse the exported regex\n CODE_BLOCK_RE.lastIndex = 0;\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\nexport const isInRanges = (index: number, ranges: [number, number][]): boolean =>\n ranges.some(([start, end]) => index >= start && index < end);\n\n/**\n * Extract the `## MODE:` section from a command file's content.\n * Mirrors the logic in both original modules: find `## MODE:`, slice\n * from there, trim trailing whitespace, append a single newline; if\n * no marker is found, normalize trailing whitespace the same way.\n */\nexport const extractModeSection = (content: string): string => {\n const modeIdx = content.indexOf('## MODE:');\n if (modeIdx !== -1) {\n return `${content.slice(modeIdx).replace(/\\s+$/u, '')}\\n`;\n }\n return `${content.replace(/\\s+$/u, '')}\\n`;\n};\n\nconst escapeRegExp = (value: string): string => value.replaceAll(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n\nconst buildKeywordRegex = (keyword: string): RegExp =>\n new RegExp(`\\\\b${escapeRegExp(keyword)}\\\\b`, 'giu');\n\nconst isModeKeyword = (value: string): value is ModeKeyword =>\n (MODE_KEYWORDS as readonly string[]).includes(value);\n\nexport const getModeMarker = (mode: string): string => {\n if (isModeKeyword(mode)) {\n return MODE_MARKERS[mode];\n }\n return '';\n};\n\n// ── Detection ──\n\n/**\n * Detect a workflow mode keyword in the given text.\n *\n * Rules (per ADR-OC-003):\n * - Word-boundary regex matching (`\\bfein\\b`, etc.), case-insensitive\n * - Most restrictive match wins (fein > sonar > blitz) regardless of position\n * - Disabled keywords (case-insensitive) are ignored\n * - Matches inside fenced code blocks (```) and inline backticks (`) are ignored\n * - Unclosed fences are not excluded (accepted false-positive)\n *\n * Returns the pure detection result (mode, matched keyword text, index) or null.\n */\nexport const detectMode = (text: string, disabled?: Set<string>): ModeDetectPure | null => {\n if (!text) {\n return null;\n }\n const codeRanges = findCodeBlockRanges(text);\n const normalizedDisabled = disabled\n ? new Set([...disabled].map((k) => k.toLowerCase()))\n : undefined;\n let best: ModeDetectPure | null = null;\n\n for (const keyword of MODE_KEYWORDS) {\n if (\n normalizedDisabled !== undefined &&\n normalizedDisabled !== null &&\n normalizedDisabled.has(keyword)\n ) {\n continue;\n }\n const regex = buildKeywordRegex(keyword);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n if (isInRanges(match.index, codeRanges)) {\n continue;\n }\n if (best === null || MODE_PRIORITY[keyword] > MODE_PRIORITY[best.mode]) {\n best = { index: match.index, keyword: match[0], mode: keyword };\n }\n }\n }\n\n return best;\n};\n\n/**\n * Remove the matched keyword from the text, cleaning up a trailing colon\n * and collapsing double spaces.\n *\n * Mirrors both originals: strip a leading colon and surrounding whitespace\n * after the keyword, then collapse double spaces and trim.\n */\nexport const stripKeyword = (text: string, result: { index: number; keyword: string }): string => {\n const before = text.slice(0, result.index);\n const after = text.slice(result.index + result.keyword.length);\n const cleaned = after.replace(/^:\\s*/u, '');\n return (before + cleaned).replaceAll(/ {2,}/gu, ' ').trim();\n};\n","import path from 'node:path';\n\nconst __dirname = import.meta.dirname;\nexport const PACKAGE_ROOT = path.resolve(__dirname, '..');\nexport const AGENTS_DIR = path.join(PACKAGE_ROOT, 'agents');\nexport const COMMANDS_DIR = path.join(PACKAGE_ROOT, 'agents', 'commands');\nexport const RULES_PATH = path.join(PACKAGE_ROOT, 'rules', 'AGENTS.md');\n","import { extractModeSection } from '@maestria/shared-mode';\nimport { readFileSync } from 'node:fs';\nimport path from 'node:path';\n\nimport type { ModeKeyword } from '@/modes/types.js';\nimport { COMMANDS_DIR } from '@/root.js';\n\nconst VALID_KEYWORDS: readonly ModeKeyword[] = ['fein', 'sonar', 'blitz'];\n\nconst loadModePrompt = (name: string): string => {\n const content = readFileSync(path.resolve(COMMANDS_DIR, `${name}.md`), 'utf-8');\n return extractModeSection(content);\n};\n\nconst isModeKeyword = (value: string): value is ModeKeyword =>\n VALID_KEYWORDS.some((keyword) => keyword === value);\n\nconst modePromptTarget: Record<string, string> = {};\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(modePromptTarget, {\n get(target, key, receiver) {\n if (typeof key === 'string' && isModeKeyword(key)) {\n if (!(key in target)) {\n try {\n target[key] = loadModePrompt(key);\n } catch (error) {\n console.warn(`[maestria] Failed to load mode prompt \"${key}\":`, error);\n target[key] = '';\n }\n }\n return target[key];\n }\n return Reflect.get(target, key, receiver) as unknown;\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 blitz: '[MODE: blitz]',\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n};\n\n/**\n * Array of all valid mode keywords for runtime iteration.\n */\nexport { VALID_KEYWORDS };\n","import {\n detectMode as sharedDetectMode,\n getModeMarker as sharedGetMarker,\n stripKeyword as sharedStripKeyword,\n} from '@maestria/shared-mode';\n\nimport { MODE_MARKERS, MODE_PROMPTS, VALID_KEYWORDS } from '@/modes/prompts.js';\nimport type { ModeKeyword, ModeResult } from '@/modes/types.js';\n\n/**\n * Detect a workflow mode keyword in the given text.\n *\n * Delegates pure detection (word-boundary, priority, code-block\n * exclusion, disabled-keyword handling, case-insensitivity) to\n * `@maestria/shared-mode` and augments with prompt/marker so the\n * existing public API and result shape are preserved.\n *\n * Behavior (per ADR-OC-003) is unchanged: most restrictive wins\n * (fein > sonar > blitz), code spans are excluded, unclosed fences\n * are not excluded (accepted false-positive).\n */\nconst isModeKeyword = (value: string): value is ModeKeyword =>\n (VALID_KEYWORDS as readonly string[]).includes(value);\n\nexport const detectMode = (text: string, disabled?: Set<string>): ModeResult | null => {\n const pure = sharedDetectMode(text, disabled);\n if (pure === null) {\n return null;\n }\n return {\n index: pure.index,\n keyword: pure.keyword,\n marker: MODE_MARKERS[pure.mode],\n mode: pure.mode,\n prompt: MODE_PROMPTS[pure.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 */\nexport const stripKeyword = (text: string, result: ModeResult): string =>\n sharedStripKeyword(text, result);\n\n/**\n * Get the mode prompt text for a given mode name.\n */\nexport const 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 */\nexport const getModeMarker = (mode: string): string => {\n if (isModeKeyword(mode)) {\n return sharedGetMarker(mode);\n }\n return '';\n};\n","/**\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 optional recon/design; required review remains)\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 type { Config, Hooks, Plugin, PluginInput } from '@opencode-ai/plugin';\nimport { merge } from 'es-toolkit';\nimport { readdirSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\n\nimport { detectMode, getModeMarker, getModePrompt, stripKeyword } from '@/modes/index.js';\nimport { maestriaOptionsSchema } from '@/modes/types.js';\nimport type { MaestriaPluginOptions } from '@/modes/types.js';\nimport { AGENTS_DIR, RULES_PATH } from '@/root.js';\n\ntype OpenCodeAgentConfig = NonNullable<NonNullable<Config['agent']>[string]>;\ntype AgentMode = NonNullable<OpenCodeAgentConfig['mode']>;\ntype AgentPermission = NonNullable<OpenCodeAgentConfig['permission']> & Record<string, unknown>;\ntype AgentConfig = Omit<OpenCodeAgentConfig, 'mode' | 'permission'> & {\n mode: AgentMode;\n permission: AgentPermission;\n};\ntype ChatMessageHook = NonNullable<Hooks['chat.message']>;\ntype ChatMessageInput = Parameters<ChatMessageHook>[0];\ntype ChatMessageOutput = Parameters<ChatMessageHook>[1];\ntype TextPart = Extract<ChatMessageOutput['parts'][number], { type: 'text' }>;\ntype ConfigInput = Parameters<NonNullable<Hooks['config']>>[0];\ntype CompactingOutput = Parameters<NonNullable<Hooks['experimental.session.compacting']>>[1];\n\ninterface AgentFrontmatter {\n description: string;\n mode: AgentMode;\n permission: AgentPermission;\n color?: string;\n maxSteps?: number;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isAgentMode = (value: unknown): value is AgentMode =>\n value === 'all' || value === 'primary' || value === 'subagent';\n\nconst parsePermission = (value: unknown): AgentPermission => {\n if (!isRecord(value)) {\n return {};\n }\n\n const permission: AgentPermission = {};\n for (const [key, permissionValue] of Object.entries(value)) {\n permission[key] = permissionValue;\n }\n return permission;\n};\n\nconst parseFrontmatter = (yamlStr: string): AgentFrontmatter => {\n const parsed = parseYaml(yamlStr) as unknown;\n const result = isRecord(parsed) ? parsed : {};\n\n return {\n color: typeof result.color === 'string' ? result.color : undefined,\n description: typeof result.description === 'string' ? result.description : '',\n maxSteps:\n result.maxSteps !== undefined && result.maxSteps !== null && result.maxSteps !== ''\n ? Number(result.maxSteps)\n : undefined,\n mode: isAgentMode(result.mode) ? result.mode : 'subagent',\n permission: parsePermission(result.permission),\n };\n};\n\n/**\n * Read an agent markdown file and split into frontmatter + prompt.\n */\nconst parseAgentFile = (filePath: string): { name: string; config: AgentConfig } => {\n const content = readFileSync(filePath, 'utf-8');\n const name = path.basename(filePath, '.md');\n\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: AgentConfig = {\n description: frontmatter.description,\n mode: frontmatter.mode,\n permission: frontmatter.permission,\n prompt,\n };\n\n if (frontmatter.color !== undefined && frontmatter.color !== null && frontmatter.color !== '') {\n config.color = frontmatter.color;\n }\n if (\n frontmatter.maxSteps !== undefined &&\n frontmatter.maxSteps !== null &&\n frontmatter.maxSteps !== 0\n ) {\n config.maxSteps = frontmatter.maxSteps;\n }\n\n return { config, name };\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 */\nconst loadAgents = (): NonNullable<Config['agent']> => {\n try {\n const files = readdirSync(AGENTS_DIR).filter((file) => file.endsWith('.md'));\n const agents: NonNullable<Config['agent']> = {};\n\n for (const file of files) {\n try {\n const { name, config } = parseAgentFile(path.join(AGENTS_DIR, file));\n agents[name] = config;\n } catch (error) {\n console.warn(`[maestria] Failed to parse agent file \"${file}\":`, error);\n }\n }\n\n return agents;\n } catch (error) {\n console.error('[maestria] Failed to read agents directory:', error);\n throw new Error(\n `[maestria] Failed to load agents from \"${AGENTS_DIR}\": ${\n error instanceof Error ? error.message : String(error)\n }`,\n { cause: error },\n );\n }\n};\n\nconst applyModeToMessage = (\n hookInput: ChatMessageInput,\n hookOutput: ChatMessageOutput,\n disabledKeywords: Set<string>,\n): void => {\n if (hookInput.agent !== 'orchestrator') {\n return;\n }\n\n const textPart = hookOutput.parts.find((part): part is TextPart => part.type === 'text');\n if (textPart === undefined) {\n return;\n }\n\n const result = detectMode(textPart.text, disabledKeywords);\n if (result === null) {\n return;\n }\n\n textPart.text = [\n getModeMarker(result.mode),\n '',\n getModePrompt(result.mode),\n '',\n stripKeyword(textPart.text, result),\n ].join('\\n');\n};\n\nconst configureAgents = (input: ConfigInput, agents: NonNullable<Config['agent']>): void => {\n input.agent = merge(input.agent ?? {}, agents);\n input.instructions = [...(input.instructions ?? []), RULES_PATH];\n};\n\nconst appendCompactionContext = (output: CompactingOutput): void => {\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\nexport const MaestriaPlugin: Plugin = async (\n _input: PluginInput,\n options?: MaestriaPluginOptions,\n) => {\n const parsed = maestriaOptionsSchema.parse(options ?? {});\n const disabledKeywords = new Set<string>(\n (parsed.modes?.disabledKeywords ?? []).map((keyword) => keyword.toLowerCase()),\n );\n const agents = loadAgents();\n await Promise.resolve();\n\n return {\n 'chat.message': async (hookInput, hookOutput) => {\n applyModeToMessage(hookInput, hookOutput, disabledKeywords);\n await Promise.resolve();\n },\n config: async (input) => {\n configureAgents(input, agents);\n await Promise.resolve();\n },\n 'experimental.session.compacting': async (_compactingInput, output) => {\n appendCompactionContext(output);\n await Promise.resolve();\n },\n };\n};\n\nexport default MaestriaPlugin;\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/rules/AGENTS.md
CHANGED
|
@@ -13,6 +13,7 @@ Cross-platform behavior contract for outcomes, evidence, safety, delegation, rev
|
|
|
13
13
|
- **!!! Match effort to stakes.** Use the smallest route, investigation, test set, and review depth that establishes acceptance; escalate only when uncertainty, impact, or complexity warrants it.
|
|
14
14
|
- **!!! Prefer reuse over reinvention.** Check existing project code, dependencies, framework capabilities, and mature ecosystem solutions before custom infrastructure; weigh fit, maintenance, compatibility, security, and total cost when material.
|
|
15
15
|
- **!!! Exhaust available evidence before asking.** Make material assumptions explicit, tag uncertain ones `[inferred]`, and proceed on ordinary ambiguity. Ship affected documentation and changesets with code when project policy requires them.
|
|
16
|
+
- **!!! Exercise testing judgment, not coverage.** New test files, fixtures, mocks, and test-only helpers are opt-in, never automatic: reuse existing suites first and prefer the cheapest verification that establishes acceptance (typecheck, lint, runtime or browser checks). Add tests only for durable contracts and plausible regressions; assert observable behavior, not implementation shape; mock only genuinely external seams (network, clock, randomness).
|
|
16
17
|
- **!!! Keep output self-contained and professional.** Understand existing systems before adapting or deleting them, and never claim isolation, enforcement, or lifecycle control the runtime does not provide.
|
|
17
18
|
- **!!! Human-facing output.** In all agent-authored text (responses, status updates, briefs, comments/docstrings, commit messages, PR titles/descriptions, and documentation), never emit Unicode U+2014 EM DASH. Prefer commas, colons, parentheses, or ASCII hyphen-minus (`-`). Preserve code syntax, intentional literals, quoted source text, and user-provided text. Scan authored output before handoff or delivery.
|
|
18
19
|
|
|
@@ -44,7 +45,7 @@ Default to one independent review and, only when blockers exist, one repair/re-r
|
|
|
44
45
|
|
|
45
46
|
Safety and authorization override user intent, methodology, and brevity. Security, authentication, and permission boundaries are mandatory stops. Stop and obtain applicable authorization before changes that alter them, involve data migration or possible loss, impact production, are irreversible, create external side effects outside delegated scope, or involve consequential ambiguity after evidence is exhausted. Ordinary in-scope security defects may be repaired autonomously.
|
|
46
47
|
|
|
47
|
-
The orchestrator owns continuation for implementation and delivery work until the outcome reaches its terminal artifact; incomplete todos, pending handoffs, or specialist messages saying "continue if needed" are not a user checkpoint. Routine delivery is autonomous. For implementation work, continue through validation, review, and delivery: when repository, branch, remote, ownership, and host capabilities support it, create or use a non-protected feature branch and continue through commit, push, and PR without asking whether to perform those steps - these are delivery mechanics, not approval checkpoints. Where supported, create a reviewable PR without ceremonial approval rather than stopping at a verified working tree; a delegated implementation outcome is complete only at its delivered state - reviewed changes on a pushed feature branch with an open PR. Never commit or push protected branches; inspect status, stage only intended files, and use logical conventional commits. Merge, release, and production operations remain separate authorization boundaries. Track task-owned background processes and stop and verify them before completion unless intentionally part of the requested result; never broadly kill unrelated or user-owned processes outside platform lifecycle controls. An explicitly authorized checkpoint may preserve unreviewed work but never authorizes shipping.
|
|
48
|
+
The orchestrator owns continuation for implementation and delivery work until the outcome reaches its terminal artifact; incomplete todos, pending handoffs, or specialist messages saying "continue if needed" are not a user checkpoint. Routine delivery is autonomous. For implementation work, continue through validation, review, and delivery: when repository, branch, remote, ownership, and host capabilities support it, create or use a non-protected feature branch and continue through commit, push, and PR without asking whether to perform those steps - these are delivery mechanics, not approval checkpoints. Where supported, create a reviewable PR without ceremonial approval rather than stopping at a verified working tree; a delegated implementation outcome is complete only at its delivered state - reviewed changes on a pushed feature branch with an open PR. Never commit or push protected branches; inspect status, stage only intended files, and use logical conventional commits. Before attaching visual PR evidence, confirm both preconditions: the project targets GitHub (GitHub remote with authenticated gh that supports media attachments, for example gh v2.99.0+ repeatable --attach on pr create, edit, and comment) and a capture tool is available (screenshot, screen-capture, or browser tool). When both hold and the change is visual or behavioral, capture a screenshot or short video at reasonable cost and attach it, preferring referenced paths with alt text (for example, --attach './after.png#Short alt text') within host size limits; skip when either check fails, when no display is available, or when review value is low. Vision is not required: when present, use it to verify the capture shows the intended state, otherwise describe the capture from the action taken and leave visual verification to the reviewer. Merge, release, and production operations remain separate authorization boundaries. Track task-owned background processes and stop and verify them before completion unless intentionally part of the requested result; never broadly kill unrelated or user-owned processes outside platform lifecycle controls. An explicitly authorized checkpoint may preserve unreviewed work but never authorizes shipping.
|
|
48
49
|
|
|
49
50
|
## Canonical Source Invariant
|
|
50
51
|
|