@serranolabs.io/munchkins 0.1.1 → 0.1.3

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.
@@ -1,36 +1,207 @@
1
- import { cpSync, existsSync, mkdirSync, readdirSync } from "node:fs";
1
+ import { cpSync, type Dirent, existsSync, mkdirSync, readdirSync, realpathSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
- const SKILLS_SRC = resolve(dirname(fileURLToPath(import.meta.url)), "../skills");
5
+ const PACKAGE_NAME = "@serranolabs.io/munchkins";
6
+ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
7
+
8
+ interface SkillSource {
9
+ pkgName: string;
10
+ skillsDir: string;
11
+ }
12
+
13
+ interface PlannedInstall {
14
+ slug: string;
15
+ fromDir: string;
16
+ }
17
+
18
+ interface InstallPlan {
19
+ entries: PlannedInstall[];
20
+ warnings: string[];
21
+ }
22
+
23
+ export interface RunSkillsInstallOptions {
24
+ cwd: string;
25
+ target: string;
26
+ packageRoot: string | null;
27
+ }
6
28
 
7
29
  export function runSkillsInstall(argv: string[]): void {
8
- const target = resolveTarget(argv);
9
- if (!existsSync(SKILLS_SRC)) {
10
- console.error(`✖ no skills bundled at ${SKILLS_SRC}`);
30
+ _runSkillsInstall({
31
+ cwd: process.cwd(),
32
+ target: _resolveTarget(argv, process.cwd()),
33
+ packageRoot: PACKAGE_ROOT,
34
+ });
35
+ }
36
+
37
+ export function _runSkillsInstall(opts: RunSkillsInstallOptions): void {
38
+ const sources = _discoverSources(opts.cwd, opts.packageRoot);
39
+ const plan = _buildInstallPlan(sources);
40
+
41
+ if (plan.entries.length === 0) {
42
+ console.error("✖ no skills found in any installed package");
11
43
  process.exit(1);
12
44
  }
13
45
 
14
- const skills = readdirSync(SKILLS_SRC, { withFileTypes: true })
15
- .filter((e) => e.isDirectory())
16
- .map((e) => e.name);
46
+ for (const warning of plan.warnings) console.log(warning);
17
47
 
18
- if (skills.length === 0) {
19
- console.error("✖ no skills to install");
20
- process.exit(1);
48
+ mkdirSync(opts.target, { recursive: true });
49
+ const installed: string[] = [];
50
+ const kept: string[] = [];
51
+ for (const entry of plan.entries) {
52
+ const to = join(opts.target, entry.slug);
53
+ if (existsSync(join(to, "SKILL.md"))) {
54
+ kept.push(entry.slug);
55
+ continue;
56
+ }
57
+ cpSync(entry.fromDir, to, { recursive: true, dereference: true });
58
+ installed.push(entry.slug);
21
59
  }
22
60
 
23
- mkdirSync(target, { recursive: true });
24
- for (const name of skills) {
25
- const from = join(SKILLS_SRC, name);
26
- const to = join(target, name);
27
- cpSync(from, to, { recursive: true, dereference: true, force: true });
28
- console.log(`✓ ${name} -> ${to}`);
61
+ console.log(_formatSummaryLine("installed", `${installed.length} new`, installed));
62
+ if (kept.length > 0) {
63
+ console.log(_formatSummaryLine("kept (already present)", String(kept.length), kept));
29
64
  }
30
65
  }
31
66
 
32
- function resolveTarget(argv: string[]): string {
67
+ export function _resolveTarget(argv: string[], cwd: string): string {
33
68
  const flagIdx = argv.findIndex((a) => a === "--dest" || a === "-d");
34
69
  if (flagIdx !== -1 && argv[flagIdx + 1]) return resolve(argv[flagIdx + 1]);
35
- return resolve(process.cwd(), ".claude/skills");
70
+ return resolve(cwd, ".claude/skills");
71
+ }
72
+
73
+ export function _discoverSources(cwd: string, packageRoot: string | null): SkillSource[] {
74
+ const seen = new Set<string>();
75
+ const sources: SkillSource[] = [];
76
+
77
+ if (packageRoot) {
78
+ const ownSkills = join(packageRoot, "skills");
79
+ if (_skillsDirHasAnySkill(ownSkills)) {
80
+ seen.add(_safeRealpath(ownSkills));
81
+ sources.push({ pkgName: PACKAGE_NAME, skillsDir: ownSkills });
82
+ }
83
+ }
84
+
85
+ const nodeModulesDir = _findNodeModules(cwd);
86
+ if (!nodeModulesDir) return sources;
87
+
88
+ const fromNodeModules = _scanNodeModules(nodeModulesDir);
89
+ fromNodeModules.sort(_compareSourcesByPriority);
90
+ for (const src of fromNodeModules) {
91
+ const real = _safeRealpath(src.skillsDir);
92
+ if (seen.has(real)) continue;
93
+ seen.add(real);
94
+ sources.push(src);
95
+ }
96
+ return sources;
97
+ }
98
+
99
+ function _compareSourcesByPriority(a: SkillSource, b: SkillSource): number {
100
+ if (a.pkgName === PACKAGE_NAME && b.pkgName !== PACKAGE_NAME) return -1;
101
+ if (b.pkgName === PACKAGE_NAME && a.pkgName !== PACKAGE_NAME) return 1;
102
+ if (a.pkgName < b.pkgName) return -1;
103
+ if (a.pkgName > b.pkgName) return 1;
104
+ return 0;
105
+ }
106
+
107
+ function _findNodeModules(start: string): string | null {
108
+ let dir = resolve(start);
109
+ while (true) {
110
+ const candidate = join(dir, "node_modules");
111
+ if (existsSync(candidate)) return candidate;
112
+ const parent = dirname(dir);
113
+ if (parent === dir) return null;
114
+ dir = parent;
115
+ }
116
+ }
117
+
118
+ function _scanNodeModules(nodeModulesDir: string): SkillSource[] {
119
+ const out: SkillSource[] = [];
120
+ const entries = _readDirSafe(nodeModulesDir);
121
+ for (const entry of entries) {
122
+ if (entry.name.startsWith(".")) continue;
123
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
124
+ const entryPath = join(nodeModulesDir, entry.name);
125
+ if (entry.name.startsWith("@")) {
126
+ const scopedEntries = _readDirSafe(entryPath);
127
+ for (const scoped of scopedEntries) {
128
+ if (scoped.name.startsWith(".")) continue;
129
+ if (!scoped.isDirectory() && !scoped.isSymbolicLink()) continue;
130
+ _maybeAddPackage(out, `${entry.name}/${scoped.name}`, join(entryPath, scoped.name));
131
+ }
132
+ } else {
133
+ _maybeAddPackage(out, entry.name, entryPath);
134
+ }
135
+ }
136
+ return out;
137
+ }
138
+
139
+ function _maybeAddPackage(out: SkillSource[], pkgName: string, pkgDir: string): void {
140
+ const skillsDir = join(pkgDir, "skills");
141
+ if (_skillsDirHasAnySkill(skillsDir)) {
142
+ out.push({ pkgName, skillsDir });
143
+ }
144
+ }
145
+
146
+ function _skillsDirHasAnySkill(skillsDir: string): boolean {
147
+ if (!existsSync(skillsDir)) return false;
148
+ for (const entry of _readDirSafe(skillsDir)) {
149
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
150
+ if (existsSync(join(skillsDir, entry.name, "SKILL.md"))) return true;
151
+ }
152
+ return false;
153
+ }
154
+
155
+ function _readDirSafe(dir: string): Dirent[] {
156
+ try {
157
+ return readdirSync(dir, { withFileTypes: true });
158
+ } catch {
159
+ return [];
160
+ }
161
+ }
162
+
163
+ function _safeRealpath(p: string): string {
164
+ try {
165
+ return realpathSync(p);
166
+ } catch {
167
+ return p;
168
+ }
169
+ }
170
+
171
+ function _buildInstallPlan(sources: SkillSource[]): InstallPlan {
172
+ interface SlugInfo {
173
+ fromDir: string;
174
+ pkgs: string[];
175
+ }
176
+ const slugMap = new Map<string, SlugInfo>();
177
+ for (const src of sources) {
178
+ for (const entry of _readDirSafe(src.skillsDir)) {
179
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
180
+ const slug = entry.name;
181
+ if (!existsSync(join(src.skillsDir, slug, "SKILL.md"))) continue;
182
+ const existing = slugMap.get(slug);
183
+ if (existing) {
184
+ existing.pkgs.push(src.pkgName);
185
+ } else {
186
+ slugMap.set(slug, { fromDir: join(src.skillsDir, slug), pkgs: [src.pkgName] });
187
+ }
188
+ }
189
+ }
190
+
191
+ const warnings: string[] = [];
192
+ const entries: PlannedInstall[] = [];
193
+ for (const [slug, info] of slugMap) {
194
+ if (info.pkgs.length > 1) {
195
+ warnings.push(
196
+ `⚠ slug collision: ${slug} shipped by ${info.pkgs.join(", ")} — first wins; remove from one bundle to disambiguate`,
197
+ );
198
+ }
199
+ entries.push({ slug, fromDir: info.fromDir });
200
+ }
201
+ return { entries, warnings };
202
+ }
203
+
204
+ function _formatSummaryLine(label: string, count: string, names: string[]): string {
205
+ const suffix = names.length > 0 ? ` (${names.join(", ")})` : "";
206
+ return `${label}: ${count}${suffix}`;
36
207
  }
@@ -1,343 +0,0 @@
1
- ---
2
- name: new-munchkin
3
- description: Author or revise a default agent inside a repo that consumes @serranolabs.io/munchkins. Use when the user wants to scaffold a NEW munchkin agent OR edit an existing one — signaled by phrases like "new munchkin", "add a default agent", "scaffold a munchkin agent", "design an agent for this repo", "edit the X munchkin", "tweak the X agent's prompt", "change X's archetype", "demote X to a single-step agent". Do NOT use to run an existing agent (use launch-munchkin) or to create a Claude Code skill (use skill-creator).
4
- ---
5
-
6
- # New Munchkin
7
-
8
- Walks the user through designing a new default agent — or revising an existing one — for a host repo that consumes `@serranolabs.io/munchkins`.
9
-
10
- - **Create mode** outputs: a fully wired `agent.ts`, a drafted `prompts/<name>.md`, the side-effect import, and an updated AGENTS.md row.
11
- - **Edit mode** outputs: an updated `agent.ts` (if archetype changes), an updated `prompts/<name>.md` (if prompt changes), and a refreshed AGENTS.md row (if description changes).
12
-
13
- ## Operating principles
14
-
15
- 1. **Concise > more prose.** Drafts are short. This skill md is short. Generated files mirror existing terse voice.
16
- 2. **Discover, never hardcode.** Paths, languages, gate commands, existing agents — all introspected from the host repo at runtime. Never assume `packages/munchkins/agents/`, never assume Bun/TS, never assume agent names like `bug-fix`/`feat-small`/`refactor` exist.
17
- 3. **Reuse over invention.** Generated files lean on the host repo's existing shared presets and shared prompt paths. The new or revised agent should look like a sibling of what's already there.
18
-
19
- ## Posture
20
-
21
- This is a **grill-me-style sequential interview**. Walk the user through the agenda one fork at a time, in this exact format per fork:
22
-
23
- ```
24
- ## <fork-id> — <fork title>
25
-
26
- **Summary.** <one short paragraph: why this decision exists, in plain language>
27
-
28
- <fixed-width tradeoff table: columns = options, rows = consequences, 4–7 rows>
29
-
30
- <one minimal code block per option>
31
-
32
- **My pick: <option>.** <one-sentence reason.>
33
-
34
- **Your call?**
35
- ```
36
-
37
- One fork per message. Wait for the user's reply before moving on. Do not batch.
38
-
39
- ## Workflow
40
-
41
- ### 0. Pre-flight
42
-
43
- Confirm the cwd is a host repo that consumes munchkins. Quick checks (any positive signal is enough):
44
-
45
- - `@serranolabs.io/munchkins` in `package.json` deps, OR
46
- - An existing `<name>-agent.ts` file using `AgentBuilder` somewhere under the repo, OR
47
- - The user explicitly invoked `/new-munchkin` from inside what they say is a munchkins-using repo.
48
-
49
- If none of these hold, tell the user "new-munchkin runs inside a repo that consumes @serranolabs.io/munchkins" and stop.
50
-
51
- ### 1. Pre-grill discovery
52
-
53
- Do all of the following **before** asking the first fork. Every downstream step depends on this state. Both create-mode and edit-mode use this discovery.
54
-
55
- #### 1a. Locate the agents directory
56
-
57
- Scan for the convention: a directory containing one or more `<name>-agent.ts` files plus a `prompts/` subdirectory, with a sibling `_shared/` (or analogous shared module exporting things like `DEFAULT_CHECKS`, `defaultFixer`, `GUIDELINES_PATH`).
58
-
59
- ```bash
60
- # Typical search; adapt as needed
61
- find . -path ./node_modules -prune -o -type f -name '*-agent.ts' -print
62
- ```
63
-
64
- If exactly one such directory exists, use it. If multiple exist, ask the user which to target. If none exist, ask the user for the path. **Never default to `packages/munchkins/agents/` without confirming it exists.**
65
-
66
- #### 1b. Enumerate existing agents
67
-
68
- For each `<name>-agent.ts` in the agents directory, read the file and extract:
69
-
70
- - **Slug** — the first arg to `new AgentBuilder(...)`.
71
- - **Description** — the second arg to `new AgentBuilder(...)`.
72
- - **Step composition** — the chain of `.add(...)` calls and which shared prompt paths they reuse (e.g., `REFACTORER_PATH`, `TEST_WRITER_PATH`).
73
- - **CLI options** — any `withUserMessageFromOption(...)` schemas declared.
74
- - **Prompt path** — the per-agent prompt md (typically `prompts/<name>.md` under the agent dir).
75
-
76
- This list drives create-mode (distinctness, archetype menu, slug collision check) and edit-mode (target picker, current-state display, conflict checks).
77
-
78
- #### 1c. Discover gate commands from CI
79
-
80
- Look for CI workflow files in this order; stop at the first match:
81
-
82
- 1. `.github/workflows/*.yml`
83
- 2. `.gitlab-ci.yml`
84
- 3. `.circleci/config.yml`
85
- 4. `Jenkinsfile`
86
-
87
- Parse the jobs that run on PR/push triggers. Extract the actual lint / typecheck / test commands (`run:` lines under steps for GitHub Actions; `script:` for GitLab; etc.). These commands are what the agent's deterministic gate will run, and what verify (N7/E3) runs locally.
88
-
89
- If no CI workflow exists, fall back to inspecting the host repo's package manifest (`package.json` `scripts`, `Makefile`, `pyproject.toml` `[tool.poetry.scripts]`, `tox.ini`, etc.) and ask the user to confirm.
90
-
91
- #### 1d. Read lint / format / typecheck configs
92
-
93
- Read whichever apply so generated files comply on first try:
94
-
95
- - TS: `biome.json`, `.eslintrc*`, `tsconfig.json`
96
- - Python: `pyproject.toml` `[tool.ruff]` / `[tool.black]`, `ruff.toml`, `mypy.ini`
97
- - Go: `.golangci.yml`, `go.mod`
98
-
99
- #### 1e. Identify primary language
100
-
101
- Determined by which manifest exists (`package.json` → TS/JS, `pyproject.toml` → Python, `go.mod` → Go). Used to tailor mandate style guidelines. Polyglot repos: pick the language of the agents directory (likely TS — munchkins-core is TS).
102
-
103
- #### 1f. Detect package manager
104
-
105
- From lockfile presence: `bun.lock` → bun, `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `package-lock.json` → npm, `poetry.lock` → poetry, `uv.lock` → uv. Use this PM in any commands surfaced to the user.
106
-
107
- ### 2. Mode pick (M0)
108
-
109
- Open the grill with a **mode** fork. This is always the first fork:
110
-
111
- ```
112
- ## M0 — Mode
113
-
114
- **Summary.** Are we creating a new munchkin or editing an existing one?
115
-
116
- │ A: Create new │ B: Edit existing
117
- ─────────┼─────────────────────┼─────────────────────────
118
- output │ new agent files + │ updated agent.ts and/or
119
- │ index.ts edit + │ prompts/<name>.md +
120
- │ AGENTS.md row │ refreshed AGENTS.md row
121
- covers │ first time scaffold │ archetype demote/promote,
122
- │ │ prompt revision,
123
- │ │ description tweak
124
- agenda │ N1–N7 (7 forks) │ E0–E3 (4 forks)
125
-
126
- **My pick:** infer from the user's trigger phrase. "edit", "tweak", "change", "demote", "promote", or naming an existing slug → B. Otherwise → A.
127
-
128
- **Your call?**
129
- ```
130
-
131
- Branch on the answer:
132
- - **A → Create workflow** (§3)
133
- - **B → Edit workflow** (§4)
134
-
135
- ### 3. Create workflow (M0=A)
136
-
137
- Run forks N1–N7 in order. Wait for user reply between each.
138
-
139
- #### N1 — Purpose
140
-
141
- Open question, no tradeoff table:
142
-
143
- > In one sentence, what does the new agent do?
144
-
145
- After the user answers, restate it back: `Restating: <one sentence>. Confirm or refine.` Wait for confirmation before proceeding.
146
-
147
- #### N2 — Distinctness
148
-
149
- Pressure-test the proposed agent against each existing agent enumerated in 1b. Build a table dynamically:
150
-
151
- ```
152
- │ existing-agent-A │ existing-agent-B │ ... │ NEW
153
- ────────────────────────┼──────────────────┼──────────────────┼─────┼──────
154
- What it does │ <desc-A> │ <desc-B> │ ... │ <new>
155
- How NEW differs │ <delta-A> │ <delta-B> │ ... │ —
156
- ```
157
-
158
- Verdict line: `build` (genuinely distinct), `refine` (overlaps with X — narrow purpose first), or `abandon` (already covered by X). State My pick. Wait.
159
-
160
- If verdict is `refine` or `abandon`, loop back to N1.
161
-
162
- #### N3 — Archetype
163
-
164
- Derive the menu from patterns observed in 1b. In a typical munchkins consumer:
165
-
166
- 1. **Single-step** — one custom prompt step + deterministic gate. (e.g., `refactor`-style)
167
- 2. **Main + refactor pass** — custom step + reuses shared `REFACTORER_PATH` step + gate. (e.g., `bug-fix`-style)
168
- 3. **Main + refactor + tests** — custom + `REFACTORER_PATH` + `TEST_WRITER_PATH` + gate. (e.g., `feat-small`-style)
169
-
170
- If the host repo has agents that don't fit these three, append additional archetypes from observed compositions. Present in a tradeoff table; user picks by number.
171
-
172
- #### N4 — Name
173
-
174
- Propose a kebab-case slug derived from the purpose (short, verb-noun where possible: `release-cut`, `dep-bump`, `doc-update`).
175
-
176
- Collision check: if the proposed slug matches any slug from 1b, propose alternatives. Validate kebab-case: `^[a-z][a-z0-9-]*[a-z0-9]$`.
177
-
178
- Light tradeoff: show the proposed slug + 1–2 alternatives. User confirms or overrides.
179
-
180
- #### N5 — CLI options (conditional — usually skipped)
181
-
182
- **Skip by default.** The agent uses `--user-message` (path to markdown OR inline text), matching the existing pattern.
183
-
184
- **Fire N5 only if** (a) the purpose statement implied a per-target flag, or (b) the user explicitly asks for custom flags.
185
-
186
- When fired:
187
- - Propose flag names + descriptions.
188
- - Conflict-check each proposed flag against (i) every option declared by agents in 1b, (ii) framework-reserved names (`--cli`, `--user-message`, `--verbose`, `--thinking`, `--dry-run`).
189
- - If any conflict, surface it and ask for a non-colliding name.
190
-
191
- #### N6 — Prompt content (one-shot draft)
192
-
193
- Draft the **full** `prompts/<name>.md` body in one shot. The user accepts with `y` or pastes edits.
194
-
195
- Template:
196
-
197
- ```md
198
- # <name> subagent
199
-
200
- You are the <name> subagent. The user prompt contains <purpose-tail>.
201
-
202
- ## Mandate
203
-
204
- 1. <read step>
205
- 2. <locate / inspect step — with style guideline woven in if relevant>
206
- 3. <act step — with style guideline woven in if relevant>
207
- 4. Commit on `$BRANCH` with a message that names <what>.
208
- 5. Stop.
209
-
210
- ## Out of scope for this step
211
-
212
- - <bullet 1, drafted>
213
- - <bullet 2, drafted>
214
- - <bullet 3, drafted>
215
-
216
- ## Output
217
-
218
- Code changes committed to `$BRANCH`. No JSON, no summary block — the deterministic loop and the human reviewer read the diff directly.
219
- ```
220
-
221
- **Style guidelines woven INLINE into mandate bullets (not a separate section).** Tailor to purpose AND the host repo's primary language (from 1e):
222
-
223
- - TS refactor agent: "respecting tsconfig strict mode and existing DRY conventions"
224
- - Python refactor agent: "respecting type hints and PEP 8 layout"
225
- - Go refactor agent: "respecting gofmt and idiomatic error handling"
226
- - Architect agent (any language): "favoring concrete over speculative abstraction; the simplest viable shape wins"
227
- - Docs agent: "matching the existing voice and section structure of nearby pages"
228
-
229
- Out-of-scope bullets follow the typical pattern: "no scope creep into adjacent files", "no test changes unless directly required", "no documentation or planning artifact updates".
230
-
231
- #### N7 — Verify
232
-
233
- After the user accepts N6, write all files (see §5), then run the gate commands discovered in 1c locally. Surface failures with the specific rule that fired.
234
-
235
- ### 4. Edit workflow (M0=B)
236
-
237
- Run forks E0–E3. The fork format is the same; some forks are no-ops if the user wants to keep current state.
238
-
239
- #### E0 — Target
240
-
241
- If the user named the agent in their trigger phrase, skip the menu and use that target. Otherwise present a menu of slugs from 1b. Validate the chosen slug exists.
242
-
243
- After picking, **show the current state** of the agent in a single block:
244
-
245
- ```
246
- agent: <slug>
247
- description: <description from AgentBuilder>
248
- archetype: <inferred from step composition — "single-step" / "main + refactor" / "main + refactor + tests" / "custom: <description>">
249
- CLI options: <list from withUserMessageFromOption schemas>
250
- prompt md: <agents-dir>/<slug>/prompts/<slug>.md
251
- <inline preview, first ~10 lines if short, else file size>
252
- ```
253
-
254
- This frames every downstream fork.
255
-
256
- #### E1 — Archetype change
257
-
258
- Tradeoff table comparing current archetype against feasible alternatives:
259
-
260
- ```
261
- │ keep │ demote │ promote │ change
262
- ──────────────────┼───────────────────┼──────────────────────┼───────────────────────┼─────────
263
- new shape │ same step chain │ drop refactor or │ add refactor or │ rewrite
264
- │ │ test step │ test step │ from
265
- │ scratch
266
- agent.ts touched │ no │ yes — remove .add() │ yes — add .add() │ yes
267
- prompt md touched │ no │ no │ no │ no
268
- ```
269
-
270
- If `keep`, this fork is a no-op — proceed to E2.
271
-
272
- If demote/promote/change: rewrite agent.ts's `.add(...)` chain. Preserve existing custom step (the one that uses `prompts/<slug>.md`). Add or remove shared steps (`REFACTORER_PATH`, `TEST_WRITER_PATH`) at their conventional positions (refactor between main and tests; tests last).
273
-
274
- #### E2 — Prompt content
275
-
276
- Show the existing `prompts/<slug>.md` body. Draft a revised version honoring whatever the user said they wanted to change (if anything). Present in the same one-shot format as N6 — user `y` to accept the draft, or paste an edited version.
277
-
278
- If the user only wanted an archetype change (no prompt edit), this fork is a no-op — confirm with the user and skip.
279
-
280
- The style-guideline rules from N6 apply identically when revising. Do not lengthen the prompt body beyond its current size unless the user explicitly asks.
281
-
282
- #### E3 — Verify
283
-
284
- Same as N7. Write the changes, run the discovered gate commands locally, surface failures.
285
-
286
- ### 5. Output (after N7 / E3 confirms)
287
-
288
- All paths come from discovery, not hardcoded.
289
-
290
- #### Create-mode files
291
-
292
- 1. **`<agents-dir>/<name>/<name>-agent.ts`** — fully wired:
293
- - Imports from `@serranolabs.io/munchkins-core`: `AgentBuilder`, `Prompt`, `gitWorktreeSandbox`, `registry`.
294
- - Imports from the discovered shared presets module: `DEFAULT_CHECKS`, `defaultFixer`, `defaultSummaryWriter`, `GUIDELINES_PATH`, `getAgentPromptsDir`, plus the archetype-appropriate `REFACTORER_PATH` / `TEST_WRITER_PATH`.
295
- - Constructs `new AgentBuilder(name, description, gitWorktreeSandbox())`, chains `.add(new Prompt(GUIDELINES_PATH).withSystem(...).withUserMessageFromOption(...))` per archetype step, ends with `.addDeterministic([...DEFAULT_CHECKS], { loop: { maxIterations: 3, fixer: defaultFixer() } })` and `.summaryWriter(defaultSummaryWriter())`.
296
- - Calls `registry.register(builder)`. Exports `{ builder }`.
297
-
298
- 2. **`<agents-dir>/<name>/prompts/<name>.md`** — content from N6.
299
-
300
- 3. **`<bundle-package>/src/index.ts`** — append a side-effect import line:
301
- ```ts
302
- import "../agents/<name>/<name>-agent.js";
303
- ```
304
- Insert in alphabetical order with the existing side-effect imports if they're already alphabetized; otherwise append at the bottom of the import block.
305
-
306
- 4. **`AGENTS.md`** at host repo root (if present) — append a row to the `Running default agents` table:
307
- ```
308
- | <name> | <description string from AgentBuilder> |
309
- ```
310
-
311
- 5. **Stdout note**: `Mirror this row in README.md if it has an agent table.`
312
-
313
- #### Edit-mode files
314
-
315
- Edit-mode never adds files, never changes the slug, never edits `<bundle-package>/src/index.ts` (the side-effect import already exists).
316
-
317
- 1. **`<agents-dir>/<slug>/<slug>-agent.ts`** — only if E1 changed the archetype. Rewrite the `.add(...)` chain in place; leave imports, the `AgentBuilder(...)` constructor args, and the `.addDeterministic` / `.summaryWriter` tail as-is unless they need to change.
318
- 2. **`<agents-dir>/<slug>/prompts/<slug>.md`** — only if E2 produced a revised prompt.
319
- 3. **`AGENTS.md`** — update the existing row if the description changed; otherwise leave it.
320
-
321
- If the description changed, surface a one-line note: `Mirror this row update in README.md if it has an agent table.`
322
-
323
- ### 6. Hard rules
324
-
325
- - Never hardcode munchkins-monorepo paths or agent names. Always derive from discovery.
326
- - Cross-package imports go through `@serranolabs.io/*` package names. No relative paths across workspace boundaries.
327
- - A new agent.ts MUST `registry.register(builder)` AND be side-effect-imported from the bundle's entry — both are required for the agent to appear in the CLI.
328
- - Use the package manager detected in 1f. Do not switch package managers (no `npm install` in a Bun repo, no `pnpm` in a Bun repo).
329
- - Never skip pre-grill discovery. Every fork depends on it.
330
- - Do not draft a prompt body that exceeds the existing prompt md files in length. Match their terseness.
331
- - **Edit-mode never deletes an agent, never renames it, and never modifies `_shared/presets.ts`.** Those are out of scope for this skill — the user does them by hand.
332
-
333
- ## What this skill does NOT do
334
-
335
- - Does not create or modify shared prompt files (e.g., a new `REFACTORER_PATH` analog). The skill consumes existing shared prompts.
336
- - Does not modify `_shared/presets.ts`. Adding new presets is out of scope.
337
- - Does not run the agent end-to-end. Verify only runs the lint/typecheck/test gate. After create-mode, suggest a smoke test:
338
- ```
339
- Try it: <pm> run munchkins <name> --user-message="<short test brief>"
340
- ```
341
- - Does not update README.md. Surface the manual follow-up in the stdout note.
342
- - Does not commit changes. The user reviews the diff and commits on their own.
343
- - Does not delete or rename existing agents. Edit-mode is additive/revisionary only.