@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.
- package/README.md +1 -1
- package/dist/cli/tutorial/sm-tutorial/SKILL.md +149 -263
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/agents-hub/en/agents-hub.md +2 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/agents-hub/es/agents-hub.md +2 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/content-editor-style/en/content-editor-style.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/content-editor-style/es/content-editor-style.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/en/todo-bullet-agent.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/en/todo-bullet-command.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/en/todo-bullet-guideline.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/en/todo-bullet-guideline2.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/en/todo-bullet-skill.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/es/todo-bullet-agent.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/es/todo-bullet-command.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/es/todo-bullet-guideline.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/es/todo-bullet-guideline2.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/edits/todo-connectors/es/todo-bullet-skill.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/manifest.json +87 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/cli-external/en/link-validation/hijoA/note-with-external-link.md +10 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/cli-external/en/link-validation/hijoB/spec.md +11 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/cli-external/es/link-validation/hijoA/note-with-external-link.md +10 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/cli-external/es/link-validation/hijoB/spec.md +11 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/harness/en/__PROVIDER__/commands/publish.md +15 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/harness/en/__PROVIDER__/skills/check-links/SKILL.md +16 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/harness/es/__PROVIDER__/commands/publish.md +16 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/harness/es/__PROVIDER__/skills/check-links/SKILL.md +17 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/en/__PROVIDER__/agents/master-agent.md +14 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/en/__PROVIDER__/skills/master-skill/SKILL.md +18 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/en/notes/ideas.md +11 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/es/__PROVIDER__/agents/master-agent.md +15 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/es/__PROVIDER__/skills/master-skill/SKILL.md +18 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/master/es/notes/ideas.md +11 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/en/AGENTS.md +6 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/en/__PROVIDER__/agents/content-editor.md +21 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/en/docs/DEPLOY.md +11 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/en/docs/STYLE.md +20 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/en/public/index.html +5 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/es/AGENTS.md +7 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/es/__PROVIDER__/agents/content-editor.md +21 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/es/docs/DEPLOY.md +12 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/es/docs/STYLE.md +21 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/es/public/index.html +5 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/shared/CLAUDE.md +1 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/shared/package.json +6 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/portfolio/shared/server.js +11 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/__PROVIDER__/agents/demo-agent.md +18 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/__PROVIDER__/commands/demo-command.md +11 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/__PROVIDER__/skills/demo-skill/SKILL.md +16 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/notes/demo-guideline.md +16 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/notes/demo-guideline2.md +12 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/notes/private-credentials.md +11 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/en/notes/todo.md +9 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/__PROVIDER__/agents/demo-agent.md +18 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/__PROVIDER__/commands/demo-command.md +11 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/__PROVIDER__/skills/demo-skill/SKILL.md +16 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/notes/demo-guideline.md +16 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/notes/demo-guideline2.md +13 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/notes/private-credentials.md +11 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/prologue/es/notes/todo.md +9 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/universal/en/findings.md +10 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/universal/es/findings.md +10 -0
- package/dist/cli/tutorial/sm-tutorial/fixtures-data/sets/universal/shared/.skillmapignore +28 -0
- package/dist/cli/tutorial/sm-tutorial/references/_core.md +55 -40
- package/dist/cli/tutorial/sm-tutorial/references/_manifest.json +309 -0
- package/dist/cli/tutorial/sm-tutorial/references/_manifest.yml +1 -1
- package/dist/cli/tutorial/sm-tutorial/references/fixtures.md +94 -271
- package/dist/cli/tutorial/sm-tutorial/references/part-authoring.md +1 -2
- package/dist/cli/tutorial/sm-tutorial/references/part-cli.md +7 -30
- package/dist/cli/tutorial/sm-tutorial/references/part-connect-harness.md +12 -30
- package/dist/cli/tutorial/sm-tutorial/references/part-daily-loop.md +6 -4
- package/dist/cli/tutorial/sm-tutorial/references/part-fundamentals.md +14 -116
- package/dist/cli/tutorial/sm-tutorial/references/part-plugins.md +2 -3
- package/dist/cli/tutorial/sm-tutorial/references/part-project-kickoff.md +13 -69
- package/dist/cli/tutorial/sm-tutorial/scripts/fixtures.js +238 -0
- package/dist/cli/tutorial/sm-tutorial/scripts/lib/args.js +29 -0
- package/dist/cli/tutorial/sm-tutorial/scripts/lib/fixtures-manifest.js +32 -0
- package/dist/cli/tutorial/sm-tutorial/scripts/lib/io.js +37 -0
- package/dist/cli/tutorial/sm-tutorial/scripts/lib/manifest.js +24 -0
- package/dist/cli/tutorial/sm-tutorial/scripts/lib/paths.js +68 -0
- package/dist/cli/tutorial/sm-tutorial/scripts/state.js +262 -0
- package/dist/cli.js +7 -8
- package/dist/index.js +6 -4
- package/dist/kernel/index.js +6 -4
- package/dist/ui/chunk-FDBHGLTW.js +3 -0
- package/dist/ui/index.html +1 -1
- package/dist/ui/{main-HAPQJZOA.js → main-5GMGTLYQ.js} +1 -1
- package/package.json +5 -3
- 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.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
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
|
-
```
|
|
203
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|