@siddicky/oh-my-musecode 0.1.0
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/.claude-plugin/marketplace.json +18 -0
- package/.claude-plugin/plugin.json +30 -0
- package/.muse-plugin/plugin.json +94 -0
- package/LICENSE +32 -0
- package/README.md +190 -0
- package/dist/mcp/state-server.d.ts +13 -0
- package/dist/mcp/state-server.js +109 -0
- package/dist/mcp/state-server.js.map +1 -0
- package/dist/paths.d.ts +58 -0
- package/dist/paths.js +167 -0
- package/dist/paths.js.map +1 -0
- package/dist/personas.d.ts +40 -0
- package/dist/personas.js +93 -0
- package/dist/personas.js.map +1 -0
- package/dist/state.d.ts +38 -0
- package/dist/state.js +59 -0
- package/dist/state.js.map +1 -0
- package/docs/recipe.md +253 -0
- package/hooks/hooks.json +34 -0
- package/hooks/lib.mjs +66 -0
- package/hooks/routing.mjs +99 -0
- package/hooks/session-start.mjs +34 -0
- package/hooks/stop.mjs +52 -0
- package/hooks/user-prompt-submit.mjs +16 -0
- package/package.json +57 -0
- package/personas/architect/SOUL.md +27 -0
- package/personas/code-reviewer/SOUL.md +30 -0
- package/personas/critic/SOUL.md +28 -0
- package/personas/debugger/SOUL.md +28 -0
- package/personas/executor/SOUL.md +25 -0
- package/personas/explore/SOUL.md +24 -0
- package/personas/manifest.json +119 -0
- package/personas/planner/SOUL.md +25 -0
- package/personas/test-engineer/SOUL.md +27 -0
- package/personas/verifier/SOUL.md +29 -0
- package/personas/writer/SOUL.md +27 -0
- package/scripts/install.mjs +303 -0
- package/scripts/preflight.mjs +121 -0
- package/scripts/settings-install.mjs +155 -0
- package/scripts/verify-manifest.mjs +211 -0
- package/scripts/verify-skills.mjs +78 -0
- package/skills/cancel/SKILL.md +76 -0
- package/skills/deep-dive/SKILL.md +73 -0
- package/skills/deep-interview/SKILL.md +101 -0
- package/skills/ralph/SKILL.md +111 -0
- package/skills/ralplan/SKILL.md +96 -0
- package/skills/team/SKILL.md +94 -0
- package/skills/trace/SKILL.md +75 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "oh-my-musecode",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "oh-my-musecode"
|
|
5
|
+
},
|
|
6
|
+
"metadata": {
|
|
7
|
+
"description": "An OMC-shaped delivery harness for Meta Muse Code.",
|
|
8
|
+
"version": "0.1.0"
|
|
9
|
+
},
|
|
10
|
+
"plugins": [
|
|
11
|
+
{
|
|
12
|
+
"name": "oh-my-musecode",
|
|
13
|
+
"source": "./",
|
|
14
|
+
"version": "0.1.0",
|
|
15
|
+
"description": "Ported oh-my-claudecode delivery pipeline for the muse CLI: deep-interview -> ralplan -> ralph, plus deep-dive/trace, team and cancel."
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "oh-my-musecode",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "An OMC-shaped delivery harness for Meta Muse Code: deep-interview -> ralplan -> ralph, plus deep-dive/trace, team and cancel. Personas are rendered into subagent prompts rather than declared as agent definitions, because muse rejects `agents` as a plugin capability.",
|
|
5
|
+
"capabilities": {
|
|
6
|
+
"skills": [
|
|
7
|
+
"./skills/deep-interview",
|
|
8
|
+
"./skills/deep-dive",
|
|
9
|
+
"./skills/trace",
|
|
10
|
+
"./skills/ralplan",
|
|
11
|
+
"./skills/ralph",
|
|
12
|
+
"./skills/team",
|
|
13
|
+
"./skills/cancel"
|
|
14
|
+
],
|
|
15
|
+
"hooks": [
|
|
16
|
+
"./hooks/hooks.json"
|
|
17
|
+
],
|
|
18
|
+
"commands": [],
|
|
19
|
+
"mcpServers": [
|
|
20
|
+
{
|
|
21
|
+
"name": "omm-state",
|
|
22
|
+
"transport": "stdio",
|
|
23
|
+
"command": "node",
|
|
24
|
+
"args": [
|
|
25
|
+
"${MUSE_PLUGIN_ROOT}/dist/mcp/state-server.js"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"name": "oh-my-musecode",
|
|
4
|
+
"displayName": "oh-my-musecode",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "A gated delivery pipeline for muse: deep-interview to ralplan to ralph, with deep-dive, trace, team and cancel.",
|
|
7
|
+
"compat": {
|
|
8
|
+
"source": "native",
|
|
9
|
+
"manifestDir": ".muse-plugin"
|
|
10
|
+
},
|
|
11
|
+
"capabilities": {
|
|
12
|
+
"skills": [
|
|
13
|
+
{
|
|
14
|
+
"id": "deep-interview",
|
|
15
|
+
"path": "skills/deep-interview/SKILL.md",
|
|
16
|
+
"enabledDefault": true
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "deep-dive",
|
|
20
|
+
"path": "skills/deep-dive/SKILL.md",
|
|
21
|
+
"enabledDefault": true
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "trace",
|
|
25
|
+
"path": "skills/trace/SKILL.md",
|
|
26
|
+
"enabledDefault": true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "ralplan",
|
|
30
|
+
"path": "skills/ralplan/SKILL.md",
|
|
31
|
+
"enabledDefault": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "ralph",
|
|
35
|
+
"path": "skills/ralph/SKILL.md",
|
|
36
|
+
"enabledDefault": true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "team",
|
|
40
|
+
"path": "skills/team/SKILL.md",
|
|
41
|
+
"enabledDefault": true
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "cancel",
|
|
45
|
+
"path": "skills/cancel/SKILL.md",
|
|
46
|
+
"enabledDefault": true
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"commands": [],
|
|
50
|
+
"hooks": [
|
|
51
|
+
{
|
|
52
|
+
"id": "omm-route",
|
|
53
|
+
"event": "UserPromptSubmit",
|
|
54
|
+
"command": [
|
|
55
|
+
"node",
|
|
56
|
+
"hooks/user-prompt-submit.mjs"
|
|
57
|
+
],
|
|
58
|
+
"timeoutMs": 5000,
|
|
59
|
+
"statusMessage": "Checking for oh-my-musecode skills"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "omm-bootstrap",
|
|
63
|
+
"event": "SessionStart",
|
|
64
|
+
"command": [
|
|
65
|
+
"node",
|
|
66
|
+
"hooks/session-start.mjs"
|
|
67
|
+
],
|
|
68
|
+
"timeoutMs": 5000,
|
|
69
|
+
"statusMessage": "Preparing oh-my-musecode state"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "omm-verification-reminder",
|
|
73
|
+
"event": "Stop",
|
|
74
|
+
"command": [
|
|
75
|
+
"node",
|
|
76
|
+
"hooks/stop.mjs"
|
|
77
|
+
],
|
|
78
|
+
"timeoutMs": 5000,
|
|
79
|
+
"statusMessage": "Checking for an active ralph run"
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"mcpServers": [
|
|
83
|
+
{
|
|
84
|
+
"id": "omm-state",
|
|
85
|
+
"transport": "stdio",
|
|
86
|
+
"command": [
|
|
87
|
+
"node",
|
|
88
|
+
"dist/mcp/state-server.js"
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"reminders": []
|
|
93
|
+
}
|
|
94
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abdullah Siddique
|
|
4
|
+
|
|
5
|
+
This project is a port of oh-my-claudecode to Meta's Muse Code CLI, and is
|
|
6
|
+
derived in part from that project's design and skill definitions:
|
|
7
|
+
|
|
8
|
+
oh-my-claudecode
|
|
9
|
+
https://github.com/Yeachan-Heo/oh-my-claudecode
|
|
10
|
+
Copyright (c) 2025 Yeachan Heo
|
|
11
|
+
Licensed under the MIT License
|
|
12
|
+
|
|
13
|
+
The above copyright notices and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
17
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
18
|
+
in the Software without restriction, including without limitation the rights
|
|
19
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
20
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
21
|
+
furnished to do so, subject to the following conditions:
|
|
22
|
+
|
|
23
|
+
The above copyright notice and this permission notice shall be included in all
|
|
24
|
+
copies or substantial portions of the Software.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
28
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
29
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
30
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
31
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
32
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# oh-my-musecode
|
|
2
|
+
|
|
3
|
+
An OMC-shaped delivery harness for [Meta Muse Code](https://dev.meta.ai/docs/cookbook#building-with-muse-code)
|
|
4
|
+
(the `muse` CLI): a gated pipeline from a vague request to verified working
|
|
5
|
+
code. It ships a native muse plugin manifest, but on muse 1.0.3 the plugins
|
|
6
|
+
subsystem itself is disabled, so it installs today through `muse skills
|
|
7
|
+
install` plus a `settings.json` merge instead — see Install below.
|
|
8
|
+
|
|
9
|
+
It ports oh-my-claudecode's Tier-0 pipeline — `deep-interview → deep-dive/trace
|
|
10
|
+
→ ralplan → ralph`, plus `team` and `cancel` — onto `muse` 1.0.3, respecting
|
|
11
|
+
Muse's approval/sandbox/trust/audit model instead of working around it. See
|
|
12
|
+
[`docs/recipe.md`](docs/recipe.md) for the full walkthrough and acceptance
|
|
13
|
+
run.
|
|
14
|
+
|
|
15
|
+
## The pipeline
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
/deep-interview → turns a vague request into an approved spec (.omm/specs/<slug>.md)
|
|
19
|
+
/deep-dive → front door when it's unclear whether the problem is a bug or a scope
|
|
20
|
+
question; runs trace first, then deep-interview if needed
|
|
21
|
+
/trace → root-causes a concrete symptom via parallel competing hypotheses
|
|
22
|
+
/ralplan → turns an approved spec into a consensus-reviewed PRD (.omm/state/prd.json)
|
|
23
|
+
/ralph → runs the PRD to completion: implement each story, verify its
|
|
24
|
+
acceptance criteria, loop until done or blocked
|
|
25
|
+
/team → N persona subagents in parallel worktrees on independent tasks
|
|
26
|
+
/cancel → stop whatever pipeline stage is active and clean up .omm/ state
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Every skill here is **explicit-invocation only**. Muse never fires a skill on
|
|
30
|
+
its own because a task looks complex or ambiguous — you (or another skill's
|
|
31
|
+
prose) has to name it. Each `SKILL.md` says this in its own body, because
|
|
32
|
+
there is no `triggers` field in Muse's skill frontmatter to enforce it.
|
|
33
|
+
Sequencing between stages works the same way: no `SKILL.md` chains
|
|
34
|
+
automatically into the next one — the skill's body tells you, in prose, what
|
|
35
|
+
to run next (`deep-interview` → "run `/ralplan`"; `ralplan` → "run `/ralph`").
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install
|
|
41
|
+
npm run build
|
|
42
|
+
node scripts/install.mjs --workspace <path> --dry-run # preview, writes nothing
|
|
43
|
+
node scripts/install.mjs --workspace <path> # installs for real
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`scripts/install.mjs` first probes the local `muse` build: `muse plugins
|
|
47
|
+
--help` (and every other `muse plugins` subcommand) answers "plugins are not
|
|
48
|
+
available in this build" on muse 1.0.3-R2198.1, and registering
|
|
49
|
+
`.agents/plugins/marketplace.json` anyway just yields
|
|
50
|
+
`muse skills list --source plugin --json` → `{"skills":[],"diagnostics":[]}`
|
|
51
|
+
— no discovery, no error, nothing delivered. **There is no `muse plugin
|
|
52
|
+
install` command on this build either.**
|
|
53
|
+
|
|
54
|
+
So on 1.0.3 the installer skips the plugin route entirely and delivers
|
|
55
|
+
through three routes verified to work:
|
|
56
|
+
|
|
57
|
+
| Piece | Route |
|
|
58
|
+
|---|---|
|
|
59
|
+
| Skills | `muse skills install <dir> --scope user --force` for each of the 7 skills, landing in `$CONFIG_DIR/skills/` |
|
|
60
|
+
| Hooks | a `hooks` entry merged into `$CONFIG_DIR/muse/settings.json` |
|
|
61
|
+
| MCP server | an `mcpServers` entry in the same `settings.json` |
|
|
62
|
+
|
|
63
|
+
(`$CONFIG_DIR` is `~/.config/muse`, or `$XDG_CONFIG_HOME/muse` when that's
|
|
64
|
+
set.) The installer also runs an escalation preflight against your local
|
|
65
|
+
`muse` build and reports plainly what it finds (see External critic below),
|
|
66
|
+
refusing to install if an enterprise policy forbids the only escalation
|
|
67
|
+
route entirely.
|
|
68
|
+
|
|
69
|
+
The repo also ships a native `.muse-plugin/plugin.json` manifest — correct
|
|
70
|
+
per muse's own documented plugin contract, and what a build with plugins
|
|
71
|
+
*enabled* would load directly. `scripts/install.mjs` detects support at
|
|
72
|
+
runtime (`pluginsSupported()`) and would use it automatically on such a
|
|
73
|
+
build. On 1.0.3-R2198.1 it is inert; do not treat it as the working install
|
|
74
|
+
path today. `.claude-plugin/` is kept alongside it only for Claude-family
|
|
75
|
+
tooling compatibility.
|
|
76
|
+
|
|
77
|
+
Confirm the skills installed:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
muse skills list --source user
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
All 7 (`deep-interview`, `deep-dive`, `trace`, `ralplan`, `ralph`, `team`,
|
|
84
|
+
`cancel`) should appear with `scope: "user"`.
|
|
85
|
+
|
|
86
|
+
## Skills
|
|
87
|
+
|
|
88
|
+
Seven skills, installed user-scoped on this build, **explicit-invocation
|
|
89
|
+
only** — muse never auto-fires a skill:
|
|
90
|
+
|
|
91
|
+
| Skill | What it does |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `deep-interview` | Socratic interview gated by a measured ambiguity score, producing an approved spec |
|
|
94
|
+
| `deep-dive` | Two-stage front door: runs `trace` first, then `deep-interview` if the problem turns out to be a scope question |
|
|
95
|
+
| `trace` | Root-causes a concrete symptom via parallel competing hypotheses and evidence-gatherers |
|
|
96
|
+
| `ralplan` | Turns an approved spec into a consensus-reviewed PRD (`prd.json`) of testable stories |
|
|
97
|
+
| `ralph` | Runs the PRD to completion: implement, verify each acceptance criterion, loop until done or blocked |
|
|
98
|
+
| `team` | N persona subagents in parallel, each isolated in its own worktree, for genuinely independent work |
|
|
99
|
+
| `cancel` | Ends the active pipeline stage and cleans up `.omm/` state — does not revert code |
|
|
100
|
+
|
|
101
|
+
## Personas
|
|
102
|
+
|
|
103
|
+
Ten personas (`executor`, `planner`, `architect`, `critic`, `explore`,
|
|
104
|
+
`verifier`, `code-reviewer`, `debugger`, `writer`, `test-engineer`), each a
|
|
105
|
+
`SOUL.md` body plus a declaratively narrowed toolset in
|
|
106
|
+
`personas/manifest.json`.
|
|
107
|
+
|
|
108
|
+
These are **not muse Agent Definitions** — muse rejects `agents` as a plugin
|
|
109
|
+
capability (a Claude-family plugin declaring it gets
|
|
110
|
+
`unsupported-agent-schema`/`agent-overlay-inactive`, and the definitions
|
|
111
|
+
never activate), so there is no route to register them as first-class agent
|
|
112
|
+
types. `scripts/verify-manifest.mjs` fails the build if the plugin manifest
|
|
113
|
+
ever tries to declare `agents` anyway.
|
|
114
|
+
|
|
115
|
+
Instead, the bundled `omm-state` MCP server exposes `persona_list` and
|
|
116
|
+
`persona_render` tools: `persona_list` returns every persona id with its
|
|
117
|
+
routing description; `persona_render(id)` returns that persona's `SOUL.md`
|
|
118
|
+
text plus its narrowed tool allowlist from `personas/manifest.json`, ready
|
|
119
|
+
to interpolate into a `subagent_spawn(role, objective, worktree_isolation)`
|
|
120
|
+
call. A skill calls `persona_list` to pick the right persona, then
|
|
121
|
+
`persona_render` to pull its prompt text and tool allowlist into the
|
|
122
|
+
objective it hands to `subagent_spawn` — the narrowing is advisory and the
|
|
123
|
+
caller applies it, since muse has no first-class concept of a persona's
|
|
124
|
+
tool scope.
|
|
125
|
+
|
|
126
|
+
## State
|
|
127
|
+
|
|
128
|
+
Runtime state lives under `.omm/` at the workspace root
|
|
129
|
+
(`.omm/specs/`, `.omm/state/`), never under `.agents/` or `.muse/`.
|
|
130
|
+
|
|
131
|
+
Those two paths are **muse-protected**: a mediated `edit_file`/`write_file`
|
|
132
|
+
write there is held for human review with no standing grant, and a shell
|
|
133
|
+
write fails read-only at the sandbox. `.omm/` is ordinary, unprotected
|
|
134
|
+
workspace state, so hooks and the bundled MCP state server can read and write
|
|
135
|
+
it freely — `src/paths.ts` is the single place that enforces this boundary
|
|
136
|
+
and refuses any write that resolves into `.agents/`, `.muse/`, or `.git/`.
|
|
137
|
+
|
|
138
|
+
## External critic
|
|
139
|
+
|
|
140
|
+
`ralplan` and `ralph` both accept `--critic codex` (or `--critic claude`) to
|
|
141
|
+
route consensus review and final verification through an external CLI
|
|
142
|
+
process instead of the in-process `critic`/`verifier` personas.
|
|
143
|
+
|
|
144
|
+
This buys genuine cross-model adversarial review, at two real costs, stated
|
|
145
|
+
plainly rather than soft-pedaled:
|
|
146
|
+
|
|
147
|
+
1. **It's session-wide, not scoped to the critic call.** muse 1.0.3 has no
|
|
148
|
+
named permission profile to escalate just the critic — `--permission-profile
|
|
149
|
+
<id>` reports the profile does not exist, and
|
|
150
|
+
`execution.permission_profiles` validates as `field_not_activated`. The
|
|
151
|
+
only route is launching the **entire session** with `muse
|
|
152
|
+
--disable-sandbox` (or `--yolo`), which removes filesystem and network
|
|
153
|
+
sandboxing for everything in that session, not just the one process that
|
|
154
|
+
needed it.
|
|
155
|
+
2. **The external critic's work falls outside Muse's append-only audit
|
|
156
|
+
trail.** Its reasoning and any files it touches are not captured the way
|
|
157
|
+
an in-session `subagent_spawn` result is — `muse export` will show that
|
|
158
|
+
`ralph`/`ralplan` invoked it, not what it did internally.
|
|
159
|
+
|
|
160
|
+
If an enterprise policy sets `execution.forbid_sandbox_bypass`, both
|
|
161
|
+
`--disable-sandbox` and `--yolo` are refused outright and the external
|
|
162
|
+
critic cannot run at all; `scripts/install.mjs` checks for this at install
|
|
163
|
+
time, and `ralph`/`ralplan` check it again at run time rather than silently
|
|
164
|
+
falling back to the in-process critic.
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npm test # build + node --test over test/**/*.test.mjs
|
|
170
|
+
npm run lint # verify-manifest.mjs + tsc --noEmit
|
|
171
|
+
npm run verify:skills # validates all 7 skills against the muse binary, failing on any inert frontmatter key
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Credits and license
|
|
175
|
+
|
|
176
|
+
oh-my-musecode is a port of [oh-my-claudecode](https://github.com/Yeachan-Heo/oh-my-claudecode)
|
|
177
|
+
(MIT, Copyright (c) 2025 Yeachan Heo) to Meta's Muse Code CLI. The pipeline shape
|
|
178
|
+
— `deep-interview` → `ralplan` → `ralph`, plus `deep-dive`/`trace`, `team` and
|
|
179
|
+
`cancel` — and the PRD-driven persistence loop come from that project. The skill
|
|
180
|
+
bodies, personas, hooks, MCP server and installer here were rewritten against
|
|
181
|
+
muse's own contracts, because muse's frontmatter subset, invoke-only skills,
|
|
182
|
+
protected paths and plugin capability rules differ substantially from Claude Code's.
|
|
183
|
+
|
|
184
|
+
The persona model follows the Hermes profile pattern documented in Meta's
|
|
185
|
+
[meta-model-cookbook](https://github.com/meta-llama/meta-model-cookbook): a
|
|
186
|
+
durable SOUL body, a routing description, and a declaratively narrowed toolset,
|
|
187
|
+
with project-specific conventions kept out of the persona.
|
|
188
|
+
|
|
189
|
+
Licensed under the MIT License — see [LICENSE](LICENSE), which retains the
|
|
190
|
+
upstream copyright notice as MIT requires.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* omm-state: an MCP server exposing the .omm/ state root over stdio.
|
|
4
|
+
*
|
|
5
|
+
* Skills need somewhere durable to keep PRDs, specs and run state across turns.
|
|
6
|
+
* That cannot be `.agents/` or `.muse/` — muse protects both — so this server is
|
|
7
|
+
* the sanctioned way to reach `.omm/`.
|
|
8
|
+
*
|
|
9
|
+
* Every tool resolves its path through StateStore, which fails closed on a
|
|
10
|
+
* protected or escaping path. A refusal is returned as a tool error rather than
|
|
11
|
+
* thrown, so the model sees why it was refused and can correct itself.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* omm-state: an MCP server exposing the .omm/ state root over stdio.
|
|
4
|
+
*
|
|
5
|
+
* Skills need somewhere durable to keep PRDs, specs and run state across turns.
|
|
6
|
+
* That cannot be `.agents/` or `.muse/` — muse protects both — so this server is
|
|
7
|
+
* the sanctioned way to reach `.omm/`.
|
|
8
|
+
*
|
|
9
|
+
* Every tool resolves its path through StateStore, which fails closed on a
|
|
10
|
+
* protected or escaping path. A refusal is returned as a tool error rather than
|
|
11
|
+
* thrown, so the model sees why it was refused and can correct itself.
|
|
12
|
+
*/
|
|
13
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
14
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import { StateStore } from '../state.js';
|
|
17
|
+
import { loadPersonas, renderPersonaPrompt } from '../personas.js';
|
|
18
|
+
import { EscapedStateRootError, ProtectedPathError } from '../paths.js';
|
|
19
|
+
const store = new StateStore({
|
|
20
|
+
workspaceRoot: process.env.MUSE_WORKSPACE_ROOT ?? process.cwd(),
|
|
21
|
+
});
|
|
22
|
+
/** Wraps a store call so a policy refusal becomes a legible tool error. */
|
|
23
|
+
function guarded(run) {
|
|
24
|
+
try {
|
|
25
|
+
return { content: [{ type: 'text', text: run() }] };
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (err instanceof ProtectedPathError || err instanceof EscapedStateRootError) {
|
|
29
|
+
return { content: [{ type: 'text', text: err.message }], isError: true };
|
|
30
|
+
}
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const server = new McpServer({ name: 'omm-state', version: '0.1.0' }, {
|
|
35
|
+
instructions: 'Durable state for oh-my-musecode, rooted at .omm/. Paths are relative to that ' +
|
|
36
|
+
'root. Writes to .agents/ or .muse/ are refused: muse protects them.',
|
|
37
|
+
});
|
|
38
|
+
server.registerTool('state_read', {
|
|
39
|
+
title: 'Read oh-my-musecode state',
|
|
40
|
+
description: 'Read a file from the .omm/ state root. Path is relative to .omm/. Returns an ' +
|
|
41
|
+
'empty result when the file does not exist.',
|
|
42
|
+
inputSchema: {
|
|
43
|
+
path: z.string().describe('Path relative to the .omm/ state root, e.g. "state/ralph-state.json"'),
|
|
44
|
+
},
|
|
45
|
+
}, async ({ path }) => guarded(() => {
|
|
46
|
+
const contents = store.read(path);
|
|
47
|
+
return contents ?? `(no state at ${path})`;
|
|
48
|
+
}));
|
|
49
|
+
server.registerTool('state_write', {
|
|
50
|
+
title: 'Write oh-my-musecode state',
|
|
51
|
+
description: 'Write a file into the .omm/ state root, creating parent directories. Path is ' +
|
|
52
|
+
'relative to .omm/. Refuses any path that escapes the root or lands in a ' +
|
|
53
|
+
'muse-protected directory (.agents/, .muse/, .git/).',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
path: z.string().describe('Path relative to the .omm/ state root'),
|
|
56
|
+
contents: z.string().describe('Full file contents to write'),
|
|
57
|
+
},
|
|
58
|
+
}, async ({ path, contents }) => guarded(() => `wrote ${store.write(path, contents)}`));
|
|
59
|
+
server.registerTool('state_clear', {
|
|
60
|
+
title: 'Clear oh-my-musecode state',
|
|
61
|
+
description: 'Remove a file or directory from the .omm/ state root. A missing target is a ' +
|
|
62
|
+
'no-op. Subject to the same protected-path refusal as state_write.',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
path: z.string().describe('Path relative to the .omm/ state root'),
|
|
65
|
+
},
|
|
66
|
+
}, async ({ path }) => guarded(() => (store.clear(path) ? `cleared ${path}` : `nothing to clear at ${path}`)));
|
|
67
|
+
server.registerTool('persona_render', {
|
|
68
|
+
title: 'Render an oh-my-musecode persona',
|
|
69
|
+
description: 'Return a persona\'s SOUL text and routing description, ready to interpolate into a ' +
|
|
70
|
+
'subagent_spawn objective, plus the tool allowlist that child should be narrowed to. ' +
|
|
71
|
+
'Personas are prompts, not muse agent definitions: muse rejects `agents` as a plugin ' +
|
|
72
|
+
'capability, so the narrowing is advisory and the caller must apply it.',
|
|
73
|
+
inputSchema: {
|
|
74
|
+
id: z
|
|
75
|
+
.string()
|
|
76
|
+
.describe('Persona id, e.g. "executor", "critic", "verifier". Omit nothing; ids are exact.'),
|
|
77
|
+
},
|
|
78
|
+
}, async ({ id }) => {
|
|
79
|
+
try {
|
|
80
|
+
const persona = loadPersonas().find((p) => p.id === id);
|
|
81
|
+
const rendered = renderPersonaPrompt(id);
|
|
82
|
+
const tools = persona ? persona.tools.join(', ') : '';
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{ type: 'text', text: `${rendered}\n\nNarrow this child's tools to: ${tools}` },
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
return { content: [{ type: 'text', text: err.message }], isError: true };
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
server.registerTool('persona_list', {
|
|
94
|
+
title: 'List oh-my-musecode personas',
|
|
95
|
+
description: 'List every available persona id with its routing description, so a skill can choose ' +
|
|
96
|
+
'the right one before calling persona_render.',
|
|
97
|
+
inputSchema: {},
|
|
98
|
+
}, async () => ({
|
|
99
|
+
content: [
|
|
100
|
+
{
|
|
101
|
+
type: 'text',
|
|
102
|
+
text: loadPersonas()
|
|
103
|
+
.map((p) => `${p.id}: ${p.description}`)
|
|
104
|
+
.join('\n'),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
}));
|
|
108
|
+
await server.connect(new StdioServerTransport());
|
|
109
|
+
//# sourceMappingURL=state-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-server.js","sourceRoot":"","sources":["../../src/mcp/state-server.ts"],"names":[],"mappings":"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"}
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State-root resolution and protected-path enforcement.
|
|
3
|
+
*
|
|
4
|
+
* muse protects `.agents/` and `.muse/` with two independent layers: a mediated
|
|
5
|
+
* `edit_file` write is held for human review with no standing grant, and a shell
|
|
6
|
+
* write fails read-only at the sandbox. Runtime state therefore cannot live in
|
|
7
|
+
* either, so oh-my-musecode keeps its own root at `.omm/`.
|
|
8
|
+
*
|
|
9
|
+
* This module is the single place that decides whether a path is writable, so the
|
|
10
|
+
* hooks and the MCP state server cannot drift apart on it.
|
|
11
|
+
*/
|
|
12
|
+
/** Directory name of the oh-my-musecode state root, relative to the workspace. */
|
|
13
|
+
export declare const STATE_ROOT_DIRNAME = ".omm";
|
|
14
|
+
/**
|
|
15
|
+
* Path prefixes muse protects. A write that resolves inside any of these is
|
|
16
|
+
* refused before it reaches the filesystem, so the failure is a clear error
|
|
17
|
+
* rather than an opaque read-only sandbox rejection mid-run.
|
|
18
|
+
*/
|
|
19
|
+
export declare const PROTECTED_DIRNAMES: readonly string[];
|
|
20
|
+
export declare class ProtectedPathError extends Error {
|
|
21
|
+
readonly path: string;
|
|
22
|
+
readonly protectedSegment: string;
|
|
23
|
+
constructor(path: string, protectedSegment: string);
|
|
24
|
+
}
|
|
25
|
+
export declare class EscapedStateRootError extends Error {
|
|
26
|
+
readonly path: string;
|
|
27
|
+
constructor(path: string, stateRoot: string);
|
|
28
|
+
}
|
|
29
|
+
/** Absolute path of the state root for a workspace. */
|
|
30
|
+
export declare function stateRoot(workspaceRoot: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Creates the state root if absent. Returns its absolute path.
|
|
33
|
+
*
|
|
34
|
+
* Called by the SessionStart hook. Deliberately does not touch `.agents/` or
|
|
35
|
+
* `.muse/`, which the harness owns.
|
|
36
|
+
*/
|
|
37
|
+
export declare function ensureStateRoot(workspaceRoot: string): string;
|
|
38
|
+
export declare class SymlinkedStateRootError extends Error {
|
|
39
|
+
readonly path: string;
|
|
40
|
+
constructor(root: string);
|
|
41
|
+
}
|
|
42
|
+
export declare class SymlinkTraversalError extends Error {
|
|
43
|
+
readonly path: string;
|
|
44
|
+
constructor(path: string, linkPath: string);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolves a caller-supplied path against the state root and refuses anything
|
|
48
|
+
* that escapes it or lands in a muse-protected directory.
|
|
49
|
+
*
|
|
50
|
+
* Both checks are needed and neither subsumes the other: the protected-path check
|
|
51
|
+
* gives an accurate message for the common mistake (`../../.agents/AGENTS.md`),
|
|
52
|
+
* while the containment check is the actual security boundary and catches every
|
|
53
|
+
* other escape.
|
|
54
|
+
*
|
|
55
|
+
* @throws {ProtectedPathError} when the path resolves into `.agents/`, `.muse/` or `.git/`
|
|
56
|
+
* @throws {EscapedStateRootError} when the path resolves outside the state root
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveWritablePath(workspaceRoot: string, requestedPath: string): string;
|