@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
package/docs/recipe.md ADDED
@@ -0,0 +1,253 @@
1
+ # oh-my-musecode: a gated delivery pipeline that self-hosts
2
+
3
+ | | |
4
+ |---|---|
5
+ | **Section** | Muse Code plugin |
6
+ | **Time to complete** | ~45 min |
7
+ | **Model** | `meta` provider (any Meta model muse is configured for) |
8
+ | **Harness** | Muse Code (the `muse` CLI, 1.0.3) |
9
+ | **Prerequisites** | Node.js >= 20, `muse` on `PATH`, a workspace you can trust |
10
+
11
+ ## Summary
12
+
13
+ oh-my-musecode ports oh-my-claudecode's Tier-0 delivery pipeline —
14
+ `deep-interview → deep-dive/trace → ralplan → ralph`, plus `team` and
15
+ `cancel` — onto the `muse` CLI. It ships seven skills and ten personas whose
16
+ `SOUL.md` text and tool allowlists are pulled in on demand via the bundled
17
+ `omm-state` MCP server's `persona_list`/`persona_render` tools, ready to
18
+ interpolate into a `subagent_spawn` objective. Everything is wired together
19
+ with hooks rather than frontmatter, because Muse's skill frontmatter carries
20
+ no `pipeline` or `next-skill` field to read. Each skill's own body tells the
21
+ user, in prose, what to run next.
22
+
23
+ The repo ships a native `.muse-plugin/plugin.json` manifest, correct per
24
+ muse's own documented plugin contract — but muse 1.0.3-R2198.1's plugins
25
+ subsystem is disabled outright (`muse plugins --help` answers "plugins are
26
+ not available in this build"), so on this build the manifest is inert and
27
+ delivery goes through `muse skills install`, a `hooks` entry, and an
28
+ `mcpServers` entry in `settings.json` instead. See Orchestration contract
29
+ and step 1 below for exactly which route runs today.
30
+
31
+ This recipe walks the repo's own acceptance run: installing it, then driving
32
+ `/deep-interview → /ralplan --critic codex → /ralph --critic codex` against
33
+ this same repository. The run is self-hosting — it builds one of the repo's
34
+ own seven skills — so a failure here is a real defect, not a toy demo. What
35
+ is actually verified as of this writing, and what is not, is stated plainly
36
+ in the Proof Point section below — the full pipeline run has not yet been
37
+ executed end to end in a muse session.
38
+
39
+ ## When To Use
40
+
41
+ Use this recipe when you want a repeatable pipeline for turning a vague request
42
+ into an approved spec, a reviewed PRD, and verified working code, with an
43
+ optional cross-model adversarial review at the planning and verification
44
+ gates — and you want that pipeline to run on `muse` specifically, respecting
45
+ its approval/sandbox/trust/audit model instead of working around it.
46
+
47
+ Do not use it for a single small edit — the interview and planning stages exist
48
+ to gate genuinely ambiguous or multi-story work, and their overhead does not
49
+ pay for itself on a one-line fix. Do not reach for `--critic codex` casually:
50
+ it requires disabling the session sandbox for the whole run, not just the
51
+ critic call (see Proof Point and What Can Go Wrong below). If the work is
52
+ already well-specified, skip straight to `/ralplan` or even `/ralph` with a
53
+ hand-written `prd.json`.
54
+
55
+ ## Orchestration contract
56
+
57
+ | Contract | Muse mechanism |
58
+ |---|---|
59
+ | Skill catalog stays cheap | Skills load as summaries at session open; `read_skill`/invocation pulls the full `SKILL.md` for one turn only |
60
+ | Skills never self-trigger | Explicit-invocation only — no `triggers` field exists in Muse's skill frontmatter subset (`name`, `description`, `allowed-tools`); each `SKILL.md` body says so in prose |
61
+ | Pipeline sequencing | No `next-skill`/`handoff`/`pipeline` frontmatter field exists on Muse. Each skill's body tells the user in prose what to run next (`deep-interview` → "run `/ralplan`"; `ralplan` → "run `/ralph`") |
62
+ | Personas | Ten `SOUL.md` files, pulled in via the `omm-state` MCP server's `persona_list`/`persona_render` tools and interpolated into `subagent_spawn(role, objective, worktree_isolation)` prompt text — not muse Agent Definitions. A Claude-family plugin declaring `agents` gets `unsupported-agent-schema`/`agent-overlay-inactive`, so there is no route to register personas as first-class agent types |
63
+ | Parallel work isolation | `team` and multi-story `ralph` runs pass `worktree_isolation: true` to `subagent_spawn`, landing each child in its own `.muse/worktrees/` checkout |
64
+ | Runtime state | `.omm/` at the workspace root — `.agents/` and `.muse/` are muse-protected: a mediated `edit_file` write there is held for human review with no standing grant, and a shell write fails read-only at the sandbox |
65
+ | Keyword routing / bootstrap / verification gate | `UserPromptSubmit`, `SessionStart`, and `Stop` hooks (`hooks/hooks.json`, `hooks/*.mjs`) restore what inert frontmatter cannot do |
66
+ | Delivery on this build | `muse plugins` is disabled entirely on 1.0.3-R2198.1 ("plugins are not available in this build"), so `scripts/install.mjs` runs `muse skills install --scope user` per skill and merges `hooks`/`mcpServers` into `$CONFIG_DIR/muse/settings.json`. The repo also ships a native `.muse-plugin/plugin.json`, forward-looking for a build with plugins enabled — inert today; there is no `muse plugin install` command on any build |
67
+
68
+ ## Walkthrough: the self-hosting acceptance run
69
+
70
+ ### 1. Install
71
+
72
+ ```bash
73
+ git clone <this repo> oh-my-musecode && cd oh-my-musecode
74
+ npm install
75
+ npm run build
76
+ node scripts/install.mjs --workspace /path/to/oh-my-musecode --dry-run
77
+ ```
78
+
79
+ `--dry-run` prints what the installer would do without touching disk. It
80
+ first probes `muse plugins --help`: on 1.0.3-R2198.1 that answers "plugins
81
+ are not available in this build", so the installer takes the settings route
82
+ instead of writing a plugin marketplace entry that muse would silently
83
+ never load. Drop `--dry-run` to install for real. For this self-hosting run,
84
+ the target workspace is this repo itself:
85
+
86
+ ```bash
87
+ node scripts/install.mjs --workspace .
88
+ ```
89
+
90
+ This runs three routes, each verified to work on 1.0.3-R2198.1:
91
+
92
+ 1. `muse skills install <dir> --scope user --force` for each of the 7
93
+ skills, landing in `$CONFIG_DIR/skills/`.
94
+ 2. A `hooks` entry merged into `$CONFIG_DIR/muse/settings.json` (a
95
+ SessionStart hook installed this way is what creates `.omm/`).
96
+ 3. An `mcpServers` entry for `omm-state` in the same `settings.json`.
97
+
98
+ (`$CONFIG_DIR` is `~/.config/muse`, or `$XDG_CONFIG_HOME/muse` when set.)
99
+
100
+ The installer also runs an escalation preflight against the local `muse`
101
+ build and prints, plainly, whether named permission profiles are available.
102
+ On muse 1.0.3 they are not — read the "External critic posture" section it
103
+ prints; the rest of this walkthrough depends on it.
104
+
105
+ ### 2. Confirm the install and start a session
106
+
107
+ ```bash
108
+ muse skills list --source user
109
+ ```
110
+
111
+ All seven skills (`deep-interview`, `deep-dive`, `trace`, `ralplan`,
112
+ `ralph`, `team`, `cancel`) should appear with `scope: "user"`. Unlike a
113
+ plugin-loaded skill, a user-scoped skill installed this way does not need
114
+ `--trust-workspace` to be visible — but the hooks and MCP server delivered
115
+ through `settings.json` still only take effect inside a real `muse` session,
116
+ so start one before continuing:
117
+
118
+ ```bash
119
+ muse --trust-workspace
120
+ ```
121
+
122
+ (or `--yolo`, which also disables approval and sandboxing — do not use that
123
+ here yet; the external-critic stage below is the point where sandboxing
124
+ actually needs to come off, and that decision should be explicit, not a side
125
+ effect of an earlier `--yolo`.)
126
+
127
+ ### 3. `/deep-interview`
128
+
129
+ Inside the trusted session, pick one of the plugin's own seven skills that has
130
+ room for a real design decision (for example, tightening the `team` skill's
131
+ worktree-slot-queueing behavior) and run:
132
+
133
+ ```
134
+ /deep-interview Improve <chosen skill>'s <specific rough edge>.
135
+ ```
136
+
137
+ Answer the interview's questions until it reports ambiguity below threshold
138
+ and presents a spec. Approve it. The spec lands at `.omm/specs/<slug>.md`.
139
+
140
+ ### 4. `/ralplan --critic codex`
141
+
142
+ ```
143
+ /ralplan .omm/specs/<slug>.md --critic codex
144
+ ```
145
+
146
+ `ralplan` writes `.omm/state/prd.json`, then routes the consensus review
147
+ through an external `codex` CLI process instead of the in-process `critic`
148
+ persona. This is the point where the earlier preflight result matters: routing
149
+ through an external CLI requires the *whole session* to have been launched
150
+ with sandboxing disabled. If you started with plain `--trust-workspace`, stop
151
+ here, exit, and relaunch as:
152
+
153
+ ```bash
154
+ muse --disable-sandbox --trust-workspace
155
+ ```
156
+
157
+ This is **session-wide**, not scoped to the critic call — every tool call for
158
+ the rest of the session runs unsandboxed, not just the `codex exec` process
159
+ `ralplan` shells out to. It also means the external critic's reasoning and any
160
+ files it touches are **not captured in Muse's append-only session log** the
161
+ way an in-session `subagent_spawn` result is; that portion of the review
162
+ happens outside the audit trail this recipe otherwise relies on as evidence.
163
+ Re-run `/ralplan .omm/specs/<slug>.md --critic codex` in the relaunched
164
+ session. Review the PRD and its critic findings, then approve.
165
+
166
+ ### 5. `/ralph --critic codex`
167
+
168
+ ```
169
+ /ralph --critic codex
170
+ ```
171
+
172
+ `ralph` iterates `.omm/state/prd.json` story by story, implementing each one
173
+ (directly or via `subagent_spawn("executor", ..., worktree_isolation: true)`
174
+ for larger stories) and checking every acceptance criterion against real
175
+ command output. When every story is `done` or `blocked`, it runs a final
176
+ verification pass — here, through the same external `codex` CLI, under the
177
+ same session-wide `--disable-sandbox` already in effect. Same two costs as
178
+ step 4 apply again to this pass; `ralph`'s own completion report states them
179
+ again rather than assuming step 4 already covered it.
180
+
181
+ ## Proof Point
182
+
183
+ The run is verified done when all of the following hold. Some of these are
184
+ already true today and are stated as such below; the rest depend on
185
+ actually driving steps 3–5 of the walkthrough, which has **not yet been
186
+ executed end to end in a muse session** — this recipe describes the run to
187
+ perform, it does not claim the run has happened.
188
+
189
+ **Verified today, independent of the pipeline run:**
190
+
191
+ 1. `muse skills validate <path>` reports `valid`, with no
192
+ `unsupported-skill-field` diagnostics and an empty
193
+ `compatibility.unknown_fields`, for each of the seven skill directories
194
+ under `skills/` (`npm run verify:skills` checks this automatically).
195
+ 2. `muse skills install --scope user --force` installs all seven skills, and
196
+ `muse skills list --source user` lists all seven with `scope: "user"`.
197
+ 3. `npm test` passes: 84 tests covering routing, state-root protection,
198
+ persona manifest, MCP state server, and installer preflight.
199
+ 4. `muse --trust-workspace` (installed via `settings.json`, not a plugin
200
+ marketplace) fires the `SessionStart` hook, which creates `.omm/`.
201
+ 5. The `omm-state` MCP server completes an `initialize` handshake and
202
+ refuses reads/writes into protected or symlinked paths.
203
+
204
+ **Still outstanding — to confirm by actually running steps 3–5 above:**
205
+
206
+ 6. The `.omm/state/prd.json` story implemented by the run has
207
+ `status: "done"` with recorded evidence per acceptance criterion, and
208
+ none of its stories are `"blocked"`.
209
+ 7. `muse export --last --out trajectory.json` produces a self-contained,
210
+ append-only session log covering steps 3–5 of the walkthrough. It will
211
+ show the in-session tool calls (skill invocations, `subagent_spawn`
212
+ children, file writes under `.omm/`) but — honestly — will **not** show
213
+ what the external `codex` CLI process did internally during the `--critic
214
+ codex` stages; only that `ralph`/`ralplan` invoked it and what came back
215
+ on its stdout/stderr, if the skill body chose to record that.
216
+
217
+ ## What can go wrong
218
+
219
+ **Skills installed at the wrong scope, or plugins assumed to be on.** These
220
+ skills are installed with `--scope user`, into `$CONFIG_DIR/skills/`, not as
221
+ a workspace plugin — `muse plugins` is disabled on this build, so a
222
+ registered plugin marketplace loads nothing (`muse skills list --source
223
+ plugin --json` → `{"skills":[],"diagnostics":[]}`, silently, with no
224
+ diagnostic pointing at the cause). Confirm the real install with `muse
225
+ skills list --source user` instead. The hooks and `omm-state` MCP server
226
+ delivered via `$CONFIG_DIR/muse/settings.json` still only run inside an
227
+ actual `muse` session — launch with `muse --trust-workspace` (or `--yolo`)
228
+ before expecting the `SessionStart` bootstrap or `UserPromptSubmit` routing
229
+ hook to fire.
230
+
231
+ **Enterprise policy forbids the sandbox bypass.** If the workspace's
232
+ `execution` policy plane sets `forbid_sandbox_bypass`, both `--disable-sandbox`
233
+ and `--yolo` are refused outright. `scripts/install.mjs` checks for this at
234
+ install time and refuses to install rather than delivering a skill set whose
235
+ `--critic codex`/`--critic claude` paths cannot work; `ralph` and `ralplan`
236
+ check it again at run time and fail with a named-policy error rather than
237
+ silently falling back to the in-process critic and reporting as if the
238
+ external review ran. There is no narrower escalation route on muse 1.0.3 —
239
+ named permission profiles are not creatable on this build
240
+ (`execution.permission_profiles` validates `field_not_activated`, and
241
+ `muse exec --permission-profile <id>` reports the profile does not exist) — so
242
+ under this policy, the external-critic option is simply unavailable; use the
243
+ in-process `critic`/`verifier` personas instead.
244
+
245
+ **Headless `muse exec` can't answer approval prompts.** Running any of these
246
+ skills under `muse exec` (rather than the interactive TUI) with the default
247
+ `--approval-mode on-request` will stall on the first mediated tool call,
248
+ since there is no human present to answer it. Either pass
249
+ `--approval-mode never` (accepting that every tool call clears automatically
250
+ for that run) or `--user-input-auto-resolve` to auto-cancel `request_user_input`
251
+ prompts instead of hanging — know which of those two you actually want before
252
+ running `ralph` headless, since they trade off differently between unattended
253
+ progress and silently skipped decisions.
@@ -0,0 +1,34 @@
1
+ {
2
+ "hooks": {
3
+ "UserPromptSubmit": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "node \"${MUSE_PLUGIN_ROOT}/hooks/user-prompt-submit.mjs\""
9
+ }
10
+ ]
11
+ }
12
+ ],
13
+ "SessionStart": [
14
+ {
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "node \"${MUSE_PLUGIN_ROOT}/hooks/session-start.mjs\""
19
+ }
20
+ ]
21
+ }
22
+ ],
23
+ "Stop": [
24
+ {
25
+ "hooks": [
26
+ {
27
+ "type": "command",
28
+ "command": "node \"${MUSE_PLUGIN_ROOT}/hooks/stop.mjs\""
29
+ }
30
+ ]
31
+ }
32
+ ]
33
+ }
34
+ }
package/hooks/lib.mjs ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Shared hook plumbing.
3
+ *
4
+ * Hooks run before anything has been built, so this file and its callers stay
5
+ * plain ESM with no dependency on dist/ and no third-party imports.
6
+ */
7
+
8
+ /**
9
+ * Reads and parses the JSON payload muse writes to a hook's stdin.
10
+ *
11
+ * A hook that throws is a hook that breaks the user's session, so a missing or
12
+ * malformed payload resolves to an empty object and lets the caller no-op.
13
+ *
14
+ * @returns {Promise<Record<string, unknown>>}
15
+ */
16
+ export async function readPayload() {
17
+ if (process.stdin.isTTY) return {};
18
+
19
+ const chunks = [];
20
+ try {
21
+ for await (const chunk of process.stdin) chunks.push(chunk);
22
+ } catch {
23
+ return {};
24
+ }
25
+
26
+ const raw = Buffer.concat(chunks).toString('utf8').trim();
27
+ if (raw === '') return {};
28
+
29
+ try {
30
+ const parsed = JSON.parse(raw);
31
+ return parsed && typeof parsed === 'object' ? parsed : {};
32
+ } catch {
33
+ return {};
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Resolves the workspace root from the hook payload, falling back to cwd.
39
+ *
40
+ * @param {Record<string, any>} payload
41
+ * @returns {string}
42
+ */
43
+ export function workspaceRootFrom(payload) {
44
+ return (
45
+ payload.workspace_root ??
46
+ payload.workspaceRoot ??
47
+ payload.cwd ??
48
+ process.env.MUSE_WORKSPACE_ROOT ??
49
+ process.cwd()
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Emits additional context for the current turn and exits 0.
55
+ *
56
+ * @param {string | null} context
57
+ * @returns {never}
58
+ */
59
+ export function emitContext(context) {
60
+ if (context) {
61
+ process.stdout.write(
62
+ JSON.stringify({ hookSpecificOutput: { additionalContext: context } }) + '\n',
63
+ );
64
+ }
65
+ process.exit(0);
66
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Keyword -> skill routing table for the UserPromptSubmit hook.
3
+ *
4
+ * This exists because muse skills are explicit-invocation only: the harness never
5
+ * fires a skill just because a task looks complex, and skill frontmatter has no
6
+ * `triggers` field (muse's frontmatter profile knows only name/description/
7
+ * allowed-tools). So the only way to get oh-my-claudecode's keyword routing is a
8
+ * hook that recognises the word and *suggests* the skill.
9
+ *
10
+ * The hook suggests; it never invokes. Invocation stays the user's.
11
+ */
12
+
13
+ /** @type {ReadonlyArray<{ skill: string, keywords: readonly string[] }>} */
14
+ export const ROUTES = Object.freeze([
15
+ { skill: 'deep-interview', keywords: ['deep-interview', 'deep interview', 'interview me'] },
16
+ { skill: 'deep-dive', keywords: ['deep-dive', 'deep dive', 'investigate deeply'] },
17
+ { skill: 'trace', keywords: ['trace', 'root cause', 'root-cause'] },
18
+ { skill: 'ralplan', keywords: ['ralplan'] },
19
+ { skill: 'ralph', keywords: ['ralph'] },
20
+ { skill: 'team', keywords: ['team mode', 'fan out', 'fan-out'] },
21
+ { skill: 'cancel', keywords: ['cancelomm', 'cancel omm'] },
22
+ ]);
23
+
24
+ /**
25
+ * Word-boundary match so "ralph" hits but "ralphie" and "ethnography" do not.
26
+ * Keywords containing spaces are matched as phrases.
27
+ *
28
+ * `/`, `.` and `_` count as word characters here, not boundaries, so a file path
29
+ * like `lib/ralph.js` or `src/trace_test.py` does not read as a request to run the
30
+ * skill. Someone naming a path is talking about code, not asking for a pipeline.
31
+ * Explicit `/ralph` invocations are handled separately by matchesSlashCommand.
32
+ *
33
+ * @param {string} prompt
34
+ * @param {string} keyword
35
+ * @returns {boolean}
36
+ */
37
+ function matchesKeyword(prompt, keyword) {
38
+ const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
39
+ return new RegExp(`(^|[^a-z0-9._/-])${escaped}([^a-z0-9._/-]|$)`, 'i').test(prompt);
40
+ }
41
+
42
+ /**
43
+ * Matches an explicit slash invocation such as `/cancel` or `/ralph`.
44
+ *
45
+ * This is the high-precision path, and it is the ONLY way some skills can be
46
+ * routed. `cancel` is the clearest case: the bare word is far too common in
47
+ * ordinary English ("cancel that subscription", "how do I cancel a promise") to
48
+ * route on, and cancellation discards in-flight state, so a false positive is
49
+ * expensive. Requiring the slash keeps the routing safe without dropping the
50
+ * skill from the table.
51
+ *
52
+ * @param {string} prompt
53
+ * @param {string} skill
54
+ * @returns {boolean}
55
+ */
56
+ function matchesSlashCommand(prompt, skill) {
57
+ const escaped = skill.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
58
+ return new RegExp(`(^|\\s)/${escaped}([^a-z0-9-]|$)`, 'i').test(prompt);
59
+ }
60
+
61
+ /**
62
+ * Returns the skill ids suggested by a prompt, in ROUTES order, without duplicates.
63
+ *
64
+ * @param {string} prompt
65
+ * @returns {string[]}
66
+ */
67
+ export function routePrompt(prompt) {
68
+ if (typeof prompt !== 'string' || prompt.trim() === '') return [];
69
+
70
+ const hits = [];
71
+ for (const { skill, keywords } of ROUTES) {
72
+ const matched =
73
+ matchesSlashCommand(prompt, skill) ||
74
+ keywords.some((keyword) => matchesKeyword(prompt, keyword));
75
+ if (matched) hits.push(skill);
76
+ }
77
+ return hits;
78
+ }
79
+
80
+ /**
81
+ * Renders the additional context emitted for matched skills, or null when nothing
82
+ * matched. Returning null (rather than an empty string) keeps the caller honest
83
+ * about the "stay silent" path.
84
+ *
85
+ * @param {string[]} skills
86
+ * @returns {string | null}
87
+ */
88
+ export function renderRoutingContext(skills) {
89
+ if (skills.length === 0) return null;
90
+
91
+ const lines = skills.map((skill) => ` - /${skill}`);
92
+ return [
93
+ 'oh-my-musecode: this prompt mentions a skill that is available in this workspace.',
94
+ 'muse skills are invoke-only, so nothing has been run. Suggested:',
95
+ ...lines,
96
+ '',
97
+ 'Invoke one explicitly to load its instructions for that turn.',
98
+ ].join('\n');
99
+ }
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SessionStart hook: make sure the .omm/ state root exists.
4
+ *
5
+ * State lives in .omm/ and never in .agents/ or .muse/: muse protects both, so a
6
+ * mediated write there is held for human review and a shell write fails read-only
7
+ * at the sandbox. This hook only ever creates .omm/, so it takes no untrusted path
8
+ * input and needs no path-policy check.
9
+ */
10
+
11
+ import { existsSync, mkdirSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { readPayload, workspaceRootFrom, emitContext } from './lib.mjs';
14
+
15
+ const payload = await readPayload();
16
+ const stateRoot = join(workspaceRootFrom(payload), '.omm');
17
+
18
+ let created = false;
19
+ if (!existsSync(stateRoot)) {
20
+ try {
21
+ mkdirSync(join(stateRoot, 'state'), { recursive: true });
22
+ created = true;
23
+ } catch (err) {
24
+ // A session that cannot create state is still a usable session; say so and
25
+ // carry on rather than failing the user's startup.
26
+ emitContext(`oh-my-musecode: could not create ${stateRoot} (${err.message}).`);
27
+ }
28
+ }
29
+
30
+ emitContext(
31
+ created
32
+ ? `oh-my-musecode: initialised state root at ${stateRoot}.`
33
+ : null,
34
+ );
package/hooks/stop.mjs ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Stop hook: hold the verification gate open while a ralph run is active.
4
+ *
5
+ * oh-my-claudecode's ralph must not declare completion without reviewer sign-off.
6
+ * Skill bodies alone cannot enforce that here, because a skill only shapes the one
7
+ * turn it was invoked on — by the time the model is stopping, the ralph skill body
8
+ * may be many turns behind it. The Stop hook is the only surface that sees every
9
+ * end-of-turn, so it is where the reminder belongs.
10
+ *
11
+ * It reminds; it does not block.
12
+ */
13
+
14
+ import { readFileSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+ import { readPayload, workspaceRootFrom, emitContext } from './lib.mjs';
17
+
18
+ const payload = await readPayload();
19
+ const statePath = join(workspaceRootFrom(payload), '.omm', 'state', 'ralph-state.json');
20
+
21
+ /** @returns {Record<string, any> | null} */
22
+ function readRalphState() {
23
+ try {
24
+ const parsed = JSON.parse(readFileSync(statePath, 'utf8'));
25
+ return parsed && typeof parsed === 'object' ? parsed : null;
26
+ } catch {
27
+ // Absent or unreadable state means no active run to gate.
28
+ return null;
29
+ }
30
+ }
31
+
32
+ const state = readRalphState();
33
+
34
+ if (!state?.active) {
35
+ emitContext(null);
36
+ }
37
+
38
+ const reviewer = state.critic_mode ?? 'architect';
39
+ const story = state.current_story ? ` (current story: ${state.current_story})` : '';
40
+
41
+ emitContext(
42
+ [
43
+ `oh-my-musecode: a ralph run is still active${story}.`,
44
+ '',
45
+ 'Before claiming completion, confirm:',
46
+ ' - every prd.json story has passes: true against its own acceptance criteria',
47
+ ` - the ${reviewer} reviewer has approved this run`,
48
+ ' - a fresh build/test run was read, not assumed',
49
+ '',
50
+ 'If the run is genuinely finished, invoke /cancel to clear state.',
51
+ ].join('\n'),
52
+ );
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * UserPromptSubmit hook: suggest oh-my-musecode skills a prompt names.
4
+ *
5
+ * Restores oh-my-claudecode's keyword routing, which cannot come from frontmatter
6
+ * here — muse's skill frontmatter profile has no `triggers` field, and skills are
7
+ * invoke-only by design. The hook therefore surfaces the suggestion and stops.
8
+ */
9
+
10
+ import { readPayload, emitContext } from './lib.mjs';
11
+ import { routePrompt, renderRoutingContext } from './routing.mjs';
12
+
13
+ const payload = await readPayload();
14
+ const prompt = payload.prompt ?? payload.user_prompt ?? '';
15
+
16
+ emitContext(renderRoutingContext(routePrompt(String(prompt))));
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@siddicky/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.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json",
12
+ "test": "npm run build && node --test \"test/**/*.test.mjs\"",
13
+ "lint": "node scripts/verify-manifest.mjs && tsc -p tsconfig.json --noEmit",
14
+ "verify:manifest": "node scripts/verify-manifest.mjs",
15
+ "verify:skills": "node scripts/verify-skills.mjs",
16
+ "install:muse": "node scripts/install.mjs"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.0.0",
20
+ "typescript": "^5.6.0"
21
+ },
22
+ "dependencies": {
23
+ "@modelcontextprotocol/sdk": "^1.30.0"
24
+ },
25
+ "author": "Abdullah Siddique",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/siddicky/oh-my-musecode.git"
29
+ },
30
+ "keywords": [
31
+ "muse",
32
+ "muse-code",
33
+ "agent",
34
+ "harness",
35
+ "skills",
36
+ "orchestration"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "files": [
42
+ "dist/",
43
+ "skills/",
44
+ "personas/",
45
+ "hooks/",
46
+ "scripts/",
47
+ "docs/",
48
+ ".muse-plugin/",
49
+ ".claude-plugin/",
50
+ "README.md",
51
+ "LICENSE"
52
+ ],
53
+ "bin": {
54
+ "oh-my-musecode": "scripts/install.mjs"
55
+ },
56
+ "prepublishOnly": "npm run build && npm test"
57
+ }
@@ -0,0 +1,27 @@
1
+ You are an architect. You give strategic technical advice on structure, trade-offs, and
2
+ long-term consequences, and you never touch the code yourself.
3
+
4
+ ## How you work
5
+
6
+ - Read broadly before opining. Understand the existing shape of the system before you
7
+ propose changing it.
8
+ - Reason in trade-offs, not verdicts. For any nontrivial recommendation, name what it
9
+ costs as well as what it buys, and say what you'd need to see to be more sure.
10
+ - Weigh options against the system's actual constraints — its scale, its team, its
11
+ failure modes — not against an abstract ideal of correctness.
12
+ - Flag the decisions that are expensive to reverse. A choice that can be undone in an
13
+ afternoon deserves less of your attention than one that gets baked into everything
14
+ built on top of it.
15
+ - Be direct about risk. If a proposed direction is likely to cause pain later, say so
16
+ plainly and say when that pain would show up.
17
+ - Give a recommendation, not just an inventory of possibilities, when asked to decide
18
+ between options.
19
+
20
+ ## What you do not do
21
+
22
+ - You do not write or edit code. You do not run commands that change anything. Your
23
+ output is analysis and recommendation, nothing else.
24
+ - You do not rubber-stamp a plan to be agreeable. Advisory means honest, not compliant.
25
+ - You do not chase implementation details that don't change the architectural call —
26
+ that level belongs to whoever builds it.
27
+ - You do not pretend certainty you don't have.
@@ -0,0 +1,30 @@
1
+ You are a code reviewer. You read a diff and find defects worth fixing before it ships,
2
+ each one rated by how much it matters.
3
+
4
+ ## How you work
5
+
6
+ - Read the diff in the context of the surrounding code, not in isolation — a change
7
+ that's fine on its own can still be wrong for the file it lands in.
8
+ - Hunt specifically for logic defects: wrong conditionals, off-by-one errors, unhandled
9
+ edge cases, state mutated where it shouldn't be, error paths that swallow failures.
10
+ - Check structure too: does this violate a single-responsibility boundary that already
11
+ existed, does it duplicate something nearby, does it introduce a dependency that
12
+ didn't need to exist.
13
+ - Rate every finding by severity — a crash-causing bug and a naming nitpick are never
14
+ reported with equal weight. Say plainly which findings block merging and which are
15
+ optional polish.
16
+ - Give a fix, not just a complaint, when the fix is obvious. When it isn't, describe the
17
+ problem precisely enough that someone else could find the fix.
18
+ - Note real risk (performance cliff, security gap, silent data loss) even when it's
19
+ outside the literal lines changed, if the diff caused or exposed it.
20
+
21
+ ## What you do not do
22
+
23
+ - You do not apply the fixes yourself unless explicitly asked to — review and repair
24
+ are different passes.
25
+ - You do not review the plan or the intent behind the change; you review the diff as
26
+ written.
27
+ - You do not flag every stylistic preference as a defect. Style comments are labeled as
28
+ such, separate from correctness findings.
29
+ - You do not wave through a diff because it's small or because the author is under
30
+ time pressure.