@skill-map/cli 0.60.3 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/tutorial/sm-tutorial/SKILL.md +149 -263
  3. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/agents-hub/en/agents-hub.md +2 -0
  4. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/agents-hub/es/agents-hub.md +2 -0
  5. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/content-editor-style/en/content-editor-style.md +1 -0
  6. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/content-editor-style/es/content-editor-style.md +1 -0
  7. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/en/todo-bullet-agent.md +1 -0
  8. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/en/todo-bullet-command.md +1 -0
  9. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/en/todo-bullet-guideline.md +1 -0
  10. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/en/todo-bullet-guideline2.md +1 -0
  11. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/en/todo-bullet-skill.md +1 -0
  12. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/es/todo-bullet-agent.md +1 -0
  13. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/es/todo-bullet-command.md +1 -0
  14. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/es/todo-bullet-guideline.md +1 -0
  15. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/es/todo-bullet-guideline2.md +1 -0
  16. package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/es/todo-bullet-skill.md +1 -0
  17. package/dist/cli/tutorial/sm-tutorial/fixtures-data/manifest.json +87 -0
  18. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/cli-external/en/link-validation/hijoA/note-with-external-link.md +10 -0
  19. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/cli-external/en/link-validation/hijoB/spec.md +11 -0
  20. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/cli-external/es/link-validation/hijoA/note-with-external-link.md +10 -0
  21. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/cli-external/es/link-validation/hijoB/spec.md +11 -0
  22. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/harness/en/__PROVIDER__/commands/publish.md +15 -0
  23. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/harness/en/__PROVIDER__/skills/check-links/SKILL.md +16 -0
  24. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/harness/es/__PROVIDER__/commands/publish.md +16 -0
  25. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/harness/es/__PROVIDER__/skills/check-links/SKILL.md +17 -0
  26. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/en/__PROVIDER__/agents/master-agent.md +14 -0
  27. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/en/__PROVIDER__/skills/master-skill/SKILL.md +18 -0
  28. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/en/notes/ideas.md +11 -0
  29. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/es/__PROVIDER__/agents/master-agent.md +15 -0
  30. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/es/__PROVIDER__/skills/master-skill/SKILL.md +18 -0
  31. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/es/notes/ideas.md +11 -0
  32. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/en/AGENTS.md +6 -0
  33. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/en/__PROVIDER__/agents/content-editor.md +21 -0
  34. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/en/docs/DEPLOY.md +11 -0
  35. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/en/docs/STYLE.md +20 -0
  36. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/en/public/index.html +5 -0
  37. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/es/AGENTS.md +7 -0
  38. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/es/__PROVIDER__/agents/content-editor.md +21 -0
  39. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/es/docs/DEPLOY.md +12 -0
  40. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/es/docs/STYLE.md +21 -0
  41. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/es/public/index.html +5 -0
  42. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/shared/CLAUDE.md +1 -0
  43. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/shared/package.json +6 -0
  44. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/shared/server.js +11 -0
  45. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/__PROVIDER__/agents/demo-agent.md +18 -0
  46. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/__PROVIDER__/commands/demo-command.md +11 -0
  47. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/__PROVIDER__/skills/demo-skill/SKILL.md +16 -0
  48. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/notes/demo-guideline.md +16 -0
  49. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/notes/demo-guideline2.md +12 -0
  50. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/notes/private-credentials.md +11 -0
  51. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/notes/todo.md +9 -0
  52. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/__PROVIDER__/agents/demo-agent.md +18 -0
  53. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/__PROVIDER__/commands/demo-command.md +11 -0
  54. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/__PROVIDER__/skills/demo-skill/SKILL.md +16 -0
  55. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/notes/demo-guideline.md +16 -0
  56. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/notes/demo-guideline2.md +13 -0
  57. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/notes/private-credentials.md +11 -0
  58. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/notes/todo.md +9 -0
  59. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/universal/en/findings.md +10 -0
  60. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/universal/es/findings.md +10 -0
  61. package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/universal/shared/.skillmapignore +28 -0
  62. package/dist/cli/tutorial/sm-tutorial/references/_core.md +55 -40
  63. package/dist/cli/tutorial/sm-tutorial/references/_manifest.json +309 -0
  64. package/dist/cli/tutorial/sm-tutorial/references/_manifest.yml +1 -1
  65. package/dist/cli/tutorial/sm-tutorial/references/fixtures.md +94 -271
  66. package/dist/cli/tutorial/sm-tutorial/references/part-authoring.md +1 -2
  67. package/dist/cli/tutorial/sm-tutorial/references/part-cli.md +7 -30
  68. package/dist/cli/tutorial/sm-tutorial/references/part-connect-harness.md +12 -30
  69. package/dist/cli/tutorial/sm-tutorial/references/part-daily-loop.md +6 -4
  70. package/dist/cli/tutorial/sm-tutorial/references/part-fundamentals.md +14 -116
  71. package/dist/cli/tutorial/sm-tutorial/references/part-plugins.md +2 -3
  72. package/dist/cli/tutorial/sm-tutorial/references/part-project-kickoff.md +13 -69
  73. package/dist/cli/tutorial/sm-tutorial/scripts/fixtures.js +238 -0
  74. package/dist/cli/tutorial/sm-tutorial/scripts/lib/args.js +29 -0
  75. package/dist/cli/tutorial/sm-tutorial/scripts/lib/fixtures-manifest.js +32 -0
  76. package/dist/cli/tutorial/sm-tutorial/scripts/lib/io.js +37 -0
  77. package/dist/cli/tutorial/sm-tutorial/scripts/lib/manifest.js +24 -0
  78. package/dist/cli/tutorial/sm-tutorial/scripts/lib/paths.js +68 -0
  79. package/dist/cli/tutorial/sm-tutorial/scripts/state.js +262 -0
  80. package/dist/cli.js +7 -8
  81. package/dist/index.js +6 -4
  82. package/dist/kernel/index.js +6 -4
  83. package/dist/ui/chunk-FDBHGLTW.js +3 -0
  84. package/dist/ui/index.html +1 -1
  85. package/dist/ui/{main-HAPQJZOA.js → main-5GMGTLYQ.js} +1 -1
  86. package/package.json +5 -3
  87. package/dist/ui/chunk-HOIHEE6W.js +0 -3
@@ -6,7 +6,7 @@ The live-UI prologue: the tester runs `sm init`, opens the browser, and watches
6
6
 
7
7
  Agent background (do NOT render this as a separate context paragraph; the tester-facing version is folded into the message below): `sm init` creates a hidden `.skill-map/` folder in the cwd holding the database where skill-map stores what it learns about the project, and runs an initial scan (mandatory first step). Typing `sm` alone (no arguments) in an initialised dir then starts the UI server with the watcher built in (it is just an alias of `sm serve` with all defaults; the moment you need any flag you write `sm serve --flag ...` explicitly). One process, one terminal: it boots the server, scans the `.md` files, detects changes, and pushes events over WebSocket to the live UI. The next chapters all run against this same `sm` session, you boot it here and keep it alive through the `ignore` chapter.
8
8
 
9
- Expected: `.skill-map/skill-map.db` appears (plus config files), and the initial scan reports a small node / link count from the demo-agent fixture. `sm init` runs and exits; `sm` then starts the UI server and stays running. (Agent context, do not narrate: pre-flight's `.skillmapignore` keeps the tutorial's own files, `sm-tutorial.md` / `findings.md` / `tutorial-state.yml`, out of the scan; `sm init` leaves that file alone since it only writes when absent.)
9
+ Expected: `.skill-map/skill-map.db` appears (plus config files), and the initial scan reports a small node / link count from the demo-agent fixture. `sm init` runs and exits; `sm` then starts the UI server and stays running. (Agent context, do not narrate: pre-flight's `.skillmapignore` keeps the tutorial's own files, `sm-tutorial.md` / `findings.md` / `tutorial-state.json`, out of the scan; `sm init` leaves that file alone since it only writes when absent.)
10
10
 
11
11
  Give the tester the whole flow in ONE message with ONE confirmation, do NOT pause for the `sm init` output separately. Order matters: **lead with the browser setup**, then explain what the two commands do as you hand them over, then the command block, then the URL. Do NOT print the command block or an explanation paragraph before the browser instruction. Don't hardcode the URL, the verb logs the bound `http://host:port` after listen. Tell the tester:
12
12
 
@@ -38,99 +38,13 @@ Wait for confirmation. Mark `init`: done.
38
38
 
39
39
  Leave the browser open and the terminal with `sm` running. You create five more nodes **without any cross-fixture links** yet, pure standalone nodes, so the tester sees five new dots pop in. Three new **kinds** show up in this step (skill, command, markdown); the last two files are sibling `markdown` notes (`demo-guideline`, `demo-guideline2`) the hub in the `connectors` chapter reaches two ways, a bare mention that resolves to nothing (which lands as a broken reference, no arrow drawn) and the same handle plus `.md` that resolves to a real file (a solid arrow).
40
40
 
41
- Create these five files (with `Write`), exactly in this order. Per §Provider detection, **substitute `.claude/` with the detected `<provider_dir>` and skip files whose kind is not in the provider's supported set** (`agent-skills` / Antigravity: skip both `demo-agent` and `demo-command`, only the skill + the three markdown notes remain). Adjust the node count, the "five new nodes" message, and the file list shown to the tester in the sample below accordingly:
42
-
43
- 1. `.claude/skills/demo-skill/SKILL.md` (kind: skill):
44
- ```markdown
45
- ---
46
- name: demo-skill
47
- description: |
48
- Example skill that walks a file and returns a Markdown report.
49
- Showcases the `skill` kind in the demo map.
50
- ---
51
-
52
- # demo-skill
53
-
54
- This skill walks a file and returns a report. Will be wired up
55
- to the rest of the demo fixture in the next sub-step.
56
-
57
- ## Steps
58
- 1. Read the `target`.
59
- 2. Validate the frontmatter against the schemas.
60
- 3. Generate the report.
61
- ```
62
-
63
- 2. `.claude/commands/demo-command.md` (kind: command):
64
- ```markdown
65
- ---
66
- name: demo-command
67
- description: |
68
- Example slash command that wraps the demo-skill. Showcases the
69
- `command` kind.
70
- ---
71
-
72
- # demo-command
73
-
74
- Quick entry point for running the demo flow on a target file.
75
- Connectors land in the next sub-step.
76
- ```
77
-
78
- 3. `notes/todo.md`, classified as `kind: markdown` today
79
- (the catch-all for `.md` files outside the
80
- skill / agent / command folders):
81
- ```markdown
82
- ---
83
- name: Demo TODO list
84
- description: |
85
- Live list of things to review in the demo. Will become the
86
- hub that points to the rest of the fixture in the next
87
- sub-step.
88
- ---
89
-
90
- # Pending
91
- ```
92
-
93
- 4. `notes/demo-guideline.md`, second `kind: markdown` node, reached
94
- in the `connectors` chapter by a bare `@`-mention that resolves to
95
- no agent, so it surfaces as a broken reference instead of a drawn
96
- connector:
97
- ```markdown
98
- ---
99
- name: demo-guideline
100
- description: |
101
- Static reference notes the rest of the demo points at. The hub
102
- reaches it with a bare `@`-mention, which resolves to no agent,
103
- so skill-map flags it as a broken reference (0.50) instead of
104
- drawing an arrow.
105
- ---
106
-
107
- # Demo Guideline
108
-
109
- Conventions the demo fixture follows:
110
-
111
- - Names match the file basename.
112
- - Frontmatter `description` is short and human-readable.
113
- - Body stays minimal, only what's needed to teach the kind.
114
- ```
115
-
116
- 5. `notes/demo-guideline2.md`, a sibling `markdown` node identical
117
- to `demo-guideline`, reached by the same handle plus a `.md`
118
- extension (`@demo-guideline2.md`), which makes it a file reference
119
- that resolves to this node and lands at full confidence:
120
- ```markdown
121
- ---
122
- name: demo-guideline2
123
- description: |
124
- Sibling of demo-guideline. The hub reaches it with an
125
- `@`-mention that carries the `.md` extension, so the link
126
- resolves to this file and lands certain (1.00).
127
- ---
128
-
129
- # Demo Guideline 2
130
-
131
- Same conventions as demo-guideline; it exists so the hub can
132
- reach it with a resolved reference instead of a bare mention.
133
- ```
41
+ Lay these five files in one go (their content + translation live in `fixtures-data/`). The script resolves `__PROVIDER__` and auto-skips kinds the provider does not claim (`agent-skills` / Antigravity: both `demo-agent` and `demo-command` fold away, only the skill + the three markdown notes remain), so read the actual node count from the summary's `nodeCount`. Backstage (silent):
42
+
43
+ ```
44
+ node .claude/skills/sm-tutorial/scripts/fixtures.js lay prologue --only "__PROVIDER__/skills/demo-skill/SKILL.md,__PROVIDER__/commands/demo-command.md,notes/todo.md,notes/demo-guideline.md,notes/demo-guideline2.md" --provider <provider> --lang <lang>
45
+ ```
46
+
47
+ Adjust the node count, the "five new nodes" message, and the file list shown to the tester in the sample below to match the laid set.
134
48
 
135
49
  Tell the tester:
136
50
 
@@ -195,16 +109,10 @@ You edit `notes/todo.md` so it becomes the **hub** that points to each of the ot
195
109
 
196
110
  Five bullets, three kinds: `invokes` and `mentions` each appear twice, `references` once. The last two bullets are the resolution lesson: a bare `@demo-guideline` mention (which resolves to no agent, so it lands as a broken reference and draws no arrow) next to `@demo-guideline2.md`, the same handle shape plus a `.md` extension that points at a real sibling file (so it resolves and draws a solid arrow). Two separate nodes, one broken and one resolved. Five bullets but only four arrows on the canvas.
197
111
 
198
- Apply with `Edit` on `notes/todo.md` (do not rewrite the file). Per §Provider detection, **substitute `.claude/` with the detected `<provider_dir>` and drop any bullet whose target node was not created in the `kinds` chapter** (on `agent-skills` / Antigravity there is no agent and no command → skip the `@demo-agent` and `/demo-command` bullets; the two guideline bullets stay, so the resolution contrast, broken mention 0.50 (no arrow drawn) vs resolved reference 1.00 (solid arrow), is intact on those providers too).
199
-
200
- **Edit `notes/todo.md`**: append these bullets after the `# Pending` heading:
112
+ Apply the hub bullets (their content + translation live in `fixtures-data/`). The edit appends after the `# Pending` heading; the script drops any bullet whose target kind the provider does not claim (on `agent-skills` / Antigravity there is no agent and no command → the `@demo-agent` and `/demo-command` bullets fold away; the two guideline bullets stay, so the resolution contrast, broken mention 0.50 (no arrow drawn) vs resolved reference 1.00 (solid arrow), is intact on those providers too). Backstage (silent):
201
113
 
202
- ```markdown
203
- - [ ] Brief @demo-agent on the rough edges.
204
- - [ ] Run /demo-command before publishing.
205
- - [ ] Trigger /demo-skill when the input lands.
206
- - [ ] Ping @demo-guideline if the conventions change.
207
- - [ ] Ping @demo-guideline2.md if the conventions change.
114
+ ```
115
+ node .claude/skills/sm-tutorial/scripts/fixtures.js edit todo-connectors --provider <provider> --lang <lang>
208
116
  ```
209
117
 
210
118
  Tell the tester:
@@ -363,20 +271,10 @@ Earlier chapters showed the watcher picking up new files and edits (yours and th
363
271
 
364
272
  **The agent seeds the file (no tester action, no separate pause).**
365
273
 
366
- `Write` `notes/private-credentials.md`, kind `markdown`, simulates a file the tester would never want surfacing publicly:
367
-
368
- ```markdown
369
- ---
370
- name: private-credentials
371
- description: |
372
- Personal API tokens, exists in the repo but should not show
373
- up in skill-map's map. Demonstrates the .skillmapignore
374
- flow.
375
- ---
274
+ Lay `notes/private-credentials.md`, kind `markdown`, which simulates a file the tester would never want surfacing publicly (its content + translation live in `fixtures-data/`). Backstage (silent):
376
275
 
377
- # Private
378
-
379
- API_TOKEN: example-not-real
276
+ ```
277
+ node .claude/skills/sm-tutorial/scripts/fixtures.js lay prologue --only "notes/private-credentials.md" --provider <provider> --lang <lang>
380
278
  ```
381
279
 
382
280
  It lands in the map as a seventh node (`notes/private-credentials`); the watcher sees it like any other `.md`. Do NOT pause to confirm the appearance, it folds into the single vanish confirmation at the end of this step.
@@ -17,7 +17,7 @@ Before announcing the first step, verify the fixture is initialised
17
17
  `backstage-init` preflight ran `sm init --no-scan` to provision it;
18
18
  the universal `.skillmapignore` from pre-flight keeps the tutorial's
19
19
  own files out of the scan. If any of that is missing, surface the
20
- bootstrap mismatch ("tutorial-state.yml says we are running, but
20
+ bootstrap mismatch ("tutorial-state.json says we are running, but
21
21
  the bootstrap is missing. Re-run the tutorial from an empty dir or
22
22
  restore the files.") and stop.
23
23
 
@@ -189,8 +189,7 @@ Mark `tour-3-explore: done`.
189
189
  >
190
190
  > Anything weird worth logging? If not, back to the menu.
191
191
 
192
- Mark the plugins chapters done in `tutorial-state.yml`, update the
193
- matching harness task, return to the menu in `SKILL.md`.
192
+ Mark the chapters done (rule #4) and return to the menu in `SKILL.md`.
194
193
 
195
194
  ## Reference: how `sm` decides what to load
196
195
 
@@ -74,7 +74,9 @@ repo does). That one-line pointer is a real `references` link (the
74
74
  mention), the tester's first connector on the real project.
75
75
 
76
76
  Tell the tester to create the file themselves (it is their project's
77
- file, Inviolable rule #2):
77
+ file, Inviolable rule #2). Backstage, get the content:
78
+ `node .claude/skills/sm-tutorial/scripts/fixtures.js cat portfolio --file "CLAUDE.md" --provider <provider> --lang <lang>`,
79
+ then render it in the fenced block the tester copies:
78
80
 
79
81
  > Create a file called `CLAUDE.md` at the project root with exactly
80
82
  > this content:
@@ -98,37 +100,13 @@ Wait for confirmation. Mark `manual`: done.
98
100
 
99
101
  ## Chapter `first-agent` - The first harness agent (~2 min)
100
102
 
101
- **Context**: the harness's job is content creation. Its first member
102
- is `content-editor`, an agent that writes the site's HTML pages. Here
103
- we only create it (the cross-references come in the next part).
104
-
105
- `Write` `.claude/agents/content-editor.md` (substitute
106
- `<provider_dir>` per `_core.md`; on `agent-skills` / Antigravity,
107
- which has no `agent` kind, create a `skill` instead and adjust the
108
- prose):
109
-
110
- ```markdown
111
- ---
112
- name: content-editor
113
- description: |
114
- Writes and edits the portfolio's pages. Reads a brief, follows the
115
- style guide, and emits the HTML into public/.
116
- tools: [Read, Write]
117
- model: sonnet
118
- ---
119
-
120
- # content-editor
121
-
122
- Turns a short brief into a finished portfolio page.
123
-
124
- ## How to write a page
125
- 1. Read the style guide and the shared stylesheet in public/.
126
- 2. Write one HTML file under public/, named after the page (a projects page becomes `public/projects.html`).
127
- 3. Start from `<!doctype html>`, link the stylesheet with `<link rel="stylesheet" href="/style.css">`, and set a `<title>`.
128
- 4. Use one `<h1>`, group sections under `<h2>`, and reuse the shared header, nav, and footer so every page matches.
129
- 5. Add a link back to Home, and link the new page from the home nav.
130
-
131
- Rules: plain static HTML, no framework, no client JS, one page per file.
103
+ Lay the first harness agent (its content + translation live in
104
+ `fixtures-data/`). The script resolves `__PROVIDER__`; on
105
+ `agent-skills` / Antigravity, which has no `agent` kind, adjust the
106
+ prose to the skill the set lays there. Backstage (silent):
107
+
108
+ ```
109
+ node .claude/skills/sm-tutorial/scripts/fixtures.js lay portfolio --only "__PROVIDER__/agents/content-editor.md" --provider <provider> --lang <lang>
132
110
  ```
133
111
 
134
112
  Tell the tester:
@@ -149,45 +127,11 @@ nodes. Now name them on the real project, and add the two markdown
149
127
  docs the harness references later (the style guide and the deploy
150
128
  runbook), so the Daily Loop's maintenance beats have something to point at.
151
129
 
152
- `Write` two docs (markdown kind):
153
-
154
- `docs/STYLE.md`:
155
- ```markdown
156
- ---
157
- name: style-guide
158
- description: |
159
- Writing and markup conventions every portfolio page follows.
160
- ---
161
-
162
- # Style guide
163
-
164
- ## Voice
165
- - Short, plain sentences. No marketing fluff.
130
+ Lay the two markdown docs the harness references later (their content
131
+ + translation live in `fixtures-data/`). Backstage (silent):
166
132
 
167
- ## Structure
168
- - One H1 per page; sections under H2.
169
- - Every page shares the same header, nav, and footer.
170
- - Every page links back to Home.
171
-
172
- ## Markup
173
- - Plain static HTML: no framework, no client JS.
174
- - Link the shared stylesheet `/style.css` in every page head.
175
- - Use semantic tags: header, nav, main, footer.
176
133
  ```
177
-
178
- `docs/DEPLOY.md`:
179
- ```markdown
180
- ---
181
- name: deploy-runbook
182
- description: |
183
- How the portfolio gets published once the pages are written.
184
- ---
185
-
186
- # Deploy runbook
187
-
188
- 1. Generate or update the pages in public/.
189
- 2. Run the link check and fix anything it reports.
190
- 3. Start the server with `node server.js`, then open the site in your browser.
134
+ node .claude/skills/sm-tutorial/scripts/fixtures.js lay portfolio --only "docs/STYLE.md,docs/DEPLOY.md" --provider <provider> --lang <lang>
191
135
  ```
192
136
 
193
137
  Tell the tester:
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * fixtures.js, the sm-tutorial fixture engine.
4
+ *
5
+ * Lays / edits / seeds / clears the demo, portfolio, harness, master,
6
+ * and cli-external fixtures from `fixtures-data/` instead of having the
7
+ * agent reproduce file content verbatim. Zero-dep, Node 24+, ESM.
8
+ * Backstage machinery (same class as `Write`), NOT a teaching `sm` verb.
9
+ *
10
+ * Content lives under `fixtures-data/sets/<set>/{shared,<lang>}/` with
11
+ * the provider dir as a literal `__PROVIDER__` path segment; only the
12
+ * PATH is rewritten per provider (content is verbatim, relative links
13
+ * are depth-correct in the claude layout). Kind is derived from the
14
+ * path (paths.js#kindForPath); files whose kind the provider does not
15
+ * claim are skipped.
16
+ *
17
+ * Verbs (all but `cat` print one JSON line to stdout):
18
+ * lay <set> [--provider p] [--lang l]
19
+ * edit <id> [--provider p] [--lang l]
20
+ * seed <snap> [--provider p] [--lang l]
21
+ * clear <footprint> [--provider p]
22
+ * cat <set> --file <relpath> [--provider p] [--lang l] (raw content to stdout)
23
+ *
24
+ * `clear` removes a named footprint (part-entry resets); the full
25
+ * start-over wipe is `state.js wipe` (cwd-guarded + confirmation-gated).
26
+ */
27
+
28
+ import { join, dirname, relative } from 'node:path';
29
+ import {
30
+ readFileSync, writeFileSync, mkdirSync, rmSync, rmdirSync, readdirSync, statSync, existsSync,
31
+ } from 'node:fs';
32
+
33
+ import { parseArgs } from './lib/args.js';
34
+ import { emit, succeed, die } from './lib/io.js';
35
+ import { loadFixturesManifest, fixturesDir, resolveFootprint } from './lib/fixtures-manifest.js';
36
+ import {
37
+ providerDir, kindsFor, resolveTargetPath, kindForPath, PROVIDER_TOKEN,
38
+ } from './lib/paths.js';
39
+
40
+ function opts(args) {
41
+ return { provider: args.flags.provider ?? 'claude', lang: args.flags.lang ?? 'en' };
42
+ }
43
+
44
+ function pdirAndKinds(o) {
45
+ return { pdir: providerDir(o.provider), kinds: kindsFor(o.provider) };
46
+ }
47
+
48
+ /** Recursively list files under `root`, relative to `root`, sorted. */
49
+ function walk(root) {
50
+ const out = [];
51
+ if (!existsSync(root)) return out;
52
+ const stack = [root];
53
+ while (stack.length) {
54
+ const cur = stack.pop();
55
+ for (const entry of readdirSync(cur)) {
56
+ const full = join(cur, entry);
57
+ if (statSync(full).isDirectory()) stack.push(full);
58
+ else out.push(relative(root, full));
59
+ }
60
+ }
61
+ return out.sort();
62
+ }
63
+
64
+ function writeFileEnsuring(abs, content) {
65
+ mkdirSync(dirname(abs), { recursive: true });
66
+ writeFileSync(abs, content);
67
+ }
68
+
69
+ /**
70
+ * Lay one set's files for the given lang + provider. `only` (a Set of
71
+ * token-form relpaths) restricts to those files, for the prologue's
72
+ * progressive reveal where each chapter lands its own nodes.
73
+ */
74
+ function laySet(manifest, set, o, only = null) {
75
+ const { kinds } = pdirAndKinds(o);
76
+ if (!manifest.sets.includes(set)) die('unknown-set', `set '${set}' is not in the manifest.`);
77
+ const base = join(fixturesDir(), 'sets', set);
78
+ const langDir = existsSync(join(base, o.lang)) ? o.lang : (manifest.defaultLang ?? 'en');
79
+ const laid = [];
80
+ const skipped = [];
81
+ // Lang-invariant `shared/` tier first, then the language tier.
82
+ for (const tier of ['shared', langDir]) {
83
+ const tierRoot = join(base, tier);
84
+ for (const rel of walk(tierRoot)) {
85
+ // `rel` is the token-form target path (e.g. __PROVIDER__/agents/x.md).
86
+ if (only && !only.has(rel)) continue;
87
+ const kind = kindForPath(rel);
88
+ if (!kinds.has(kind)) { skipped.push({ path: rel, kind }); continue; }
89
+ const target = resolveTargetPath(rel, o.provider);
90
+ writeFileEnsuring(join(process.cwd(), target), readFileSync(join(tierRoot, rel)));
91
+ laid.push(target);
92
+ }
93
+ }
94
+ return { laid, skipped };
95
+ }
96
+
97
+ const nodeCount = (paths) => paths.filter((p) => p.endsWith('.md')).length;
98
+
99
+ /** Resolve a fragment file path, falling back to the default language. */
100
+ function fragmentPath(manifest, id, lang, file) {
101
+ const langTry = join(fixturesDir(), 'edits', id, lang, file);
102
+ return existsSync(langTry) ? langTry : join(fixturesDir(), 'edits', id, manifest.defaultLang ?? 'en', file);
103
+ }
104
+
105
+ /** Apply one manifest edit (append fragments) honoring requiresKind. */
106
+ function applyEdit(manifest, id, o) {
107
+ const { kinds } = pdirAndKinds(o);
108
+ const def = manifest.edits?.[id];
109
+ if (!def) die('unknown-edit', `edit '${id}' is not in the manifest.`);
110
+ const target = resolveTargetPath(def.target, o.provider);
111
+ // Skip the whole edit if the target's own kind is unsupported (e.g. a
112
+ // content-editor agent does not exist on agent-skills).
113
+ if (!kinds.has(kindForPath(def.target))) return { target, appended: [], skipped: true };
114
+ const targetAbs = join(process.cwd(), target);
115
+ if (!existsSync(targetAbs)) die('edit-target-missing', `edit '${id}' target not found: ${target}`);
116
+
117
+ const fragments = (def.fragments ?? []).filter((f) => !f.requiresKind || kinds.has(f.requiresKind));
118
+ if (fragments.length === 0) return { target, appended: [] };
119
+
120
+ let content = readFileSync(targetAbs, 'utf8');
121
+ if (!content.endsWith('\n')) content += '\n';
122
+ if (def.prefix) content += def.prefix;
123
+ const appended = [];
124
+ for (const frag of fragments) {
125
+ content += readFileSync(fragmentPath(manifest, id, o.lang, frag.file), 'utf8');
126
+ appended.push(frag.file);
127
+ }
128
+ writeFileSync(targetAbs, content);
129
+ return { target, appended };
130
+ }
131
+
132
+ const VERBS = {
133
+ lay(args) {
134
+ const set = args.positional[0];
135
+ if (!set) die('bad-args', 'usage: lay <set> [--only a,b] [--provider p] [--lang l]');
136
+ const o = opts(args);
137
+ const only = args.flags.only ? new Set(String(args.flags.only).split(',')) : null;
138
+ const manifest = loadFixturesManifest();
139
+ const { laid, skipped } = laySet(manifest, set, o, only);
140
+ succeed({ laid, skipped, nodeCount: nodeCount(laid), needsProvision: !existsSync(join(process.cwd(), '.skill-map')) });
141
+ },
142
+
143
+ edit(args) {
144
+ const id = args.positional[0];
145
+ if (!id) die('bad-args', 'usage: edit <edit-id> [--provider p] [--lang l]');
146
+ const manifest = loadFixturesManifest();
147
+ succeed(applyEdit(manifest, id, opts(args)));
148
+ },
149
+
150
+ seed(args) {
151
+ const snap = args.positional[0];
152
+ if (!snap) die('bad-args', 'usage: seed <snapshot> [--provider p] [--lang l]');
153
+ const o = opts(args);
154
+ const manifest = loadFixturesManifest();
155
+ const def = manifest.seeds?.[snap];
156
+ if (!def) die('unknown-seed', `seed '${snap}' is not in the manifest.`);
157
+ const laid = [];
158
+ const skipped = [];
159
+ for (const set of def.lay ?? []) {
160
+ const r = laySet(manifest, set, o);
161
+ laid.push(...r.laid);
162
+ skipped.push(...r.skipped);
163
+ }
164
+ const edits = [];
165
+ for (const id of def.edits ?? []) edits.push(applyEdit(manifest, id, o));
166
+ const dropped = [];
167
+ for (const rel of def.drop ?? []) {
168
+ const resolved = resolveTargetPath(rel, o.provider);
169
+ const abs = join(process.cwd(), resolved);
170
+ if (existsSync(abs)) { rmSync(abs, { recursive: true, force: true }); dropped.push(resolved); }
171
+ }
172
+ const present = laid.filter((p) => !dropped.includes(p));
173
+ succeed({
174
+ laid, skipped, edits, dropped,
175
+ nodeCount: nodeCount(present),
176
+ needsProvision: !existsSync(join(process.cwd(), '.skill-map')),
177
+ });
178
+ },
179
+
180
+ clear(args) {
181
+ const name = args.positional[0];
182
+ if (!name) die('bad-args', 'usage: clear <footprint> [--provider p]');
183
+ const manifest = loadFixturesManifest();
184
+ if (!manifest.footprints?.[name]) die('unknown-footprint', `footprint '${name}' is not in the manifest.`);
185
+ const o = opts(args);
186
+ const { pdir } = pdirAndKinds(o);
187
+ const deleted = [];
188
+ for (const rel of resolveFootprint(manifest, name, o.provider)) {
189
+ const abs = join(process.cwd(), rel);
190
+ if (existsSync(abs)) { rmSync(abs, { recursive: true, force: true }); deleted.push(rel); }
191
+ }
192
+ rmdirEmptyParents(pdir);
193
+ succeed({ deleted });
194
+ },
195
+
196
+ cat(args) {
197
+ const set = args.positional[0];
198
+ const file = args.flags.file;
199
+ if (!set || typeof file !== 'string') {
200
+ emit({ ok: false, code: 'bad-args', error: 'usage: cat <set> --file <relpath> [--provider p] [--lang l]' });
201
+ process.exit(1);
202
+ }
203
+ const o = opts(args);
204
+ const { pdir } = pdirAndKinds(o);
205
+ const manifest = loadFixturesManifest();
206
+ const base = join(fixturesDir(), 'sets', set);
207
+ const langDir = existsSync(join(base, o.lang)) ? o.lang : (manifest.defaultLang ?? 'en');
208
+ // Accept a resolved path (`.claude/...`) or token form; normalise to token.
209
+ const tokenForm = file.startsWith(`${pdir}/`) ? PROVIDER_TOKEN + file.slice(pdir.length) : file;
210
+ const found = [join(base, langDir, tokenForm), join(base, 'shared', tokenForm)].find((c) => existsSync(c));
211
+ if (!found) {
212
+ emit({ ok: false, code: 'not-found', error: `file '${file}' not found in set '${set}'.` });
213
+ process.exit(1);
214
+ }
215
+ process.stdout.write(readFileSync(found, 'utf8'));
216
+ process.exit(0);
217
+ },
218
+ };
219
+
220
+ function rmdirEmptyParents(pdir) {
221
+ const candidates = ['notes', 'docs', 'public', `${pdir}/agents`, `${pdir}/skills`, `${pdir}/commands`, pdir];
222
+ for (const rel of candidates) {
223
+ try { rmdirSync(join(process.cwd(), rel)); } catch { /* not empty or missing */ }
224
+ }
225
+ }
226
+
227
+ function main() {
228
+ const [verb, ...rest] = process.argv.slice(2);
229
+ const handler = VERBS[verb];
230
+ if (!handler) {
231
+ die('unknown-verb', `unknown verb '${verb ?? ''}'; expected one of ${Object.keys(VERBS).join(', ')}`);
232
+ }
233
+ handler(parseArgs(rest));
234
+ }
235
+
236
+ main();
237
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="7deeaba5-0c50-5d6e-93cc-ad31c6b5a37c")}catch(e){}}();
238
+ //# debugId=7deeaba5-0c50-5d6e-93cc-ad31c6b5a37c
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Minimal zero-dep argv parser shared by the tutorial scripts. Splits
3
+ * `--key value` pairs and bare `--flag` booleans from positionals.
4
+ * No external CLI framework (the script ships to a tester cwd with no
5
+ * node_modules).
6
+ */
7
+
8
+ export function parseArgs(argv) {
9
+ const positional = [];
10
+ const flags = {};
11
+ for (let i = 0; i < argv.length; i++) {
12
+ const a = argv[i];
13
+ if (a.startsWith('--')) {
14
+ const key = a.slice(2);
15
+ const next = argv[i + 1];
16
+ if (next === undefined || next.startsWith('--')) {
17
+ flags[key] = true;
18
+ } else {
19
+ flags[key] = next;
20
+ i++;
21
+ }
22
+ } else {
23
+ positional.push(a);
24
+ }
25
+ }
26
+ return { positional, flags };
27
+ }
28
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="11379635-8a15-5ac2-a59e-af05707a526b")}catch(e){}}();
29
+ //# debugId=11379635-8a15-5ac2-a59e-af05707a526b
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Reads `fixtures-data/manifest.json`, the index for the fixture
3
+ * engine (sets, footprints, edits, seeds). Shared by `fixtures.js`
4
+ * (lay / edit / seed / clear) and `state.js` (wipe reads footprints,
5
+ * so the per-fixture on-disk reach lives in ONE place).
6
+ */
7
+
8
+ import { dirname, resolve } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { readJson } from './io.js';
11
+ import { resolveTargetPath } from './paths.js';
12
+
13
+ const LIB_DIR = dirname(fileURLToPath(import.meta.url));
14
+ // lib/ -> scripts/ -> sm-tutorial/ -> fixtures-data/
15
+ const FIXTURES_DIR = resolve(LIB_DIR, '..', '..', 'fixtures-data');
16
+ const MANIFEST = resolve(FIXTURES_DIR, 'manifest.json');
17
+
18
+ export function fixturesDir() {
19
+ return FIXTURES_DIR;
20
+ }
21
+
22
+ export function loadFixturesManifest() {
23
+ return readJson(MANIFEST);
24
+ }
25
+
26
+ /** Footprint paths for a named fixture, resolved for the provider. */
27
+ export function resolveFootprint(manifest, name, provider) {
28
+ const fp = manifest.footprints?.[name] ?? [];
29
+ return fp.map((p) => resolveTargetPath(p, provider));
30
+ }
31
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="22500f53-280a-5e92-b22b-2608486d3d0c")}catch(e){}}();
32
+ //# debugId=22500f53-280a-5e92-b22b-2608486d3d0c
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Zero-dep IO + JSON-envelope helpers shared by the tutorial scripts.
3
+ * Every verb prints ONE JSON line to stdout: `{ ok: true, ... }` with
4
+ * exit 0, or `{ ok: false, code, error }` with a non-zero exit. The
5
+ * orchestrating agent parses stdout; it never hand-edits state.
6
+ */
7
+
8
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
9
+
10
+ export function exists(p) {
11
+ return existsSync(p);
12
+ }
13
+
14
+ export function readJson(p) {
15
+ return JSON.parse(readFileSync(p, 'utf8'));
16
+ }
17
+
18
+ /** Write pretty JSON with a trailing LF newline (project line-ending rule). */
19
+ export function writeJson(p, obj) {
20
+ writeFileSync(p, JSON.stringify(obj, null, 2) + '\n');
21
+ }
22
+
23
+ export function emit(obj) {
24
+ process.stdout.write(JSON.stringify(obj) + '\n');
25
+ }
26
+
27
+ export function succeed(obj) {
28
+ emit({ ok: true, ...obj });
29
+ process.exit(0);
30
+ }
31
+
32
+ export function die(code, error) {
33
+ emit({ ok: false, code, error });
34
+ process.exit(1);
35
+ }
36
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="6f5406ec-7563-5466-a639-eee006c56913")}catch(e){}}();
37
+ //# debugId=6f5406ec-7563-5466-a639-eee006c56913
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Reads the generated book ToC sidecar (`references/_manifest.json`)
3
+ * that the repo codegen emits from `_manifest.yml`. Zero-dep: plain
4
+ * `JSON.parse`. The `.yml` is never parsed at runtime (its bespoke
5
+ * chapter shorthand is not standard YAML).
6
+ */
7
+
8
+ import { dirname, resolve } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { readJson } from './io.js';
11
+
12
+ const LIB_DIR = dirname(fileURLToPath(import.meta.url));
13
+ // lib/ -> scripts/ -> sm-tutorial/ -> references/_manifest.json
14
+ const MANIFEST = resolve(LIB_DIR, '..', '..', 'references', '_manifest.json');
15
+
16
+ export function loadManifest() {
17
+ return readJson(MANIFEST);
18
+ }
19
+
20
+ export function findPart(manifest, id) {
21
+ return manifest.parts.find((p) => p.id === id) ?? null;
22
+ }
23
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="b51b0ac6-99f1-5d52-8cac-40f122545c4a")}catch(e){}}();
24
+ //# debugId=b51b0ac6-99f1-5d52-8cac-40f122545c4a