@long.dg/le-restaurant 0.1.1 → 0.2.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/dist/{chunk-SWHUS5EX.js → chunk-WRL4BRWX.js} +156 -11
- package/dist/chunk-WRL4BRWX.js.map +1 -0
- package/dist/{cli-BDBD_kfX.d.ts → cli-D4JjM2iZ.d.ts} +2 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +13 -5
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/chef-de-rang/SKILL.md +4 -1
- package/skills/maitre-d/SKILL.md +13 -4
- package/skills/mise-en-place/SKILL.md +11 -5
- package/skills/sous-chef/SKILL.md +4 -3
- package/dist/chunk-SWHUS5EX.js.map +0 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
|
|
5
|
+
import { resolve as resolve3 } from "path";
|
|
6
|
+
import { log, note } from "@clack/prompts";
|
|
4
7
|
import { Command, CommanderError } from "commander";
|
|
5
8
|
|
|
6
9
|
// src/adapters/claude.ts
|
|
@@ -16,31 +19,67 @@ var skillsDir = resolve(moduleDir, "..", "skills");
|
|
|
16
19
|
function skillSourcePath(name) {
|
|
17
20
|
return join(skillsDir, name, "SKILL.md");
|
|
18
21
|
}
|
|
19
|
-
function
|
|
20
|
-
const raw = readFileSync(skillSourcePath(name), "utf8");
|
|
22
|
+
function parseSkillContent(raw, name) {
|
|
21
23
|
const parsed = matter(raw);
|
|
22
24
|
const frontmatter = parsed.data;
|
|
25
|
+
let version;
|
|
26
|
+
if (typeof frontmatter.version === "string" && frontmatter.version.length > 0) {
|
|
27
|
+
version = frontmatter.version;
|
|
28
|
+
} else {
|
|
29
|
+
console.warn(
|
|
30
|
+
`[le-restaurant] skill "${name}" has no version in its SKILL.md frontmatter \u2014 falling back to "0.0.0". Add a semver version: field.`
|
|
31
|
+
);
|
|
32
|
+
version = "0.0.0";
|
|
33
|
+
}
|
|
23
34
|
return {
|
|
24
35
|
name: typeof frontmatter.name === "string" ? frontmatter.name : name,
|
|
25
36
|
description: typeof frontmatter.description === "string" ? frontmatter.description : "",
|
|
37
|
+
version,
|
|
26
38
|
frontmatter,
|
|
27
39
|
body: parsed.content.replace(/^\s+/, "")
|
|
28
40
|
};
|
|
29
41
|
}
|
|
42
|
+
function loadSkill(name) {
|
|
43
|
+
const raw = readFileSync(skillSourcePath(name), "utf8");
|
|
44
|
+
return parseSkillContent(raw, name);
|
|
45
|
+
}
|
|
30
46
|
function loadSkills() {
|
|
31
47
|
return readdirSync(skillsDir).filter((entry) => statSync(join(skillsDir, entry)).isDirectory()).map((dir) => loadSkill(dir)).sort((a, b) => a.name.localeCompare(b.name));
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
// src/adapters/claude.ts
|
|
51
|
+
var CHEF_AGENT_TOOLS = "Read, Write, Edit, Bash, Glob, Grep";
|
|
52
|
+
function renderChefAgentDefinition(chef) {
|
|
53
|
+
const frontmatter = [
|
|
54
|
+
"---",
|
|
55
|
+
"name: chef-de-rang",
|
|
56
|
+
`description: ${chef.description}`,
|
|
57
|
+
"model: sonnet",
|
|
58
|
+
`tools: ${CHEF_AGENT_TOOLS}`,
|
|
59
|
+
"---"
|
|
60
|
+
].join("\n");
|
|
61
|
+
return `${frontmatter}
|
|
62
|
+
|
|
63
|
+
${chef.body}`;
|
|
64
|
+
}
|
|
35
65
|
var claudeAdapter = {
|
|
36
66
|
id: "claude",
|
|
37
67
|
label: "Claude Code",
|
|
38
68
|
translate(skills, _ctx) {
|
|
39
|
-
|
|
69
|
+
const outputs = skills.map((skill) => ({
|
|
40
70
|
path: `.claude/skills/${skill.name}/SKILL.md`,
|
|
41
71
|
contents: readFileSync2(skillSourcePath(skill.name), "utf8"),
|
|
42
72
|
mode: "overwrite"
|
|
43
73
|
}));
|
|
74
|
+
const chef = skills.find((skill) => skill.name === "chef-de-rang");
|
|
75
|
+
if (chef) {
|
|
76
|
+
outputs.push({
|
|
77
|
+
path: ".claude/agents/chef-de-rang.md",
|
|
78
|
+
contents: renderChefAgentDefinition(chef),
|
|
79
|
+
mode: "overwrite"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return outputs;
|
|
44
83
|
}
|
|
45
84
|
};
|
|
46
85
|
|
|
@@ -72,17 +111,18 @@ function mergeManagedBlock(existing, block) {
|
|
|
72
111
|
|
|
73
112
|
// src/adapters/codex.ts
|
|
74
113
|
function renderSection(skill) {
|
|
75
|
-
return `## Skill: ${skill.name}
|
|
114
|
+
return `## Skill: ${skill.name} (v${skill.version})
|
|
76
115
|
|
|
77
116
|
${skill.description}
|
|
78
117
|
|
|
79
118
|
${skill.body.trimEnd()}`;
|
|
80
119
|
}
|
|
120
|
+
var MODEL_DISPATCH_NOTICE = "> **Note:** Model-aware dispatch (Sonnet/Opus selection) is a no-op on Codex \u2014 chef-de-rang runs on the session model. The HITL gate and difficulty tagging still apply.";
|
|
81
121
|
var codexAdapter = {
|
|
82
122
|
id: "codex",
|
|
83
123
|
label: "Codex",
|
|
84
124
|
translate(skills, _ctx) {
|
|
85
|
-
const inner = [MANAGED_NOTICE, ...skills.map(renderSection)].join("\n\n");
|
|
125
|
+
const inner = [MANAGED_NOTICE, MODEL_DISPATCH_NOTICE, ...skills.map(renderSection)].join("\n\n");
|
|
86
126
|
return [
|
|
87
127
|
{
|
|
88
128
|
path: "AGENTS.md",
|
|
@@ -100,12 +140,14 @@ function importPath(skill) {
|
|
|
100
140
|
}
|
|
101
141
|
function renderSkillFile(skill) {
|
|
102
142
|
return `${SKILL_FILE_NOTICE}
|
|
143
|
+
<!-- Version: ${skill.version} -->
|
|
103
144
|
|
|
104
145
|
${skill.description}
|
|
105
146
|
|
|
106
147
|
${skill.body.trimEnd()}
|
|
107
148
|
`;
|
|
108
149
|
}
|
|
150
|
+
var MODEL_DISPATCH_NOTICE2 = "> **Note:** Model-aware dispatch (Sonnet/Opus selection) is a no-op on Gemini \u2014 chef-de-rang runs on the session model. The HITL gate and difficulty tagging still apply.";
|
|
109
151
|
var geminiAdapter = {
|
|
110
152
|
id: "gemini",
|
|
111
153
|
label: "Gemini",
|
|
@@ -118,6 +160,8 @@ var geminiAdapter = {
|
|
|
118
160
|
const imports = skills.map((skill) => `@${importPath(skill)}`).join("\n");
|
|
119
161
|
const inner = `${MANAGED_NOTICE}
|
|
120
162
|
|
|
163
|
+
${MODEL_DISPATCH_NOTICE2}
|
|
164
|
+
|
|
121
165
|
${imports}`;
|
|
122
166
|
const geminiMd = {
|
|
123
167
|
path: "GEMINI.md",
|
|
@@ -128,6 +172,42 @@ ${imports}`;
|
|
|
128
172
|
}
|
|
129
173
|
};
|
|
130
174
|
|
|
175
|
+
// src/check.ts
|
|
176
|
+
function compareVersions(a, b) {
|
|
177
|
+
const parse = (v) => v.split(".").slice(0, 3).map((seg) => parseInt(seg, 10) || 0);
|
|
178
|
+
const [aMajor = 0, aMinor = 0, aPatch = 0] = parse(a);
|
|
179
|
+
const [bMajor = 0, bMinor = 0, bPatch = 0] = parse(b);
|
|
180
|
+
if (aMajor !== bMajor) return aMajor - bMajor;
|
|
181
|
+
if (aMinor !== bMinor) return aMinor - bMinor;
|
|
182
|
+
return aPatch - bPatch;
|
|
183
|
+
}
|
|
184
|
+
function checkSkills(manifest, bundled) {
|
|
185
|
+
return Object.entries(manifest.skills).map(([name, installedVersion]) => {
|
|
186
|
+
const bundledVersion = bundled[name];
|
|
187
|
+
if (bundledVersion === void 0) {
|
|
188
|
+
return { name, installedVersion, bundledVersion: void 0, status: "unknown" };
|
|
189
|
+
}
|
|
190
|
+
const cmp = compareVersions(installedVersion, bundledVersion);
|
|
191
|
+
const status = cmp < 0 ? "stale (local)" : "up-to-date";
|
|
192
|
+
return { name, installedVersion, bundledVersion, status };
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
async function fetchLatestNpmVersion(pkgName, fetcher = fetch) {
|
|
196
|
+
try {
|
|
197
|
+
const encoded = pkgName.replace("/", "%2F");
|
|
198
|
+
const url = `https://registry.npmjs.org/${encoded}/latest`;
|
|
199
|
+
const res = await fetcher(url);
|
|
200
|
+
if (!res.ok) return null;
|
|
201
|
+
const data = await res.json();
|
|
202
|
+
return typeof data.version === "string" ? data.version : null;
|
|
203
|
+
} catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function checkNpmFreshness(installedVersion, latestVersion) {
|
|
208
|
+
return compareVersions(installedVersion, latestVersion) < 0 ? "update available (npm)" : "up-to-date (npm)";
|
|
209
|
+
}
|
|
210
|
+
|
|
131
211
|
// src/summary.ts
|
|
132
212
|
function usageNotes(agentId) {
|
|
133
213
|
switch (agentId) {
|
|
@@ -143,7 +223,10 @@ function usageNotes(agentId) {
|
|
|
143
223
|
"le-restaurant managed markers (edit outside the markers freely).",
|
|
144
224
|
"Caveat: Codex has no skill system, so this guidance is ALWAYS-ON \u2014",
|
|
145
225
|
"there is no on-demand triggering. Every section is in context all the",
|
|
146
|
-
"time, unlike Claude Code's on-demand skills."
|
|
226
|
+
"time, unlike Claude Code's on-demand skills.",
|
|
227
|
+
"Note: Model-aware dispatch (Sonnet/Opus selection) is a no-op \u2014",
|
|
228
|
+
"chef-de-rang runs on the session model. The HITL gate and difficulty",
|
|
229
|
+
"tagging still apply."
|
|
147
230
|
];
|
|
148
231
|
case "gemini":
|
|
149
232
|
return [
|
|
@@ -151,7 +234,10 @@ function usageNotes(agentId) {
|
|
|
151
234
|
"GEMINI.md via @import lines inside le-restaurant managed markers.",
|
|
152
235
|
"Caveat: Gemini has no skill system, so this guidance is ALWAYS-ON \u2014",
|
|
153
236
|
"there is no on-demand triggering. Every imported skill is in context",
|
|
154
|
-
"all the time, unlike Claude Code's on-demand skills."
|
|
237
|
+
"all the time, unlike Claude Code's on-demand skills.",
|
|
238
|
+
"Note: Model-aware dispatch (Sonnet/Opus selection) is a no-op \u2014",
|
|
239
|
+
"chef-de-rang runs on the session model. The HITL gate and difficulty",
|
|
240
|
+
"tagging still apply."
|
|
155
241
|
];
|
|
156
242
|
}
|
|
157
243
|
}
|
|
@@ -163,8 +249,8 @@ function renderSummary(input) {
|
|
|
163
249
|
}
|
|
164
250
|
lines.push("");
|
|
165
251
|
lines.push("How to use:");
|
|
166
|
-
for (const
|
|
167
|
-
lines.push(` ${
|
|
252
|
+
for (const note2 of usageNotes(input.agentId)) {
|
|
253
|
+
lines.push(` ${note2}`);
|
|
168
254
|
}
|
|
169
255
|
return lines.join("\n");
|
|
170
256
|
}
|
|
@@ -195,6 +281,10 @@ function atomicWriteFile(absolute, contents) {
|
|
|
195
281
|
throw err;
|
|
196
282
|
}
|
|
197
283
|
}
|
|
284
|
+
function writeManifest(manifest, targetDir) {
|
|
285
|
+
const absolute = resolve2(targetDir, ".le-restaurant.json");
|
|
286
|
+
atomicWriteFile(absolute, JSON.stringify(manifest, null, 2) + "\n");
|
|
287
|
+
}
|
|
198
288
|
function writeOutputs(outputs, ctx) {
|
|
199
289
|
const results = [];
|
|
200
290
|
for (const output of outputs) {
|
|
@@ -230,7 +320,7 @@ function writeOutputs(outputs, ctx) {
|
|
|
230
320
|
// package.json
|
|
231
321
|
var package_default = {
|
|
232
322
|
name: "@long.dg/le-restaurant",
|
|
233
|
-
version: "0.
|
|
323
|
+
version: "0.2.0",
|
|
234
324
|
description: "Install the brigade of agent skills into your project, translated for your coding agent.",
|
|
235
325
|
type: "module",
|
|
236
326
|
publishConfig: {
|
|
@@ -331,6 +421,15 @@ function runInstall(req) {
|
|
|
331
421
|
};
|
|
332
422
|
const outputs = adapter.translate(req.skills, ctx);
|
|
333
423
|
const results = writeOutputs(outputs, ctx);
|
|
424
|
+
writeManifest(
|
|
425
|
+
{
|
|
426
|
+
package: VERSION,
|
|
427
|
+
agent: req.agentId,
|
|
428
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
429
|
+
skills: Object.fromEntries(req.skills.map((s) => [s.name, s.version]))
|
|
430
|
+
},
|
|
431
|
+
req.targetDir
|
|
432
|
+
);
|
|
334
433
|
return { adapter, results };
|
|
335
434
|
}
|
|
336
435
|
function resolveFlags(flags, targetDir, available) {
|
|
@@ -383,6 +482,52 @@ function buildProgram() {
|
|
|
383
482
|
console.log(`- ${skill.name}: ${skill.description}`);
|
|
384
483
|
}
|
|
385
484
|
});
|
|
485
|
+
program.command("check").description(
|
|
486
|
+
"Check installed skills for staleness against the bundled package versions."
|
|
487
|
+
).argument("[targetDir]", "project directory to check", ".").action(async (targetDir) => {
|
|
488
|
+
const manifestPath = resolve3(targetDir, ".le-restaurant.json");
|
|
489
|
+
if (!existsSync2(manifestPath)) {
|
|
490
|
+
note(
|
|
491
|
+
`No .le-restaurant.json found in ${resolve3(targetDir)}.
|
|
492
|
+
Run \`le-restaurant\` in this directory first.`,
|
|
493
|
+
"not installed here"
|
|
494
|
+
);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const manifest = JSON.parse(
|
|
498
|
+
readFileSync4(manifestPath, "utf8")
|
|
499
|
+
);
|
|
500
|
+
const bundled = Object.fromEntries(
|
|
501
|
+
loadSkills().map((s) => [s.name, s.version])
|
|
502
|
+
);
|
|
503
|
+
const results = checkSkills(manifest, bundled);
|
|
504
|
+
if (results.length === 0) {
|
|
505
|
+
note("No skills recorded in the manifest.", "check");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
for (const r of results) {
|
|
509
|
+
const label = `${r.name} (installed: ${r.installedVersion}, bundled: ${r.bundledVersion ?? "\u2014"})`;
|
|
510
|
+
if (r.status === "up-to-date") {
|
|
511
|
+
log.success(`up-to-date ${label}`);
|
|
512
|
+
} else if (r.status === "stale (local)") {
|
|
513
|
+
log.warn(`stale (local) ${label}`);
|
|
514
|
+
} else {
|
|
515
|
+
log.info(`unknown ${label}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const latestNpmVersion = await fetchLatestNpmVersion(package_default.name);
|
|
519
|
+
if (latestNpmVersion === null) {
|
|
520
|
+
log.warn("npm registry unreachable \u2014 local results only");
|
|
521
|
+
} else {
|
|
522
|
+
const npmKind = checkNpmFreshness(manifest.package, latestNpmVersion);
|
|
523
|
+
const pkgLabel = `package (installed: ${manifest.package}, latest: ${latestNpmVersion})`;
|
|
524
|
+
if (npmKind === "update available (npm)") {
|
|
525
|
+
log.warn(`update available (npm) ${pkgLabel}`);
|
|
526
|
+
} else {
|
|
527
|
+
log.success(`up-to-date (npm) ${pkgLabel}`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
});
|
|
386
531
|
return program;
|
|
387
532
|
}
|
|
388
533
|
async function runCli(argv = process.argv) {
|
|
@@ -439,4 +584,4 @@ export {
|
|
|
439
584
|
runCli,
|
|
440
585
|
run
|
|
441
586
|
};
|
|
442
|
-
//# sourceMappingURL=chunk-
|
|
587
|
+
//# sourceMappingURL=chunk-WRL4BRWX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/adapters/claude.ts","../src/registry.ts","../src/markers.ts","../src/adapters/codex.ts","../src/adapters/gemini.ts","../src/check.ts","../src/summary.ts","../src/writer.ts","../package.json"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { log, note } from \"@clack/prompts\";\nimport { Command, CommanderError } from \"commander\";\nimport { claudeAdapter } from \"./adapters/claude.js\";\nimport { codexAdapter } from \"./adapters/codex.js\";\nimport { geminiAdapter } from \"./adapters/gemini.js\";\nimport type { Adapter } from \"./adapters/types.js\";\nimport { checkNpmFreshness, checkSkills, fetchLatestNpmVersion } from \"./check.js\";\nimport { loadSkills } from \"./registry.js\";\nimport { renderSummary } from \"./summary.js\";\nimport type { ConflictPolicy, InstallContext, InstallManifest, Skill } from \"./types.js\";\nimport { writeManifest, writeOutputs, type WriteResult } from \"./writer.js\";\nimport pkg from \"../package.json\" with { type: \"json\" };\n\n// Single source of truth: the published package version. `tsup` inlines this at\n// build time, so `npm version` bumping package.json is all that's needed.\nconst VERSION = pkg.version;\n\n/** The agents we can install for. */\nexport type AgentId = \"claude\" | \"codex\" | \"gemini\";\n\n/** Adapter registry, keyed by agent id. The single dispatch table. */\nconst ADAPTERS: Record<AgentId, Adapter> = {\n claude: claudeAdapter,\n codex: codexAdapter,\n gemini: geminiAdapter,\n};\n\n/** Valid agent ids, for validation + help text. */\nexport const AGENT_IDS = Object.keys(ADAPTERS) as AgentId[];\n\n/**\n * Map a chosen agent id to its adapter.\n *\n * Throws a clear error on an unknown id so both the CLI (non-zero exit) and\n * library callers fail loudly rather than silently installing the wrong thing.\n */\nexport function selectAdapter(agentId: string): Adapter {\n const adapter = ADAPTERS[agentId as AgentId];\n if (!adapter) {\n throw new Error(\n `Unknown agent \"${agentId}\". Choose one of: ${AGENT_IDS.join(\", \")}.`,\n );\n }\n return adapter;\n}\n\n/**\n * The conflict policy to use for an agent by default.\n *\n * Claude's passthrough files are fully tool-owned, so they overwrite. The\n * Codex/Gemini marker targets (`AGENTS.md`, `GEMINI.md`) merge so user content\n * outside the managed markers is preserved on re-runs.\n */\nexport function defaultConflictPolicy(agentId: string): ConflictPolicy {\n return agentId === \"claude\" ? \"overwrite\" : \"merge\";\n}\n\n/**\n * Resolve a `--skills` value into the concrete list of skills to install.\n *\n * Accepts the literal `all` (every available skill) or a comma-separated list\n * of skill names (order/whitespace tolerant). Throws clearly on an empty\n * selection or any unknown name.\n */\nexport function resolveSkillSelection(\n spec: string,\n available: Skill[],\n): Skill[] {\n const trimmed = (spec ?? \"\").trim();\n if (trimmed.toLowerCase() === \"all\") return available;\n\n const names = trimmed\n .split(\",\")\n .map((n) => n.trim())\n .filter((n) => n.length > 0);\n if (names.length === 0) {\n throw new Error(\n `No skills selected. Use --skills all or a comma list (e.g. ${available\n .map((s) => s.name)\n .slice(0, 2)\n .join(\",\")}).`,\n );\n }\n\n const byName = new Map(available.map((s) => [s.name, s]));\n const unknown = names.filter((n) => !byName.has(n));\n if (unknown.length > 0) {\n throw new Error(\n `Unknown skill(s): ${unknown.join(\", \")}. Available: ${available\n .map((s) => s.name)\n .join(\", \")}.`,\n );\n }\n return names.map((n) => byName.get(n)!);\n}\n\n/** A resolved, ready-to-run install request. */\nexport interface InstallRequest {\n agentId: AgentId;\n skills: Skill[];\n targetDir: string;\n conflictPolicy?: ConflictPolicy;\n}\n\n/**\n * Translate the selected skills with the chosen adapter and write them to the\n * target dir. The single install primitive shared by the flag and wizard paths.\n */\nexport function runInstall(req: InstallRequest): {\n adapter: Adapter;\n results: WriteResult[];\n} {\n const adapter = selectAdapter(req.agentId);\n const ctx: InstallContext = {\n targetDir: req.targetDir,\n selectedSkills: req.skills,\n conflictPolicy: req.conflictPolicy ?? defaultConflictPolicy(req.agentId),\n };\n const outputs = adapter.translate(req.skills, ctx);\n const results = writeOutputs(outputs, ctx);\n\n // Write the uniform manifest so `check` can read it regardless of adapter.\n writeManifest(\n {\n package: VERSION,\n agent: req.agentId,\n installedAt: new Date().toISOString().split(\"T\")[0],\n skills: Object.fromEntries(req.skills.map((s) => [s.name, s.version])),\n },\n req.targetDir,\n );\n\n return { adapter, results };\n}\n\n/** Options collected from the non-interactive flags. */\ninterface InstallFlags {\n agent?: string;\n skills?: string;\n yes?: boolean;\n}\n\n/**\n * Resolve raw `--agent`/`--skills` flags into a validated install request.\n * Throws clearly (non-zero exit upstream) on any invalid value.\n */\nexport function resolveFlags(\n flags: InstallFlags,\n targetDir: string,\n available: Skill[],\n): InstallRequest {\n if (!flags.agent) {\n throw new Error(\"Missing --agent. Choose one of: \" + AGENT_IDS.join(\", \"));\n }\n selectAdapter(flags.agent); // validates the agent id\n const skills = resolveSkillSelection(flags.skills ?? \"all\", available);\n return { agentId: flags.agent as AgentId, skills, targetDir };\n}\n\n/** Run an install request and print its post-install summary. */\nfunction installAndReport(req: InstallRequest): void {\n const { adapter, results } = runInstall(req);\n console.log(\n renderSummary({\n agentId: adapter.id,\n agentLabel: adapter.label,\n results,\n }),\n );\n}\n\n/**\n * Build the commander program. Factored out so tests can introspect the\n * wired-up commands/options without spawning a process.\n */\nexport function buildProgram(): Command {\n const program = new Command();\n\n program\n .name(\"le-restaurant\")\n .description(\n \"Install the brigade of agent skills into your project, translated for your coding agent.\",\n )\n .version(VERSION);\n\n // Root command: non-interactive flags, or the wizard when no agent is given.\n program\n .argument(\"[targetDir]\", \"target project directory\", \".\")\n .option(\n \"--agent <agent>\",\n `target coding agent (${AGENT_IDS.join(\"|\")})`,\n )\n .option(\n \"--skills <skills>\",\n \"skills to install: 'all' or a comma-separated list\",\n )\n .option(\"--yes\", \"skip prompts; install with the given flags\", false)\n .action(async (targetDir: string, opts: InstallFlags) => {\n const available = loadSkills();\n\n // No agent flag → interactive wizard.\n if (!opts.agent) {\n const { runWizard } = await import(\"./wizard.js\");\n const choice = await runWizard(available);\n if (!choice) return; // cancelled\n installAndReport({\n agentId: choice.agentId,\n skills: choice.skills,\n targetDir,\n });\n return;\n }\n\n // Non-interactive: validate flags (throws → non-zero exit upstream).\n installAndReport(resolveFlags(opts, targetDir, available));\n });\n\n program\n .command(\"list\")\n .description(\"List the skills available to install.\")\n .action(() => {\n const skills = loadSkills();\n for (const skill of skills) {\n console.log(`- ${skill.name}: ${skill.description}`);\n }\n });\n\n program\n .command(\"check\")\n .description(\n \"Check installed skills for staleness against the bundled package versions.\",\n )\n .argument(\"[targetDir]\", \"project directory to check\", \".\")\n .action(async (targetDir: string) => {\n const manifestPath = resolve(targetDir, \".le-restaurant.json\");\n\n if (!existsSync(manifestPath)) {\n note(\n `No .le-restaurant.json found in ${resolve(targetDir)}.\\nRun \\`le-restaurant\\` in this directory first.`,\n \"not installed here\",\n );\n return;\n }\n\n const manifest = JSON.parse(\n readFileSync(manifestPath, \"utf8\"),\n ) as InstallManifest;\n\n const bundled = Object.fromEntries(\n loadSkills().map((s) => [s.name, s.version]),\n );\n\n const results = checkSkills(manifest, bundled);\n\n if (results.length === 0) {\n note(\"No skills recorded in the manifest.\", \"check\");\n return;\n }\n\n for (const r of results) {\n const label = `${r.name} (installed: ${r.installedVersion}, bundled: ${r.bundledVersion ?? \"—\"})`;\n if (r.status === \"up-to-date\") {\n log.success(`up-to-date ${label}`);\n } else if (r.status === \"stale (local)\") {\n log.warn(`stale (local) ${label}`);\n } else {\n log.info(`unknown ${label}`);\n }\n }\n\n // --- npm freshness (A3) ---\n // Fetch the latest published version from the registry. On network\n // failure `fetchLatestNpmVersion` returns null — fall back to local-only\n // results and emit a short notice. Always exits 0.\n const latestNpmVersion = await fetchLatestNpmVersion(pkg.name);\n if (latestNpmVersion === null) {\n log.warn(\"npm registry unreachable — local results only\");\n } else {\n const npmKind = checkNpmFreshness(manifest.package, latestNpmVersion);\n const pkgLabel = `package (installed: ${manifest.package}, latest: ${latestNpmVersion})`;\n if (npmKind === \"update available (npm)\") {\n log.warn(`update available (npm) ${pkgLabel}`);\n } else {\n log.success(`up-to-date (npm) ${pkgLabel}`);\n }\n }\n });\n\n return program;\n}\n\n/**\n * Parse argv, run the CLI, and resolve to the process exit code.\n *\n * commander is put in `exitOverride` mode so it throws instead of calling\n * `process.exit`, letting us (and tests) observe the exit code. Help/version\n * exit 0; parse/validation errors exit non-zero.\n */\nexport async function runCli(argv: string[] = process.argv): Promise<number> {\n const program = buildProgram();\n program.exitOverride();\n program.configureOutput({\n writeErr: (str) => process.stderr.write(str),\n });\n try {\n await program.parseAsync(argv);\n return Number(process.exitCode ?? 0);\n } catch (err) {\n if (err instanceof CommanderError) {\n // --help / --version resolve to exitCode 0; parse errors are non-zero.\n return err.exitCode;\n }\n console.error(err instanceof Error ? err.message : String(err));\n return 1;\n }\n}\n\n/** Convenience entry: run and let the process adopt the resolved exit code. */\nexport function run(argv: string[] = process.argv): Promise<number> {\n return runCli(argv).then((code) => {\n process.exitCode = code;\n return code;\n });\n}\n\n// Execute when invoked as the bin (not when imported by tests).\nconst invokedDirectly =\n typeof process !== \"undefined\" &&\n process.argv[1] !== undefined &&\n /cli\\.(js|ts)$/.test(process.argv[1]);\n\nif (invokedDirectly) {\n void run();\n}\n","import { readFileSync } from \"node:fs\";\nimport { skillSourcePath } from \"../registry.js\";\nimport type { FileOutput, InstallContext, Skill } from \"../types.js\";\nimport type { Adapter } from \"./types.js\";\n\n/**\n * Default tool grant for the emitted chef-de-rang subagent — the common\n * build/edit/search set a chef-de-rang needs to take a ticket from plan to\n * working code. Kept conservative; the maître-d overrides the *model* per\n * dispatch, not the tools.\n */\nconst CHEF_AGENT_TOOLS = \"Read, Write, Edit, Bash, Glob, Grep\";\n\n/**\n * Render the chef-de-rang **agent definition** that pins the cheap default\n * model. Claude Code reads `.claude/agents/<name>.md` frontmatter, so a\n * `model: sonnet` floor is enforced by config rather than prose. The maître-d\n * raises it to Opus per dispatch via the per-call model override (which takes\n * precedence over this frontmatter default).\n *\n * The body is generated from the skill body so the subagent carries the same\n * instructions as the on-demand skill.\n */\nfunction renderChefAgentDefinition(chef: Skill): string {\n const frontmatter = [\n \"---\",\n \"name: chef-de-rang\",\n `description: ${chef.description}`,\n \"model: sonnet\",\n `tools: ${CHEF_AGENT_TOOLS}`,\n \"---\",\n ].join(\"\\n\");\n return `${frontmatter}\\n\\n${chef.body}`;\n}\n\n/**\n * Claude Code — passthrough PLUS an emitted agent definition.\n *\n * Claude Code natively supports on-demand skills, so we copy each source\n * `SKILL.md` verbatim to `.claude/skills/<name>/SKILL.md`. To guarantee\n * byte-for-byte fidelity we read the original vendored file rather than\n * re-serialize the normalized `Skill` (which would lose exact formatting).\n *\n * In addition, when the selected skills include `chef-de-rang`, we emit a\n * model-pinned subagent definition at `.claude/agents/chef-de-rang.md`. This is\n * the Claude-only cost win: the maître-d can dispatch a real Sonnet subagent\n * and override to Opus per ticket. Other adapters can't express this, so they\n * degrade to a documented no-op (handled in their own adapters).\n */\nexport const claudeAdapter: Adapter = {\n id: \"claude\",\n label: \"Claude Code\",\n translate(skills: Skill[], _ctx: InstallContext): FileOutput[] {\n const outputs: FileOutput[] = skills.map((skill) => ({\n path: `.claude/skills/${skill.name}/SKILL.md`,\n contents: readFileSync(skillSourcePath(skill.name), \"utf8\"),\n mode: \"overwrite\",\n }));\n\n const chef = skills.find((skill) => skill.name === \"chef-de-rang\");\n if (chef) {\n outputs.push({\n path: \".claude/agents/chef-de-rang.md\",\n contents: renderChefAgentDefinition(chef),\n mode: \"overwrite\",\n });\n }\n\n return outputs;\n },\n};\n","import { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport matter from \"gray-matter\";\nimport type { Skill } from \"./types.js\";\n\nconst moduleDir = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Absolute path to the vendored source skills.\n *\n * This module lives at `src/registry.ts` in dev and `dist/registry.js` once\n * built; in both cases the vendored `skills/` directory sits one level up.\n */\nexport const skillsDir = resolve(moduleDir, \"..\", \"skills\");\n\n/** Absolute path to a vendored skill's source `SKILL.md`. */\nexport function skillSourcePath(name: string): string {\n return join(skillsDir, name, \"SKILL.md\");\n}\n\n/**\n * Parse raw `SKILL.md` content into a normalized `Skill`.\n *\n * Exported so tests can call it directly with inline content without touching\n * the file system. `loadSkill` delegates here.\n */\nexport function parseSkillContent(raw: string, name: string): Skill {\n const parsed = matter(raw);\n const frontmatter = parsed.data as Record<string, unknown>;\n\n let version: string;\n if (typeof frontmatter.version === \"string\" && frontmatter.version.length > 0) {\n version = frontmatter.version;\n } else {\n console.warn(\n `[le-restaurant] skill \"${name}\" has no version in its SKILL.md frontmatter — falling back to \"0.0.0\". Add a semver version: field.`,\n );\n version = \"0.0.0\";\n }\n\n return {\n name: typeof frontmatter.name === \"string\" ? frontmatter.name : name,\n description:\n typeof frontmatter.description === \"string\" ? frontmatter.description : \"\",\n version,\n frontmatter,\n body: parsed.content.replace(/^\\s+/, \"\"),\n };\n}\n\n/** Read one vendored `SKILL.md` and normalize it into a `Skill`. */\nexport function loadSkill(name: string): Skill {\n const raw = readFileSync(skillSourcePath(name), \"utf8\");\n return parseSkillContent(raw, name);\n}\n\n/** Discover and load every vendored source skill. */\nexport function loadSkills(): Skill[] {\n return readdirSync(skillsDir)\n .filter((entry) => statSync(join(skillsDir, entry)).isDirectory())\n .map((dir) => loadSkill(dir))\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n","/**\n * Managed-marker helper, shared by the merge-style adapters (Codex, Gemini).\n *\n * Tool-owned content inside files a user may also edit (`AGENTS.md`,\n * `GEMINI.md`) is bounded by managed markers. Only the span between the\n * markers is ever rewritten on a re-run; everything outside is the user's and\n * is preserved verbatim.\n */\n\nexport const MARKER_START = \"<!-- le-restaurant:start -->\";\nexport const MARKER_END = \"<!-- le-restaurant:end -->\";\n\n/** Notice rendered as the first line inside every managed block. */\nexport const MANAGED_NOTICE =\n \"<!-- Managed by le-restaurant. Do not edit between these markers; re-run the installer to update. -->\";\n\n/** Wrap `inner` content in managed markers, returning a complete block. */\nexport function renderManagedBlock(inner: string): string {\n return `${MARKER_START}\\n${inner}\\n${MARKER_END}\\n`;\n}\n\n/** Does `content` already contain a managed block? */\nexport function hasManagedBlock(content: string): boolean {\n return content.includes(MARKER_START) && content.includes(MARKER_END);\n}\n\n/**\n * Merge a freshly rendered managed `block` into `existing` file content.\n *\n * If `existing` already contains a managed span, that span (markers included)\n * is replaced in place. Otherwise the block is appended, separated from any\n * prior content by a blank line. User content outside the markers is never\n * touched, so repeated merges are idempotent for a stable block.\n */\nexport function mergeManagedBlock(existing: string, block: string): string {\n const start = existing.indexOf(MARKER_START);\n const end = existing.indexOf(MARKER_END);\n if (start !== -1 && end !== -1 && end > start) {\n // Replace the managed span, including the end marker and its trailing\n // newline if present, so the rendered block (which ends in \"\\n\") slots in\n // without accumulating blank lines.\n let after = end + MARKER_END.length;\n if (existing[after] === \"\\n\") after += 1;\n return existing.slice(0, start) + block + existing.slice(after);\n }\n if (existing.length === 0) return block;\n const separator = existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n return existing + separator + block;\n}\n","import {\n MANAGED_NOTICE,\n renderManagedBlock,\n} from \"../markers.js\";\nimport type { FileOutput, InstallContext, Skill } from \"../types.js\";\nimport type { Adapter } from \"./types.js\";\n\n/**\n * Render one skill as a delimited `## Skill: <name>` section: heading, the\n * one-line description, then the skill body.\n */\nfunction renderSection(skill: Skill): string {\n return `## Skill: ${skill.name} (v${skill.version})\\n\\n${skill.description}\\n\\n${skill.body.trimEnd()}`;\n}\n\n/**\n * Notice rendered inside the managed block: honest about what Codex cannot do.\n * Triggering degrades to always-on guidance (flagged to the user elsewhere).\n * Model-aware dispatch is a no-op: Codex has no subagent model-override, so\n * chef-de-rang runs on the session model; HITL and difficulty tagging still apply.\n */\nconst MODEL_DISPATCH_NOTICE =\n \"> **Note:** Model-aware dispatch (Sonnet/Opus selection) is a no-op on Codex — \" +\n \"chef-de-rang runs on the session model. The HITL gate and difficulty tagging still apply.\";\n\n/**\n * Codex — merge strategy.\n *\n * Codex reads a single monolithic `AGENTS.md` per directory with no named or\n * on-demand skills, so every selected skill is folded into one `AGENTS.md` as\n * a delimited `## Skill: <name>` section. The whole set lives inside managed\n * markers, so the writer's merge mode updates only that span on re-runs and\n * never disturbs surrounding user content. Triggering degrades to always-on\n * guidance (flagged to the user elsewhere). Model-aware dispatch is likewise a\n * no-op — documented in the generated output via MODEL_DISPATCH_NOTICE.\n */\nexport const codexAdapter: Adapter = {\n id: \"codex\",\n label: \"Codex\",\n translate(skills: Skill[], _ctx: InstallContext): FileOutput[] {\n const inner = [MANAGED_NOTICE, MODEL_DISPATCH_NOTICE, ...skills.map(renderSection)].join(\"\\n\\n\");\n return [\n {\n path: \"AGENTS.md\",\n contents: renderManagedBlock(inner),\n mode: \"merge\",\n },\n ];\n },\n};\n","import { MANAGED_NOTICE, renderManagedBlock } from \"../markers.js\";\nimport type { FileOutput, InstallContext, Skill } from \"../types.js\";\nimport type { Adapter } from \"./types.js\";\n\n/** Notice rendered at the top of each generated `.gemini/skills/<name>.md`. */\nconst SKILL_FILE_NOTICE =\n \"<!-- Managed by le-restaurant; edit the source skill, not this file. -->\";\n\n/** Relative import path for a skill's modular file, as used in `GEMINI.md`. */\nfunction importPath(skill: Skill): string {\n return `./.gemini/skills/${skill.name}.md`;\n}\n\n/** Render one skill into its own modular `.gemini/skills/<name>.md` file. */\nfunction renderSkillFile(skill: Skill): string {\n return `${SKILL_FILE_NOTICE}\\n<!-- Version: ${skill.version} -->\\n\\n${skill.description}\\n\\n${skill.body.trimEnd()}\\n`;\n}\n\n/**\n * Notice rendered in GEMINI.md's managed block: honest about what Gemini cannot do.\n * Skills are always-on (no on-demand triggering). Model-aware dispatch is also a\n * no-op: Gemini has no subagent model-override, so chef-de-rang runs on the session\n * model; HITL and difficulty tagging still apply.\n */\nconst MODEL_DISPATCH_NOTICE =\n \"> **Note:** Model-aware dispatch (Sonnet/Opus selection) is a no-op on Gemini — \" +\n \"chef-de-rang runs on the session model. The HITL gate and difficulty tagging still apply.\";\n\n/**\n * Gemini — import strategy.\n *\n * Gemini concatenates `GEMINI.md` hierarchically and supports `@file.md`\n * imports, so each skill is written to its own modular\n * `.gemini/skills/<name>.md` and referenced by an `@./.gemini/skills/<name>.md`\n * import line. The import lines live inside managed markers in `GEMINI.md`, so\n * re-runs update only that span; the modular skill files are fully tool-owned\n * (overwrite). Skills are always-on — no on-demand triggering. Model-aware\n * dispatch is likewise a no-op — documented in GEMINI.md via MODEL_DISPATCH_NOTICE.\n */\nexport const geminiAdapter: Adapter = {\n id: \"gemini\",\n label: \"Gemini\",\n translate(skills: Skill[], _ctx: InstallContext): FileOutput[] {\n const skillFiles: FileOutput[] = skills.map((skill) => ({\n path: `.gemini/skills/${skill.name}.md`,\n contents: renderSkillFile(skill),\n mode: \"overwrite\",\n }));\n\n const imports = skills.map((skill) => `@${importPath(skill)}`).join(\"\\n\");\n const inner = `${MANAGED_NOTICE}\\n\\n${MODEL_DISPATCH_NOTICE}\\n\\n${imports}`;\n const geminiMd: FileOutput = {\n path: \"GEMINI.md\",\n contents: renderManagedBlock(inner),\n mode: \"merge\",\n };\n\n return [...skillFiles, geminiMd];\n },\n};\n","import type { InstallManifest } from \"./types.js\";\n\n/** The staleness verdict for a single skill. */\nexport type SkillStatusKind = \"up-to-date\" | \"stale (local)\" | \"unknown\";\n\n/** The npm-freshness verdict for the installed package itself. */\nexport type NpmStatusKind = \"update available (npm)\" | \"up-to-date (npm)\";\n\n/** Per-skill comparison result returned by `checkSkills`. */\nexport interface SkillStatus {\n /** The skill identifier. */\n name: string;\n /** Version recorded in the installed manifest. */\n installedVersion: string;\n /** Version shipped in the currently-running package (bundled). */\n bundledVersion: string | undefined;\n /** The staleness verdict. */\n status: SkillStatusKind;\n}\n\n/**\n * Compare two semver strings numerically (major.minor.patch).\n *\n * Returns:\n * - negative when `a` is older than `b`\n * - 0 when equal\n * - positive when `a` is newer than `b`\n *\n * No external dependency: splits on `.` and compares each numeric segment.\n * Pre-release / build-metadata suffixes (if any) are ignored — good enough\n * for the local-drift use-case where only package-published versions appear.\n */\nexport function compareVersions(a: string, b: string): number {\n const parse = (v: string): number[] =>\n v\n .split(\".\")\n .slice(0, 3)\n .map((seg) => parseInt(seg, 10) || 0);\n\n const [aMajor = 0, aMinor = 0, aPatch = 0] = parse(a);\n const [bMajor = 0, bMinor = 0, bPatch = 0] = parse(b);\n\n if (aMajor !== bMajor) return aMajor - bMajor;\n if (aMinor !== bMinor) return aMinor - bMinor;\n return aPatch - bPatch;\n}\n\n/**\n * Compare every skill in `manifest` against the `bundled` version map.\n *\n * Pure function — no I/O. `bundled` is `{ [skillName]: semver }` derived\n * from the package's vendored `skills/` directory via `loadSkills()`.\n *\n * Rules:\n * - `installedVersion < bundledVersion` → `\"stale (local)\"`\n * - `installedVersion >= bundledVersion` → `\"up-to-date\"`\n * - skill not present in `bundled` → `\"unknown\"`\n */\nexport function checkSkills(\n manifest: InstallManifest,\n bundled: Record<string, string>,\n): SkillStatus[] {\n return Object.entries(manifest.skills).map(([name, installedVersion]) => {\n const bundledVersion = bundled[name];\n\n if (bundledVersion === undefined) {\n return { name, installedVersion, bundledVersion: undefined, status: \"unknown\" };\n }\n\n const cmp = compareVersions(installedVersion, bundledVersion);\n const status: SkillStatusKind = cmp < 0 ? \"stale (local)\" : \"up-to-date\";\n\n return { name, installedVersion, bundledVersion, status };\n });\n}\n\n/**\n * Fetch the latest published version of a package from the npm registry.\n *\n * The `fetcher` parameter is injectable so tests can pass a mock without\n * touching the global `fetch`. Defaults to the Node 18+ global `fetch`.\n *\n * Scoped package names (e.g. `@scope/pkg`) must have the `/` between scope\n * and name percent-encoded (`%2F`) in the registry URL — the leading `@` is\n * kept as-is.\n *\n * Returns `null` when the network is unavailable (fetch throws) or the\n * registry returns a non-200 status. Callers must handle the offline case\n * gracefully and must not propagate the error.\n */\nexport async function fetchLatestNpmVersion(\n pkgName: string,\n fetcher: typeof fetch = fetch,\n): Promise<string | null> {\n try {\n // Encode the \"/\" between scope and name; keep the leading \"@\".\n const encoded = pkgName.replace(\"/\", \"%2F\");\n const url = `https://registry.npmjs.org/${encoded}/latest`;\n const res = await fetcher(url);\n if (!res.ok) return null;\n const data = (await res.json()) as { version?: string };\n return typeof data.version === \"string\" ? data.version : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Compare the installed package version against the npm registry's latest.\n *\n * Pure function — no I/O.\n * - installed < latest → `\"update available (npm)\"`\n * - installed >= latest → `\"up-to-date (npm)\"`\n */\nexport function checkNpmFreshness(\n installedVersion: string,\n latestVersion: string,\n): NpmStatusKind {\n return compareVersions(installedVersion, latestVersion) < 0\n ? \"update available (npm)\"\n : \"up-to-date (npm)\";\n}\n","import type { WriteResult } from \"./writer.js\";\n\n/** Everything the post-install summary needs to render. */\nexport interface SummaryInput {\n /** The agent the skills were installed for. */\n agentId: \"claude\" | \"codex\" | \"gemini\";\n /** Human-readable agent label (e.g. \"Claude Code\"). */\n agentLabel: string;\n /** What the writer did with each file. */\n results: WriteResult[];\n}\n\n/**\n * Per-agent \"how to use the skills\" guidance.\n *\n * Claude Code keeps native on-demand triggering, so its skills behave as\n * designed. Codex and Gemini have no skill system — the translation degrades to\n * always-on guidance, which we must state honestly (see Architecture: \"Accept\n * fidelity loss on Codex/Gemini rather than fake a skill system\").\n */\nfunction usageNotes(agentId: SummaryInput[\"agentId\"]): string[] {\n switch (agentId) {\n case \"claude\":\n return [\n \"Skills install to .claude/skills/<name>/SKILL.md.\",\n \"Claude Code triggers each skill on demand — it picks the right one\",\n \"from its description when a matching task comes up. Nothing else to do.\",\n ];\n case \"codex\":\n return [\n \"Skills are folded into AGENTS.md as delimited sections, inside\",\n \"le-restaurant managed markers (edit outside the markers freely).\",\n \"Caveat: Codex has no skill system, so this guidance is ALWAYS-ON —\",\n \"there is no on-demand triggering. Every section is in context all the\",\n \"time, unlike Claude Code's on-demand skills.\",\n \"Note: Model-aware dispatch (Sonnet/Opus selection) is a no-op —\",\n \"chef-de-rang runs on the session model. The HITL gate and difficulty\",\n \"tagging still apply.\",\n ];\n case \"gemini\":\n return [\n \"Each skill is written to .gemini/skills/<name>.md and imported from\",\n \"GEMINI.md via @import lines inside le-restaurant managed markers.\",\n \"Caveat: Gemini has no skill system, so this guidance is ALWAYS-ON —\",\n \"there is no on-demand triggering. Every imported skill is in context\",\n \"all the time, unlike Claude Code's on-demand skills.\",\n \"Note: Model-aware dispatch (Sonnet/Opus selection) is a no-op —\",\n \"chef-de-rang runs on the session model. The HITL gate and difficulty\",\n \"tagging still apply.\",\n ];\n }\n}\n\n/**\n * Render the post-install summary: the target agent, every file written (with\n * the action taken), and how to use the installed skills — including the\n * always-on caveat for Codex/Gemini.\n */\nexport function renderSummary(input: SummaryInput): string {\n const lines: string[] = [];\n lines.push(`Installed ${input.results.length} file(s) for ${input.agentLabel}:`);\n for (const r of input.results) {\n lines.push(` ${r.action.padEnd(11)} ${r.path}`);\n }\n lines.push(\"\");\n lines.push(\"How to use:\");\n for (const note of usageNotes(input.agentId)) {\n lines.push(` ${note}`);\n }\n return lines.join(\"\\n\");\n}\n","import {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { randomBytes } from \"node:crypto\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { mergeManagedBlock } from \"./markers.js\";\nimport type { FileOutput, InstallContext, InstallManifest } from \"./types.js\";\n\n/** What the writer did with a single output file. */\nexport type WriteAction = \"created\" | \"overwritten\" | \"merged\" | \"skipped\";\n\n/** A file the writer processed, for the post-install summary. */\nexport interface WriteResult {\n /** Path relative to the target dir (as the adapter emitted it). */\n path: string;\n /** The action the conflict policy resolved to. */\n action: WriteAction;\n}\n\n/**\n * Atomically write `contents` to `absolute`.\n *\n * The bytes are written to a sibling temp file first, then renamed over the\n * destination — `rename` is atomic on a single filesystem, so a reader never\n * sees a half-written file and a failure mid-write never clobbers the existing\n * target. On any error the temp file is removed and the error rethrown.\n */\nexport function atomicWriteFile(absolute: string, contents: string): void {\n mkdirSync(dirname(absolute), { recursive: true });\n const suffix = randomBytes(6).toString(\"hex\");\n const tmp = `${absolute}.${suffix}.tmp`;\n try {\n writeFileSync(tmp, contents);\n renameSync(tmp, absolute);\n } catch (err) {\n try {\n rmSync(tmp, { force: true });\n } catch {\n // best-effort cleanup; surface the original failure below\n }\n throw err;\n }\n}\n\n/**\n * Write the `.le-restaurant.json` manifest to the target dir.\n *\n * Overwrites any existing manifest so the file always reflects the latest\n * install — the `check` command reads this as the single source of truth.\n */\nexport function writeManifest(\n manifest: InstallManifest,\n targetDir: string,\n): void {\n const absolute = resolve(targetDir, \".le-restaurant.json\");\n atomicWriteFile(absolute, JSON.stringify(manifest, null, 2) + \"\\n\");\n}\n\n/**\n * Write a set of adapter outputs into the install target.\n *\n * Each `FileOutput.path` is resolved relative to `ctx.targetDir` and written\n * atomically. When a target file already exists, the `conflictPolicy` decides:\n * - `skip` — leave the existing file untouched;\n * - `overwrite` — replace it with the emitted contents;\n * - `merge` — for `mode: \"merge\"` outputs, fold the managed block into the\n * existing file (preserving user content outside the markers); for other\n * outputs, fall back to overwrite.\n *\n * A file that does not yet exist is always created regardless of policy.\n */\nexport function writeOutputs(\n outputs: FileOutput[],\n ctx: InstallContext,\n): WriteResult[] {\n const results: WriteResult[] = [];\n for (const output of outputs) {\n const absolute = isAbsolute(output.path)\n ? output.path\n : resolve(ctx.targetDir, output.path);\n\n if (!existsSync(absolute)) {\n atomicWriteFile(absolute, output.contents);\n results.push({ path: output.path, action: \"created\" });\n continue;\n }\n\n switch (ctx.conflictPolicy) {\n case \"skip\":\n results.push({ path: output.path, action: \"skipped\" });\n break;\n case \"merge\":\n if (output.mode === \"merge\") {\n const existing = readFileSync(absolute, \"utf8\");\n atomicWriteFile(absolute, mergeManagedBlock(existing, output.contents));\n results.push({ path: output.path, action: \"merged\" });\n } else {\n atomicWriteFile(absolute, output.contents);\n results.push({ path: output.path, action: \"overwritten\" });\n }\n break;\n default:\n atomicWriteFile(absolute, output.contents);\n results.push({ path: output.path, action: \"overwritten\" });\n break;\n }\n }\n return results;\n}\n","{\n \"name\": \"@long.dg/le-restaurant\",\n \"version\": \"0.2.0\",\n \"description\": \"Install the brigade of agent skills into your project, translated for your coding agent.\",\n \"type\": \"module\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"bin\": {\n \"le-restaurant\": \"dist/cli.js\"\n },\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"skills\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"typecheck\": \"tsc --noEmit\",\n \"check:sync\": \"vitest run test/check-skills-sync.test.ts\"\n },\n \"keywords\": [\n \"claude\",\n \"codex\",\n \"gemini\",\n \"skills\",\n \"cli\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/dglong/le-restaurant.git\"\n },\n \"homepage\": \"https://github.com/dglong/le-restaurant#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/dglong/le-restaurant/issues\"\n },\n \"author\": \"\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"@clack/prompts\": \"^1.6.0\",\n \"commander\": \"^14.0.1\",\n \"gray-matter\": \"^4.0.3\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^24.10.1\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.3\",\n \"vitest\": \"^3.2.4\"\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","resolve","readFileSync","readFileSync","MODEL_DISPATCH_NOTICE","note","readFileSync","dirname","resolve","dirname","resolve","readFileSync","resolve","existsSync","readFileSync"]}
|
|
@@ -10,6 +10,8 @@ interface Skill {
|
|
|
10
10
|
name: string;
|
|
11
11
|
/** One-line description from frontmatter. */
|
|
12
12
|
description: string;
|
|
13
|
+
/** Semver string from frontmatter; defaults to "0.0.0" with a warning if absent. */
|
|
14
|
+
version: string;
|
|
13
15
|
/** Full parsed frontmatter as a key/value map. */
|
|
14
16
|
frontmatter: Record<string, unknown>;
|
|
15
17
|
/** The markdown body after the frontmatter, verbatim. */
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import 'commander';
|
|
2
|
-
export { a as AGENT_IDS, c as AgentId, d as InstallRequest, g as buildProgram, h as defaultConflictPolicy, r as resolveFlags, i as resolveSkillSelection, j as run, k as runCli, l as runInstall, s as selectAdapter } from './cli-
|
|
2
|
+
export { a as AGENT_IDS, c as AgentId, d as InstallRequest, g as buildProgram, h as defaultConflictPolicy, r as resolveFlags, i as resolveSkillSelection, j as run, k as runCli, l as runInstall, s as selectAdapter } from './cli-D4JjM2iZ.js';
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import { A as Adapter, S as Skill, W as WriteResult } from './cli-
|
|
2
|
-
export { a as AGENT_IDS, b as Agent, c as AgentId, C as ConflictPolicy, F as FileOutput, I as InstallContext, d as InstallRequest, e as WriteAction, f as WriteMode, g as buildProgram, h as defaultConflictPolicy, r as resolveFlags, i as resolveSkillSelection, j as run, k as runCli, l as runInstall, s as selectAdapter, w as writeOutputs } from './cli-
|
|
1
|
+
import { A as Adapter, S as Skill, W as WriteResult } from './cli-D4JjM2iZ.js';
|
|
2
|
+
export { a as AGENT_IDS, b as Agent, c as AgentId, C as ConflictPolicy, F as FileOutput, I as InstallContext, d as InstallRequest, e as WriteAction, f as WriteMode, g as buildProgram, h as defaultConflictPolicy, r as resolveFlags, i as resolveSkillSelection, j as run, k as runCli, l as runInstall, s as selectAdapter, w as writeOutputs } from './cli-D4JjM2iZ.js';
|
|
3
3
|
import 'commander';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Claude Code — passthrough
|
|
6
|
+
* Claude Code — passthrough PLUS an emitted agent definition.
|
|
7
7
|
*
|
|
8
8
|
* Claude Code natively supports on-demand skills, so we copy each source
|
|
9
9
|
* `SKILL.md` verbatim to `.claude/skills/<name>/SKILL.md`. To guarantee
|
|
10
10
|
* byte-for-byte fidelity we read the original vendored file rather than
|
|
11
11
|
* re-serialize the normalized `Skill` (which would lose exact formatting).
|
|
12
|
+
*
|
|
13
|
+
* In addition, when the selected skills include `chef-de-rang`, we emit a
|
|
14
|
+
* model-pinned subagent definition at `.claude/agents/chef-de-rang.md`. This is
|
|
15
|
+
* the Claude-only cost win: the maître-d can dispatch a real Sonnet subagent
|
|
16
|
+
* and override to Opus per ticket. Other adapters can't express this, so they
|
|
17
|
+
* degrade to a documented no-op (handled in their own adapters).
|
|
12
18
|
*/
|
|
13
19
|
declare const claudeAdapter: Adapter;
|
|
14
20
|
|
|
@@ -20,7 +26,8 @@ declare const claudeAdapter: Adapter;
|
|
|
20
26
|
* a delimited `## Skill: <name>` section. The whole set lives inside managed
|
|
21
27
|
* markers, so the writer's merge mode updates only that span on re-runs and
|
|
22
28
|
* never disturbs surrounding user content. Triggering degrades to always-on
|
|
23
|
-
* guidance (flagged to the user elsewhere).
|
|
29
|
+
* guidance (flagged to the user elsewhere). Model-aware dispatch is likewise a
|
|
30
|
+
* no-op — documented in the generated output via MODEL_DISPATCH_NOTICE.
|
|
24
31
|
*/
|
|
25
32
|
declare const codexAdapter: Adapter;
|
|
26
33
|
|
|
@@ -32,7 +39,8 @@ declare const codexAdapter: Adapter;
|
|
|
32
39
|
* `.gemini/skills/<name>.md` and referenced by an `@./.gemini/skills/<name>.md`
|
|
33
40
|
* import line. The import lines live inside managed markers in `GEMINI.md`, so
|
|
34
41
|
* re-runs update only that span; the modular skill files are fully tool-owned
|
|
35
|
-
* (overwrite). Skills are always-on — no on-demand triggering.
|
|
42
|
+
* (overwrite). Skills are always-on — no on-demand triggering. Model-aware
|
|
43
|
+
* dispatch is likewise a no-op — documented in GEMINI.md via MODEL_DISPATCH_NOTICE.
|
|
36
44
|
*/
|
|
37
45
|
declare const geminiAdapter: Adapter;
|
|
38
46
|
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: chef-de-rang
|
|
3
3
|
description: Builds one feature to completion from a single order ticket. Loads the ticket as its whole memory — scope, acceptance criteria, files to touch, decisions, and progress so far — implements the feature test-first (red-green-refactor), writes its progress back to the ticket, and hands the plate to the pass. Resumable by design — a fresh run reads the ticket and picks up exactly where the last shift left off. Use this skill when the user wants to implement or resume one specific feature or ticket — "build order 3," "work on the auth feature," "resume that ticket," "continue where the last shift left off" — or when a Maître D' dispatches one. Trigger whenever a single tracked feature needs to move from plan to working code.
|
|
4
|
+
version: 1.0.0
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Chef de Rang
|
|
@@ -11,7 +12,9 @@ Done well = the feature works, its acceptance criteria pass, and the ticket refl
|
|
|
11
12
|
|
|
12
13
|
## Load the table first
|
|
13
14
|
|
|
14
|
-
Read the order ticket named in the request (in `<docs-root
|
|
15
|
+
Read the order ticket named in the request (in `<docs-root>/recipes/<idea-slug>/service/order-NN-<slug>.md`). Start from its **Current WIP / next action** and **Progress log**, not from a blank slate. Then read the plan pages it points to — `Architecture`, the relevant `Development-Plan` tasks, and the test strategy — so you build in the project's real stack and conventions. Note the ticket's `Difficulty` and `HITL` tags — informational context set upstream by mise-en-place and copied down by the Maître D'; you read them but don't act on them (model selection is the Maître D's call, not yours).
|
|
16
|
+
|
|
17
|
+
**Which model you're running on (Claude Code):** when the Maître D' *dispatches* you, the model is pinned per the ticket's `Difficulty` (tidy→Sonnet, hard→Opus, and a pass-bounce re-fires at Opus). When you're invoked **manually** — a human running the skill directly rather than a maître-d dispatch — you run on the current **session model**, not the pinned one. Either way the work is the same; this just explains why a manual run may be on a different model than a dispatched one.
|
|
15
18
|
|
|
16
19
|
## Check the order is ready
|
|
17
20
|
|
package/skills/maitre-d/SKILL.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: maitre-d
|
|
3
3
|
description: Front-of-house orchestrator for building a planned project. Reads a mise-en-place plan wiki, cuts it into per-feature "order tickets," and runs service one course at a time — fires the next ready ticket, sends a chef-de-rang to build it, then works the pass to verify the feature is done before moving on. The natural follow-up once a build plan exists. Use this skill whenever the user wants to start or continue implementing a planned project — "let's build this," "start development," "what's next to build," "continue the build," "run the implementation," "work through the plan" — or hands over a plan folder and asks to execute it. Trigger it even without the word "build" when the user is moving from a finished plan toward shipping code.
|
|
4
|
+
version: 1.0.0
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Maître D'
|
|
@@ -21,14 +22,14 @@ Find the plan: scan the docs root for a `<slug>/plan/` wiki (`mise-en-place` out
|
|
|
21
22
|
|
|
22
23
|
## The service layer
|
|
23
24
|
|
|
24
|
-
Everything lives in `<docs-root
|
|
25
|
+
Everything lives in `<docs-root>/recipes/<idea-slug>/service/`, beside `plan/` and `recipe.md`, so one idea stays in one folder:
|
|
25
26
|
- `Board.md` — the floor at a glance: every ticket, its status, and the course order.
|
|
26
27
|
- `order-NN-<slug>.md` — one ticket per feature/phase. The unit of work **and** its memory.
|
|
27
28
|
|
|
28
29
|
## Flow
|
|
29
30
|
|
|
30
31
|
### 1. Seat the room (first run)
|
|
31
|
-
From `Phases.md` and `Development-Plan.md`, cut the work into tickets — usually one per phase or coherent feature. Seed each ticket straight from the plan: its tasks, their **Done when** criteria, and **Touches** list. Write `Board.md` with the course order, respecting dependencies.
|
|
32
|
+
From `Phases.md` and `Development-Plan.md`, cut the work into tickets — usually one per phase or coherent feature. Seed each ticket straight from the plan: its tasks, their **Done when** criteria, and **Touches** list, and its `Difficulty` and `HITL` tags — authored by mise-en-place, they travel to the ticket unchanged. Write `Board.md` with the course order, respecting dependencies.
|
|
32
33
|
|
|
33
34
|
**Resolve shared surfaces up front.** Scan every ticket's **Touches** for shared surfaces the plan already names — routing, shared services/types, the component library, build config. Don't leave these to be discovered mid-cook: pre-own or pre-stub them now (e.g. register a route placeholder, declare a shared interface) so a chef-de-rang is never blocked halfway through a course by something the plan already foresaw. Only genuinely *unforeseen* shared needs should surface as a mid-cook escalation.
|
|
34
35
|
|
|
@@ -38,7 +39,13 @@ Show the breakdown — tickets, course order, and the shared surfaces you're hol
|
|
|
38
39
|
Some surfaces are shared — routing, shared services/types, the component library, build config. The Maître D' holds these. When a ticket needs a cross-cutting change, it's made deliberately (at seating or at the pass), not buried inside one feature. Note shared touches on the ticket so nothing collides. When a shared decision is non-obvious, log it in the plan's `Decisions & Glossary` (ADR log) so the *why* outlives the build — a ticket's local **Decisions** are fine for feature-scoped choices, but cross-cutting ones graduate to the plan.
|
|
39
40
|
|
|
40
41
|
### 3. Fire the next ticket
|
|
41
|
-
Pick the next ticket whose dependencies are **Served** and whose **Definition of Ready** is met (criteria + touches present, no blocking unknown). If it isn't ready, fix the ticket first — never fire a vague order. Set Status → **Firing**, then
|
|
42
|
+
Pick the next ticket whose dependencies are **Served** and whose **Definition of Ready** is met (criteria + touches present, no blocking unknown). If it isn't ready, fix the ticket first — never fire a vague order. If the ticket carries `HITL: yes`, pause and ask the human for explicit go-ahead before proceeding — do not fire until you have it. Set Status → **Firing**, then dispatch a `chef-de-rang` **as a subagent** (its own isolated context — not inline in your own) with the ticket path and the plan pages it points to.
|
|
43
|
+
|
|
44
|
+
**Pick the model — Claude Code only.** On Claude Code the install ships a `chef-de-rang` agent definition pinned to `model: sonnet`, so the cheap default is enforced by config. Map the ticket's `Difficulty` tag to the model you dispatch with:
|
|
45
|
+
- `Difficulty: tidy` → **Sonnet** (the agent definition's default; dispatch as-is).
|
|
46
|
+
- `Difficulty: hard` → **Opus** (override the model on this dispatch; the per-call model takes precedence over the frontmatter default).
|
|
47
|
+
|
|
48
|
+
This switch is Claude-Code-only — only its format expresses a model-pinned subagent with a per-dispatch override. On Codex/Gemini it's a documented no-op: the chef-de-rang runs on the session model, and only the HITL gate and tagging carry over. Note too that a **manual** invocation of chef-de-rang (a human running the skill directly, not a maître-d dispatch) runs on the session model — the pinned model applies only when *you* dispatch it.
|
|
42
49
|
|
|
43
50
|
### 4. Work the pass
|
|
44
51
|
When the chef-de-rang hands back, check the plate before it ships. **Proof, not claims** — a plate is passed on shown command output, never on a "tests pass" assertion. For each check, *run the command yourself*, read the actual output, and only then judge:
|
|
@@ -49,7 +56,7 @@ When the chef-de-rang hands back, check the plate before it ships. **Proof, not
|
|
|
49
56
|
|
|
50
57
|
Record the verification on the ticket's **Tests** section: the commands run and their result (e.g. `vitest run → 12 passed`, `tsc --noEmit → clean`). An "I'm done" with no command output behind it is not done.
|
|
51
58
|
|
|
52
|
-
A plate that fails goes back to the same ticket with notes; Status stays **Firing**, it does not ship. Only when the output proves it: Status → **Served**, update `Board.md`, fire the next.
|
|
59
|
+
A plate that fails goes back to the same ticket with notes; Status stays **Firing**, it does not ship. On Claude Code, **escalate the model on the re-fire**: a bounced plate re-dispatches at **Opus** even if its `Difficulty` is `tidy` (Sonnet→Opus) — the harder second attempt earns the stronger model. Only when the output proves it: Status → **Served**, update `Board.md`, fire the next.
|
|
53
60
|
|
|
54
61
|
### 5. Last orders
|
|
55
62
|
When every ticket is Served, say so plainly and point at what the plan parked under "beyond MVP." Don't invent new courses.
|
|
@@ -62,6 +69,8 @@ One per feature, at `service/order-NN-<slug>.md`. This is the durable memory —
|
|
|
62
69
|
# Order NN · <feature name> · Status: To fire
|
|
63
70
|
**Covers:** <plan phase / tasks this ticket delivers>
|
|
64
71
|
**Depends on:** <other tickets, or none>
|
|
72
|
+
**Difficulty:** tidy|hard
|
|
73
|
+
**HITL:** yes|no
|
|
65
74
|
|
|
66
75
|
## Done when (acceptance — checked at the pass)
|
|
67
76
|
- [ ] <criterion, observable>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mise-en-place
|
|
3
3
|
description: Turns a confirmed product idea into a buildable plan — a small cross-linked wiki of scope, architecture, phases, and a development plan — and refines it across repeated runs. The natural follow-up to an idea-validation session such as the sous-chef skill — once an idea is "cooked," this gets everything in place before you build, and you can re-run it to harden the plan over time. Use this skill whenever the user has a settled idea and wants to plan or refine the build — "how should I build this?", "design the architecture", "break this into phases", "write a dev plan", "scope this out", "tighten up the plan", "solidify the architecture", or when they hand over a recipe/idea doc and ask what's next. Trigger it even without the words "architecture" or "plan" when the user is moving from *what* to build toward *how*.
|
|
4
|
+
version: 1.0.0
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Mise en Place
|
|
@@ -22,10 +23,10 @@ What does **not** change is the two gates. Scope and stack still get a yes befor
|
|
|
22
23
|
|
|
23
24
|
## Pick the mode first
|
|
24
25
|
|
|
25
|
-
1. **Refine** — a plan wiki already exists (`<docs-root
|
|
26
|
+
1. **Refine** — a plan wiki already exists (`<docs-root>/recipes/<idea-slug>/plan/`, or a `*-plan/` folder from an older run) → load it, don't regenerate. Ask which page to harden, update it in place, bump its status (see below). Leave the rest untouched.
|
|
26
27
|
2. **Fresh** — no plan yet → run the full flow below.
|
|
27
28
|
|
|
28
|
-
Either way, check the dish is cooked. **Find it by scanning the docs root for
|
|
29
|
+
Either way, check the dish is cooked. **Find it by scanning the docs root for `recipes/*/recipe.md`** (sous-chef output) — don't guess the slug. If several turn up, confirm which idea; if the user named one, match its folder. Failing that, take any `recipe*.md` or idea doc in the working dir. Read it as the source of truth.
|
|
29
30
|
|
|
30
31
|
Two stop conditions before you plan anything:
|
|
31
32
|
- **No usable idea** (missing or half-baked) → send it back to cook: suggest the `sous-chef` skill or ask 2–3 quick questions. Never plan a fuzzy idea.
|
|
@@ -35,7 +36,7 @@ Two stop conditions before you plan anything:
|
|
|
35
36
|
|
|
36
37
|
Two gates, then plate. Don't generate pages before both pass — wrong scope or stack wastes the prep.
|
|
37
38
|
|
|
38
|
-
1. **Read the dish.** If the idea's `recipe.md` exists (in `<docs-root
|
|
39
|
+
1. **Read the dish.** If the idea's `recipe.md` exists (in `<docs-root>/recipes/<idea-slug>/`), pull its **Problem / Solution / Users & context / Constraints / Verdict** straight in — that's the handoff contract. If there's only a loose idea doc or nothing, reconstruct those inputs with 2–3 quick questions (the Verdict is just the go/pivot/shelve call — for a fresh build it's an implicit "go"). Either way, restate the build in one line and confirm.
|
|
39
40
|
2. **Gate A — scope.** Draft the MVP boundary: in / out / non-goals. Show a tight list, get a yes. While here, note two things that decide the page set: does it handle **sensitive/regulated data, auth, or payments**? Does it need **hosting, CI/CD, or infra**?
|
|
40
41
|
3. **Gate B — stack & page set.** Inspect the repo (`package.json`, configs, `Dockerfile`) and the idea's real needs, propose a stack with one-line rationale per choice, flag uncertainties. Confirm before committing; research genuine unknowns via a line cook, don't guess. Then confirm which pages to plate: the four **core** pages always, plus any **optional** pages the Gate A answers call for (see the page list). Don't bolt a compliance page onto a weekend tool — but don't skip one on something handling health or payment data either.
|
|
41
42
|
4. **Plate the wiki.** Generate the chosen pages, cross-linked, share the folder path.
|
|
@@ -50,13 +51,15 @@ In agentic environments, delegate lookups (lib comparisons, "still maintained?",
|
|
|
50
51
|
|
|
51
52
|
## The wiki
|
|
52
53
|
|
|
53
|
-
**Where it goes:** keep outputs organized and colocated with the recipe. Find the **docs root** — an existing `docs/`, `documentation/`, Obsidian vault, or the repo's docs dir; else create `docs/`. The wiki lives at `<docs-root
|
|
54
|
+
**Where it goes:** keep outputs organized and colocated with the recipe. Find the **docs root** — an existing `docs/`, `documentation/`, Obsidian vault, or the repo's docs dir; else create `docs/`. Inside it, every recipe lives under one `recipes/` container. The wiki lives at `<docs-root>/recipes/<idea-slug>/plan/`, right beside the idea's `recipe.md`, so everything about one idea sits in one folder. Ask once only if the location is genuinely unclear.
|
|
54
55
|
|
|
55
56
|
Cross-link with `[[Page Name]]` (Obsidian / GitHub wiki) or relative `./Page.md` for a plain repo — match where the user keeps docs. Every page links back to `[[Home]]`. Each page header carries `Status: Draft|Firm`. Fill only what the session produced; mark gaps `TBD`.
|
|
56
57
|
|
|
57
58
|
**Core pages (always):** Home, Scope, Architecture, Phases, Development Plan, Decisions & Glossary.
|
|
58
59
|
**Optional pages (include only when warranted):** Security & Compliance (sensitive/regulated data, auth, payments); Deployment & Ops (needs hosting, CI/CD, or infra). When you include one, add its row to the Home table.
|
|
59
60
|
|
|
61
|
+
**Tag each feature.** In `Phases.md` and `Development-Plan.md`, author a `Difficulty: tidy|hard` and `HITL: yes|no` tag on every phase or feature entry. Rubric — deterministic; two readers should reach the same answer: `Difficulty: hard` if **any** of — touches a shared surface (routing, shared types/services, build config); has unresolved decisions or open unknowns; deep dependency chain (depends on two or more incomplete items); or non-trivial logic/algorithm; else `tidy`. `HITL: yes` if architecturally consequential, ambiguous in scope, or security/data-sensitive; else `no`.
|
|
62
|
+
|
|
60
63
|
For diagrams, pick the type that earns its place: `flowchart` for components/flow, `sequenceDiagram` for a request path, `erDiagram` for the data model. Don't ship the placeholder below unchanged.
|
|
61
64
|
|
|
62
65
|
### `Home.md`
|
|
@@ -111,7 +114,9 @@ flowchart LR
|
|
|
111
114
|
# Phases · Status: Draft
|
|
112
115
|
Skeleton → MVP → beyond; each phase ships something demoable.
|
|
113
116
|
## Phase 0 — Walking skeleton — goal / deliverables / exit criteria
|
|
117
|
+
**Difficulty:** tidy|hard **HITL:** yes|no
|
|
114
118
|
## Phase 1 — <core value> — goal / deliverables / exit / depends on
|
|
119
|
+
**Difficulty:** tidy|hard **HITL:** yes|no
|
|
115
120
|
## Phase 2+ — ...
|
|
116
121
|
## Sequencing notes
|
|
117
122
|
← [[Home]]
|
|
@@ -122,6 +127,7 @@ Skeleton → MVP → beyond; each phase ships something demoable.
|
|
|
122
127
|
# Development Plan · Status: Draft
|
|
123
128
|
## Tasks — per phase, epics → shippable tasks with S/M/L size
|
|
124
129
|
### Phase 0
|
|
130
|
+
**Difficulty:** tidy|hard **HITL:** yes|no
|
|
125
131
|
- [ ] <task> — S
|
|
126
132
|
- **Done when:** <observable acceptance criteria — how you'd verify it>
|
|
127
133
|
- **Touches:** <files / areas / components>
|
|
@@ -188,6 +194,6 @@ The page that keeps the *why* alive once building starts. Seed it from the recip
|
|
|
188
194
|
- Skipping security/deploy planning on a project that clearly needs it — or bolting those pages onto a throwaway tool.
|
|
189
195
|
- Dev-plan tasks with no acceptance criteria, so nobody (human or agent) can tell when they're done.
|
|
190
196
|
- Researching in the main kitchen instead of via a line cook.
|
|
191
|
-
- Scattering the plan away from the recipe instead of colocating under one `<docs-root
|
|
197
|
+
- Scattering the plan away from the recipe instead of colocating under one `<docs-root>/recipes/<idea-slug>/` folder.
|
|
192
198
|
- Treating service mode as license to skip a gate.
|
|
193
199
|
- Pages that don't link back to `[[Home]]`.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sous-chef
|
|
3
3
|
description: A friendly kitchen-themed thinking partner that helps the user "cook" a half-formed idea — clarifying and taste-testing it through short, focused questions, tracking how "done" the shared understanding is, and plating a structured recipe card (problem / solution / inspiration / risks) once it's ready. Use this skill whenever the user brings a raw or early-stage idea and wants to think it through, validate it, flesh it out, sanity-check it, or "bounce something off you" — e.g. "I have an idea for...", "thinking about building...", "is this idea any good?", "help me brainstorm", "help me figure out whether...". Trigger it even when the user never says "brainstorm" but is clearly exploring an unproven idea and wants a thinking partner rather than immediate execution or code.
|
|
4
|
+
version: 1.0.0
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Sous-Chef
|
|
@@ -107,13 +108,13 @@ If there's no subagent capability, say so briefly and ask whether to search inli
|
|
|
107
108
|
|
|
108
109
|
## Where outputs go
|
|
109
110
|
|
|
110
|
-
Keep outputs organized and colocated. First find the **docs root**: if the working dir already has a docs folder — `docs/`, `documentation/`, an Obsidian vault, or the repo's established docs dir — use it; otherwise create `docs/`. Ask once only if it's genuinely unclear, then remember it for the session.
|
|
111
|
+
Keep outputs organized and colocated. First find the **docs root**: if the working dir already has a docs folder — `docs/`, `documentation/`, an Obsidian vault, or the repo's established docs dir — use it; otherwise create `docs/`. Ask once only if it's genuinely unclear, then remember it for the session. Inside the docs root, every recipe lives under one `recipes/` container.
|
|
111
112
|
|
|
112
|
-
Give the idea a short slug and write the card to `<docs-root
|
|
113
|
+
Give the idea a short slug and write the card to `<docs-root>/recipes/<idea-slug>/recipe.md` — its own folder named for the idea, so the folder name alone tells you the dish. That folder becomes the home for everything about this idea — a planning skill like `mise-en-place` drops its wiki right alongside it, so the recipe and the build plan live together.
|
|
113
114
|
|
|
114
115
|
## The recipe card
|
|
115
116
|
|
|
116
|
-
After the chef confirms the tasting notes, write the card to `<docs-root
|
|
117
|
+
After the chef confirms the tasting notes, write the card to `<docs-root>/recipes/<idea-slug>/recipe.md` (see *Where outputs go*) and share the path. Fill only what the session produced — mark a section "TBD" rather than inventing it. Right-size it: a throwaway weekend tool doesn't need the venture sections, so skip them rather than padding.
|
|
117
118
|
|
|
118
119
|
This card is the clean handoff into a planning skill like `mise-en-place` (which reads Problem / Solution / Users / Constraints directly), but it also stands on its own as a decision record you can revisit.
|
|
119
120
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/adapters/claude.ts","../src/registry.ts","../src/markers.ts","../src/adapters/codex.ts","../src/adapters/gemini.ts","../src/summary.ts","../src/writer.ts","../package.json"],"sourcesContent":["import { Command, CommanderError } from \"commander\";\nimport { claudeAdapter } from \"./adapters/claude.js\";\nimport { codexAdapter } from \"./adapters/codex.js\";\nimport { geminiAdapter } from \"./adapters/gemini.js\";\nimport type { Adapter } from \"./adapters/types.js\";\nimport { loadSkills } from \"./registry.js\";\nimport { renderSummary } from \"./summary.js\";\nimport type { ConflictPolicy, InstallContext, Skill } from \"./types.js\";\nimport { writeOutputs, type WriteResult } from \"./writer.js\";\nimport pkg from \"../package.json\" with { type: \"json\" };\n\n// Single source of truth: the published package version. `tsup` inlines this at\n// build time, so `npm version` bumping package.json is all that's needed.\nconst VERSION = pkg.version;\n\n/** The agents we can install for. */\nexport type AgentId = \"claude\" | \"codex\" | \"gemini\";\n\n/** Adapter registry, keyed by agent id. The single dispatch table. */\nconst ADAPTERS: Record<AgentId, Adapter> = {\n claude: claudeAdapter,\n codex: codexAdapter,\n gemini: geminiAdapter,\n};\n\n/** Valid agent ids, for validation + help text. */\nexport const AGENT_IDS = Object.keys(ADAPTERS) as AgentId[];\n\n/**\n * Map a chosen agent id to its adapter.\n *\n * Throws a clear error on an unknown id so both the CLI (non-zero exit) and\n * library callers fail loudly rather than silently installing the wrong thing.\n */\nexport function selectAdapter(agentId: string): Adapter {\n const adapter = ADAPTERS[agentId as AgentId];\n if (!adapter) {\n throw new Error(\n `Unknown agent \"${agentId}\". Choose one of: ${AGENT_IDS.join(\", \")}.`,\n );\n }\n return adapter;\n}\n\n/**\n * The conflict policy to use for an agent by default.\n *\n * Claude's passthrough files are fully tool-owned, so they overwrite. The\n * Codex/Gemini marker targets (`AGENTS.md`, `GEMINI.md`) merge so user content\n * outside the managed markers is preserved on re-runs.\n */\nexport function defaultConflictPolicy(agentId: string): ConflictPolicy {\n return agentId === \"claude\" ? \"overwrite\" : \"merge\";\n}\n\n/**\n * Resolve a `--skills` value into the concrete list of skills to install.\n *\n * Accepts the literal `all` (every available skill) or a comma-separated list\n * of skill names (order/whitespace tolerant). Throws clearly on an empty\n * selection or any unknown name.\n */\nexport function resolveSkillSelection(\n spec: string,\n available: Skill[],\n): Skill[] {\n const trimmed = (spec ?? \"\").trim();\n if (trimmed.toLowerCase() === \"all\") return available;\n\n const names = trimmed\n .split(\",\")\n .map((n) => n.trim())\n .filter((n) => n.length > 0);\n if (names.length === 0) {\n throw new Error(\n `No skills selected. Use --skills all or a comma list (e.g. ${available\n .map((s) => s.name)\n .slice(0, 2)\n .join(\",\")}).`,\n );\n }\n\n const byName = new Map(available.map((s) => [s.name, s]));\n const unknown = names.filter((n) => !byName.has(n));\n if (unknown.length > 0) {\n throw new Error(\n `Unknown skill(s): ${unknown.join(\", \")}. Available: ${available\n .map((s) => s.name)\n .join(\", \")}.`,\n );\n }\n return names.map((n) => byName.get(n)!);\n}\n\n/** A resolved, ready-to-run install request. */\nexport interface InstallRequest {\n agentId: AgentId;\n skills: Skill[];\n targetDir: string;\n conflictPolicy?: ConflictPolicy;\n}\n\n/**\n * Translate the selected skills with the chosen adapter and write them to the\n * target dir. The single install primitive shared by the flag and wizard paths.\n */\nexport function runInstall(req: InstallRequest): {\n adapter: Adapter;\n results: WriteResult[];\n} {\n const adapter = selectAdapter(req.agentId);\n const ctx: InstallContext = {\n targetDir: req.targetDir,\n selectedSkills: req.skills,\n conflictPolicy: req.conflictPolicy ?? defaultConflictPolicy(req.agentId),\n };\n const outputs = adapter.translate(req.skills, ctx);\n const results = writeOutputs(outputs, ctx);\n return { adapter, results };\n}\n\n/** Options collected from the non-interactive flags. */\ninterface InstallFlags {\n agent?: string;\n skills?: string;\n yes?: boolean;\n}\n\n/**\n * Resolve raw `--agent`/`--skills` flags into a validated install request.\n * Throws clearly (non-zero exit upstream) on any invalid value.\n */\nexport function resolveFlags(\n flags: InstallFlags,\n targetDir: string,\n available: Skill[],\n): InstallRequest {\n if (!flags.agent) {\n throw new Error(\"Missing --agent. Choose one of: \" + AGENT_IDS.join(\", \"));\n }\n selectAdapter(flags.agent); // validates the agent id\n const skills = resolveSkillSelection(flags.skills ?? \"all\", available);\n return { agentId: flags.agent as AgentId, skills, targetDir };\n}\n\n/** Run an install request and print its post-install summary. */\nfunction installAndReport(req: InstallRequest): void {\n const { adapter, results } = runInstall(req);\n console.log(\n renderSummary({\n agentId: adapter.id,\n agentLabel: adapter.label,\n results,\n }),\n );\n}\n\n/**\n * Build the commander program. Factored out so tests can introspect the\n * wired-up commands/options without spawning a process.\n */\nexport function buildProgram(): Command {\n const program = new Command();\n\n program\n .name(\"le-restaurant\")\n .description(\n \"Install the brigade of agent skills into your project, translated for your coding agent.\",\n )\n .version(VERSION);\n\n // Root command: non-interactive flags, or the wizard when no agent is given.\n program\n .argument(\"[targetDir]\", \"target project directory\", \".\")\n .option(\n \"--agent <agent>\",\n `target coding agent (${AGENT_IDS.join(\"|\")})`,\n )\n .option(\n \"--skills <skills>\",\n \"skills to install: 'all' or a comma-separated list\",\n )\n .option(\"--yes\", \"skip prompts; install with the given flags\", false)\n .action(async (targetDir: string, opts: InstallFlags) => {\n const available = loadSkills();\n\n // No agent flag → interactive wizard.\n if (!opts.agent) {\n const { runWizard } = await import(\"./wizard.js\");\n const choice = await runWizard(available);\n if (!choice) return; // cancelled\n installAndReport({\n agentId: choice.agentId,\n skills: choice.skills,\n targetDir,\n });\n return;\n }\n\n // Non-interactive: validate flags (throws → non-zero exit upstream).\n installAndReport(resolveFlags(opts, targetDir, available));\n });\n\n program\n .command(\"list\")\n .description(\"List the skills available to install.\")\n .action(() => {\n const skills = loadSkills();\n for (const skill of skills) {\n console.log(`- ${skill.name}: ${skill.description}`);\n }\n });\n\n return program;\n}\n\n/**\n * Parse argv, run the CLI, and resolve to the process exit code.\n *\n * commander is put in `exitOverride` mode so it throws instead of calling\n * `process.exit`, letting us (and tests) observe the exit code. Help/version\n * exit 0; parse/validation errors exit non-zero.\n */\nexport async function runCli(argv: string[] = process.argv): Promise<number> {\n const program = buildProgram();\n program.exitOverride();\n program.configureOutput({\n writeErr: (str) => process.stderr.write(str),\n });\n try {\n await program.parseAsync(argv);\n return Number(process.exitCode ?? 0);\n } catch (err) {\n if (err instanceof CommanderError) {\n // --help / --version resolve to exitCode 0; parse errors are non-zero.\n return err.exitCode;\n }\n console.error(err instanceof Error ? err.message : String(err));\n return 1;\n }\n}\n\n/** Convenience entry: run and let the process adopt the resolved exit code. */\nexport function run(argv: string[] = process.argv): Promise<number> {\n return runCli(argv).then((code) => {\n process.exitCode = code;\n return code;\n });\n}\n\n// Execute when invoked as the bin (not when imported by tests).\nconst invokedDirectly =\n typeof process !== \"undefined\" &&\n process.argv[1] !== undefined &&\n /cli\\.(js|ts)$/.test(process.argv[1]);\n\nif (invokedDirectly) {\n void run();\n}\n","import { readFileSync } from \"node:fs\";\nimport { skillSourcePath } from \"../registry.js\";\nimport type { FileOutput, InstallContext, Skill } from \"../types.js\";\nimport type { Adapter } from \"./types.js\";\n\n/**\n * Claude Code — passthrough strategy.\n *\n * Claude Code natively supports on-demand skills, so we copy each source\n * `SKILL.md` verbatim to `.claude/skills/<name>/SKILL.md`. To guarantee\n * byte-for-byte fidelity we read the original vendored file rather than\n * re-serialize the normalized `Skill` (which would lose exact formatting).\n */\nexport const claudeAdapter: Adapter = {\n id: \"claude\",\n label: \"Claude Code\",\n translate(skills: Skill[], _ctx: InstallContext): FileOutput[] {\n return skills.map((skill) => ({\n path: `.claude/skills/${skill.name}/SKILL.md`,\n contents: readFileSync(skillSourcePath(skill.name), \"utf8\"),\n mode: \"overwrite\",\n }));\n },\n};\n","import { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport matter from \"gray-matter\";\nimport type { Skill } from \"./types.js\";\n\nconst moduleDir = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Absolute path to the vendored source skills.\n *\n * This module lives at `src/registry.ts` in dev and `dist/registry.js` once\n * built; in both cases the vendored `skills/` directory sits one level up.\n */\nexport const skillsDir = resolve(moduleDir, \"..\", \"skills\");\n\n/** Absolute path to a vendored skill's source `SKILL.md`. */\nexport function skillSourcePath(name: string): string {\n return join(skillsDir, name, \"SKILL.md\");\n}\n\n/** Read one vendored `SKILL.md` and normalize it into a `Skill`. */\nexport function loadSkill(name: string): Skill {\n const raw = readFileSync(skillSourcePath(name), \"utf8\");\n const parsed = matter(raw);\n const frontmatter = parsed.data as Record<string, unknown>;\n return {\n name: typeof frontmatter.name === \"string\" ? frontmatter.name : name,\n description:\n typeof frontmatter.description === \"string\" ? frontmatter.description : \"\",\n frontmatter,\n body: parsed.content.replace(/^\\s+/, \"\"),\n };\n}\n\n/** Discover and load every vendored source skill. */\nexport function loadSkills(): Skill[] {\n return readdirSync(skillsDir)\n .filter((entry) => statSync(join(skillsDir, entry)).isDirectory())\n .map((dir) => loadSkill(dir))\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n","/**\n * Managed-marker helper, shared by the merge-style adapters (Codex, Gemini).\n *\n * Tool-owned content inside files a user may also edit (`AGENTS.md`,\n * `GEMINI.md`) is bounded by managed markers. Only the span between the\n * markers is ever rewritten on a re-run; everything outside is the user's and\n * is preserved verbatim.\n */\n\nexport const MARKER_START = \"<!-- le-restaurant:start -->\";\nexport const MARKER_END = \"<!-- le-restaurant:end -->\";\n\n/** Notice rendered as the first line inside every managed block. */\nexport const MANAGED_NOTICE =\n \"<!-- Managed by le-restaurant. Do not edit between these markers; re-run the installer to update. -->\";\n\n/** Wrap `inner` content in managed markers, returning a complete block. */\nexport function renderManagedBlock(inner: string): string {\n return `${MARKER_START}\\n${inner}\\n${MARKER_END}\\n`;\n}\n\n/** Does `content` already contain a managed block? */\nexport function hasManagedBlock(content: string): boolean {\n return content.includes(MARKER_START) && content.includes(MARKER_END);\n}\n\n/**\n * Merge a freshly rendered managed `block` into `existing` file content.\n *\n * If `existing` already contains a managed span, that span (markers included)\n * is replaced in place. Otherwise the block is appended, separated from any\n * prior content by a blank line. User content outside the markers is never\n * touched, so repeated merges are idempotent for a stable block.\n */\nexport function mergeManagedBlock(existing: string, block: string): string {\n const start = existing.indexOf(MARKER_START);\n const end = existing.indexOf(MARKER_END);\n if (start !== -1 && end !== -1 && end > start) {\n // Replace the managed span, including the end marker and its trailing\n // newline if present, so the rendered block (which ends in \"\\n\") slots in\n // without accumulating blank lines.\n let after = end + MARKER_END.length;\n if (existing[after] === \"\\n\") after += 1;\n return existing.slice(0, start) + block + existing.slice(after);\n }\n if (existing.length === 0) return block;\n const separator = existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n return existing + separator + block;\n}\n","import {\n MANAGED_NOTICE,\n renderManagedBlock,\n} from \"../markers.js\";\nimport type { FileOutput, InstallContext, Skill } from \"../types.js\";\nimport type { Adapter } from \"./types.js\";\n\n/**\n * Render one skill as a delimited `## Skill: <name>` section: heading, the\n * one-line description, then the skill body.\n */\nfunction renderSection(skill: Skill): string {\n return `## Skill: ${skill.name}\\n\\n${skill.description}\\n\\n${skill.body.trimEnd()}`;\n}\n\n/**\n * Codex — merge strategy.\n *\n * Codex reads a single monolithic `AGENTS.md` per directory with no named or\n * on-demand skills, so every selected skill is folded into one `AGENTS.md` as\n * a delimited `## Skill: <name>` section. The whole set lives inside managed\n * markers, so the writer's merge mode updates only that span on re-runs and\n * never disturbs surrounding user content. Triggering degrades to always-on\n * guidance (flagged to the user elsewhere).\n */\nexport const codexAdapter: Adapter = {\n id: \"codex\",\n label: \"Codex\",\n translate(skills: Skill[], _ctx: InstallContext): FileOutput[] {\n const inner = [MANAGED_NOTICE, ...skills.map(renderSection)].join(\"\\n\\n\");\n return [\n {\n path: \"AGENTS.md\",\n contents: renderManagedBlock(inner),\n mode: \"merge\",\n },\n ];\n },\n};\n","import { MANAGED_NOTICE, renderManagedBlock } from \"../markers.js\";\nimport type { FileOutput, InstallContext, Skill } from \"../types.js\";\nimport type { Adapter } from \"./types.js\";\n\n/** Notice rendered at the top of each generated `.gemini/skills/<name>.md`. */\nconst SKILL_FILE_NOTICE =\n \"<!-- Managed by le-restaurant; edit the source skill, not this file. -->\";\n\n/** Relative import path for a skill's modular file, as used in `GEMINI.md`. */\nfunction importPath(skill: Skill): string {\n return `./.gemini/skills/${skill.name}.md`;\n}\n\n/** Render one skill into its own modular `.gemini/skills/<name>.md` file. */\nfunction renderSkillFile(skill: Skill): string {\n return `${SKILL_FILE_NOTICE}\\n\\n${skill.description}\\n\\n${skill.body.trimEnd()}\\n`;\n}\n\n/**\n * Gemini — import strategy.\n *\n * Gemini concatenates `GEMINI.md` hierarchically and supports `@file.md`\n * imports, so each skill is written to its own modular\n * `.gemini/skills/<name>.md` and referenced by an `@./.gemini/skills/<name>.md`\n * import line. The import lines live inside managed markers in `GEMINI.md`, so\n * re-runs update only that span; the modular skill files are fully tool-owned\n * (overwrite). Skills are always-on — no on-demand triggering.\n */\nexport const geminiAdapter: Adapter = {\n id: \"gemini\",\n label: \"Gemini\",\n translate(skills: Skill[], _ctx: InstallContext): FileOutput[] {\n const skillFiles: FileOutput[] = skills.map((skill) => ({\n path: `.gemini/skills/${skill.name}.md`,\n contents: renderSkillFile(skill),\n mode: \"overwrite\",\n }));\n\n const imports = skills.map((skill) => `@${importPath(skill)}`).join(\"\\n\");\n const inner = `${MANAGED_NOTICE}\\n\\n${imports}`;\n const geminiMd: FileOutput = {\n path: \"GEMINI.md\",\n contents: renderManagedBlock(inner),\n mode: \"merge\",\n };\n\n return [...skillFiles, geminiMd];\n },\n};\n","import type { WriteResult } from \"./writer.js\";\n\n/** Everything the post-install summary needs to render. */\nexport interface SummaryInput {\n /** The agent the skills were installed for. */\n agentId: \"claude\" | \"codex\" | \"gemini\";\n /** Human-readable agent label (e.g. \"Claude Code\"). */\n agentLabel: string;\n /** What the writer did with each file. */\n results: WriteResult[];\n}\n\n/**\n * Per-agent \"how to use the skills\" guidance.\n *\n * Claude Code keeps native on-demand triggering, so its skills behave as\n * designed. Codex and Gemini have no skill system — the translation degrades to\n * always-on guidance, which we must state honestly (see Architecture: \"Accept\n * fidelity loss on Codex/Gemini rather than fake a skill system\").\n */\nfunction usageNotes(agentId: SummaryInput[\"agentId\"]): string[] {\n switch (agentId) {\n case \"claude\":\n return [\n \"Skills install to .claude/skills/<name>/SKILL.md.\",\n \"Claude Code triggers each skill on demand — it picks the right one\",\n \"from its description when a matching task comes up. Nothing else to do.\",\n ];\n case \"codex\":\n return [\n \"Skills are folded into AGENTS.md as delimited sections, inside\",\n \"le-restaurant managed markers (edit outside the markers freely).\",\n \"Caveat: Codex has no skill system, so this guidance is ALWAYS-ON —\",\n \"there is no on-demand triggering. Every section is in context all the\",\n \"time, unlike Claude Code's on-demand skills.\",\n ];\n case \"gemini\":\n return [\n \"Each skill is written to .gemini/skills/<name>.md and imported from\",\n \"GEMINI.md via @import lines inside le-restaurant managed markers.\",\n \"Caveat: Gemini has no skill system, so this guidance is ALWAYS-ON —\",\n \"there is no on-demand triggering. Every imported skill is in context\",\n \"all the time, unlike Claude Code's on-demand skills.\",\n ];\n }\n}\n\n/**\n * Render the post-install summary: the target agent, every file written (with\n * the action taken), and how to use the installed skills — including the\n * always-on caveat for Codex/Gemini.\n */\nexport function renderSummary(input: SummaryInput): string {\n const lines: string[] = [];\n lines.push(`Installed ${input.results.length} file(s) for ${input.agentLabel}:`);\n for (const r of input.results) {\n lines.push(` ${r.action.padEnd(11)} ${r.path}`);\n }\n lines.push(\"\");\n lines.push(\"How to use:\");\n for (const note of usageNotes(input.agentId)) {\n lines.push(` ${note}`);\n }\n return lines.join(\"\\n\");\n}\n","import {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { randomBytes } from \"node:crypto\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { mergeManagedBlock } from \"./markers.js\";\nimport type { FileOutput, InstallContext } from \"./types.js\";\n\n/** What the writer did with a single output file. */\nexport type WriteAction = \"created\" | \"overwritten\" | \"merged\" | \"skipped\";\n\n/** A file the writer processed, for the post-install summary. */\nexport interface WriteResult {\n /** Path relative to the target dir (as the adapter emitted it). */\n path: string;\n /** The action the conflict policy resolved to. */\n action: WriteAction;\n}\n\n/**\n * Atomically write `contents` to `absolute`.\n *\n * The bytes are written to a sibling temp file first, then renamed over the\n * destination — `rename` is atomic on a single filesystem, so a reader never\n * sees a half-written file and a failure mid-write never clobbers the existing\n * target. On any error the temp file is removed and the error rethrown.\n */\nexport function atomicWriteFile(absolute: string, contents: string): void {\n mkdirSync(dirname(absolute), { recursive: true });\n const suffix = randomBytes(6).toString(\"hex\");\n const tmp = `${absolute}.${suffix}.tmp`;\n try {\n writeFileSync(tmp, contents);\n renameSync(tmp, absolute);\n } catch (err) {\n try {\n rmSync(tmp, { force: true });\n } catch {\n // best-effort cleanup; surface the original failure below\n }\n throw err;\n }\n}\n\n/**\n * Write a set of adapter outputs into the install target.\n *\n * Each `FileOutput.path` is resolved relative to `ctx.targetDir` and written\n * atomically. When a target file already exists, the `conflictPolicy` decides:\n * - `skip` — leave the existing file untouched;\n * - `overwrite` — replace it with the emitted contents;\n * - `merge` — for `mode: \"merge\"` outputs, fold the managed block into the\n * existing file (preserving user content outside the markers); for other\n * outputs, fall back to overwrite.\n *\n * A file that does not yet exist is always created regardless of policy.\n */\nexport function writeOutputs(\n outputs: FileOutput[],\n ctx: InstallContext,\n): WriteResult[] {\n const results: WriteResult[] = [];\n for (const output of outputs) {\n const absolute = isAbsolute(output.path)\n ? output.path\n : resolve(ctx.targetDir, output.path);\n\n if (!existsSync(absolute)) {\n atomicWriteFile(absolute, output.contents);\n results.push({ path: output.path, action: \"created\" });\n continue;\n }\n\n switch (ctx.conflictPolicy) {\n case \"skip\":\n results.push({ path: output.path, action: \"skipped\" });\n break;\n case \"merge\":\n if (output.mode === \"merge\") {\n const existing = readFileSync(absolute, \"utf8\");\n atomicWriteFile(absolute, mergeManagedBlock(existing, output.contents));\n results.push({ path: output.path, action: \"merged\" });\n } else {\n atomicWriteFile(absolute, output.contents);\n results.push({ path: output.path, action: \"overwritten\" });\n }\n break;\n default:\n atomicWriteFile(absolute, output.contents);\n results.push({ path: output.path, action: \"overwritten\" });\n break;\n }\n }\n return results;\n}\n","{\n \"name\": \"@long.dg/le-restaurant\",\n \"version\": \"0.1.1\",\n \"description\": \"Install the brigade of agent skills into your project, translated for your coding agent.\",\n \"type\": \"module\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"bin\": {\n \"le-restaurant\": \"dist/cli.js\"\n },\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"files\": [\n \"dist\",\n \"skills\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"typecheck\": \"tsc --noEmit\",\n \"check:sync\": \"vitest run test/check-skills-sync.test.ts\"\n },\n \"keywords\": [\n \"claude\",\n \"codex\",\n \"gemini\",\n \"skills\",\n \"cli\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/dglong/le-restaurant.git\"\n },\n \"homepage\": \"https://github.com/dglong/le-restaurant#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/dglong/le-restaurant/issues\"\n },\n \"author\": \"\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"@clack/prompts\": \"^1.6.0\",\n \"commander\": \"^14.0.1\",\n \"gray-matter\": \"^4.0.3\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^24.10.1\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.3\",\n \"vitest\": \"^3.2.4\"\n }\n}\n"],"mappings":"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","names":["readFileSync","readFileSync","readFileSync","dirname","resolve","dirname","resolve","readFileSync"]}
|