@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.
Files changed (48) hide show
  1. package/.claude-plugin/marketplace.json +18 -0
  2. package/.claude-plugin/plugin.json +30 -0
  3. package/.muse-plugin/plugin.json +94 -0
  4. package/LICENSE +32 -0
  5. package/README.md +190 -0
  6. package/dist/mcp/state-server.d.ts +13 -0
  7. package/dist/mcp/state-server.js +109 -0
  8. package/dist/mcp/state-server.js.map +1 -0
  9. package/dist/paths.d.ts +58 -0
  10. package/dist/paths.js +167 -0
  11. package/dist/paths.js.map +1 -0
  12. package/dist/personas.d.ts +40 -0
  13. package/dist/personas.js +93 -0
  14. package/dist/personas.js.map +1 -0
  15. package/dist/state.d.ts +38 -0
  16. package/dist/state.js +59 -0
  17. package/dist/state.js.map +1 -0
  18. package/docs/recipe.md +253 -0
  19. package/hooks/hooks.json +34 -0
  20. package/hooks/lib.mjs +66 -0
  21. package/hooks/routing.mjs +99 -0
  22. package/hooks/session-start.mjs +34 -0
  23. package/hooks/stop.mjs +52 -0
  24. package/hooks/user-prompt-submit.mjs +16 -0
  25. package/package.json +57 -0
  26. package/personas/architect/SOUL.md +27 -0
  27. package/personas/code-reviewer/SOUL.md +30 -0
  28. package/personas/critic/SOUL.md +28 -0
  29. package/personas/debugger/SOUL.md +28 -0
  30. package/personas/executor/SOUL.md +25 -0
  31. package/personas/explore/SOUL.md +24 -0
  32. package/personas/manifest.json +119 -0
  33. package/personas/planner/SOUL.md +25 -0
  34. package/personas/test-engineer/SOUL.md +27 -0
  35. package/personas/verifier/SOUL.md +29 -0
  36. package/personas/writer/SOUL.md +27 -0
  37. package/scripts/install.mjs +303 -0
  38. package/scripts/preflight.mjs +121 -0
  39. package/scripts/settings-install.mjs +155 -0
  40. package/scripts/verify-manifest.mjs +211 -0
  41. package/scripts/verify-skills.mjs +78 -0
  42. package/skills/cancel/SKILL.md +76 -0
  43. package/skills/deep-dive/SKILL.md +73 -0
  44. package/skills/deep-interview/SKILL.md +101 -0
  45. package/skills/ralph/SKILL.md +111 -0
  46. package/skills/ralplan/SKILL.md +96 -0
  47. package/skills/team/SKILL.md +94 -0
  48. 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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"}
@@ -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;