@natjswenson/devlog 0.9.0 → 0.11.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/SKILL.md +151 -82
- package/bin/devlog.js +160 -29
- package/examples/react/useDevLogEntries.js +2 -0
- package/image-style/icons.md +3 -2
- package/image-style/style-guide.example.md +165 -28
- package/lib/assemble_post.mjs +71 -0
- package/lib/config_ops.mjs +1 -0
- package/lib/core.mjs +12 -0
- package/lib/cover_gen.mjs +6 -1
- package/lib/lint_post.mjs +47 -1
- package/lib/migrate_entry_numbers.mjs +116 -0
- package/lib/publish_entry.mjs +255 -14
- package/lib/scan.mjs +101 -18
- package/package.json +1 -1
- package/skill-invariants.json +21 -1
package/SKILL.md
CHANGED
|
@@ -16,7 +16,7 @@ actual work, backed by cited reputable sources, and including **gotchas earned f
|
|
|
16
16
|
experience**. The release is the springboard; the teaching is the point.
|
|
17
17
|
|
|
18
18
|
The deterministic work (release discovery, post linting, manifest updates, config edits)
|
|
19
|
-
is done by the `@natjswenson/devlog` CLI — invoke it as `npx -y @natjswenson/devlog <cmd>`.
|
|
19
|
+
is done by the `@natjswenson/devlog` CLI — invoke it as `npx -y @natjswenson/devlog@latest <cmd>`.
|
|
20
20
|
Every agent-facing command prints JSON.
|
|
21
21
|
|
|
22
22
|
## Decide which mode you're in
|
|
@@ -38,10 +38,14 @@ Map the user's request onto the CLI — never hand-edit `config.json`:
|
|
|
38
38
|
|
|
39
39
|
| Intent | Command |
|
|
40
40
|
|---|---|
|
|
41
|
-
| Show config | `npx -y @natjswenson/devlog config --json` |
|
|
42
|
-
| Add a project | `npx -y @natjswenson/devlog add-project --yes --path <abs-path> [--key K] [--remote O/R] [--label L] [--tag-prefix P] [--path-filter F] [--private]` |
|
|
43
|
-
| Remove a project | `npx -y @natjswenson/devlog remove-project <key> --yes` |
|
|
44
|
-
| Change a setting | `npx -y @natjswenson/devlog set <field> <value>` (settable: `targetRepo`, `branch`, `gitAuthor`, `githubUser`, `voicePath`, `deepDive.minSources`, `deepDive.topicDomains`) |
|
|
41
|
+
| Show config | `npx -y @natjswenson/devlog@latest config --json` |
|
|
42
|
+
| Add a project | `npx -y @natjswenson/devlog@latest add-project --yes --path <abs-path> [--key K] [--remote O/R] [--label L] [--tag-prefix P] [--path-filter F] [--private]` |
|
|
43
|
+
| Remove a project | `npx -y @natjswenson/devlog@latest remove-project <key> --yes` |
|
|
44
|
+
| Change a setting | `npx -y @natjswenson/devlog@latest set <field> <value>` (settable: `targetRepo`, `branch`, `targetDir`, `gitAuthor`, `githubUser`, `voicePath`, `deepDive.minSources`, `deepDive.topicDomains`) |
|
|
45
|
+
|
|
46
|
+
`targetDir` is the subdirectory of `targetRepo` that holds the devlog content tree
|
|
47
|
+
(e.g. `content/devlog` when the target is the site repo itself); unset/empty means the
|
|
48
|
+
repo root. Set it with `set targetDir content/devlog`, clear it with `set targetDir ''`.
|
|
45
49
|
|
|
46
50
|
For **add-project**: resolve the path first (the repo the user named, or the cwd), then
|
|
47
51
|
detect what the CLI will use — key = directory basename, remote = `git -C '<path>' remote
|
|
@@ -67,26 +71,32 @@ fix the named field via `set`.
|
|
|
67
71
|
|
|
68
72
|
## Status mode
|
|
69
73
|
|
|
70
|
-
Run `npx -y @natjswenson/devlog scan --json` and render a compact table:
|
|
71
|
-
releases (version + date +
|
|
72
|
-
`prerelease`, `empty-range`, etc. — omit `entry-exists`
|
|
73
|
-
`tagFetch: "failed"` ("using local tags only") and
|
|
74
|
-
confirm which entries exist; publish will still
|
|
74
|
+
Run `npx -y @natjswenson/devlog@latest scan --json --summary` and render a compact table:
|
|
75
|
+
project, new releases (version + date + `commitCount`), and skipped-tag counts worth
|
|
76
|
+
mentioning (`prerelease`, `empty-range`, `entry-tombstoned`, etc. — omit `entry-exists`
|
|
77
|
+
noise unless asked). Note `tagFetch: "failed"` ("using local tags only") and
|
|
78
|
+
`existenceCheck: "failed"` ("couldn't confirm which entries exist; publish will still
|
|
79
|
+
refuse overwrites"). Write nothing.
|
|
75
80
|
|
|
76
81
|
## Generate mode
|
|
77
82
|
|
|
78
83
|
### Step 1: Scan for new releases
|
|
79
84
|
|
|
80
85
|
```bash
|
|
81
|
-
npx -y @natjswenson/devlog scan --json #
|
|
82
|
-
npx -y @natjswenson/devlog scan --json --project '<key>'
|
|
86
|
+
npx -y @natjswenson/devlog@latest scan --json --summary # the plan table
|
|
87
|
+
npx -y @natjswenson/devlog@latest scan --json --project '<key>' # full detail, one project
|
|
83
88
|
```
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
`
|
|
87
|
-
|
|
88
|
-
settings (`minSources`, `topicDomains`).
|
|
90
|
+
Start with `--summary`: per project it gives `newReleases` (`tag`, `version`, `date`,
|
|
91
|
+
`commitCount`), per-reason `skippedTags` counts, `publishedEntries` (every live entry's
|
|
92
|
+
`version`/`title`/`tags` — the topic-dedup input for 3b), and the resolved `deepDive`
|
|
93
|
+
settings (`minSources`, `topicDomains`). When you start writing a project's posts, run
|
|
94
|
+
the full per-project scan for its `commits[{hash, subject, date, public}]` and
|
|
95
|
+
`diffstat`. Handle the edges:
|
|
89
96
|
|
|
97
|
+
- The output echoes `cliVersion` — the version npx actually ran. npx caches aggressively;
|
|
98
|
+
if it's older than the version this SKILL.md shipped with, say so and re-run (the
|
|
99
|
+
`@latest` pin usually prevents this).
|
|
90
100
|
- `error: "unknown-project"` → list `availableKeys` and stop.
|
|
91
101
|
- `totalNewReleases: 0` → tell the user nothing new was tagged (mention notable skipped
|
|
92
102
|
tags) and stop. Do not create empty entries.
|
|
@@ -146,14 +156,28 @@ trivia or niche internals. If the obvious topic is repo-specific, step up one le
|
|
|
146
156
|
the general pattern behind it — the test is "could a reader finish this how-to and have
|
|
147
157
|
something working of their own?" When a release spans two candidate topics (or two
|
|
148
158
|
releases in one run share one), don't write the same guide twice: give each post the
|
|
149
|
-
most usable topic the run hasn't already covered.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
159
|
+
most usable topic the run hasn't already covered. **The same rule applies against the
|
|
160
|
+
whole catalog**: before settling on a topic, check every project's `publishedEntries`
|
|
161
|
+
(titles + tags, in the scan output) — if an existing entry already teaches this topic,
|
|
162
|
+
find the angle this release genuinely adds, or step to the next-most-usable topic.
|
|
163
|
+
Never publish a near-duplicate of a guide the catalog already has. Vary the surface
|
|
164
|
+
too: don't reuse the catalog's title shapes ("How to …" again and again) or repeat the
|
|
165
|
+
same section-heading skeleton post after post.
|
|
166
|
+
|
|
167
|
+
In a monorepo, one commit can appear in several projects' ranges — it belongs to the
|
|
168
|
+
post whose release story it is; other posts leave it out of their narrative and
|
|
169
|
+
`## Changelog` (`publish-entry` refuses a draft whose Changelog repeats a commit an
|
|
170
|
+
existing entry already lists). **Never force a second angle the history doesn't
|
|
171
|
+
support**: if the twin release has no story of its own beyond the shared commits, give
|
|
172
|
+
it a proportionally small post — or fold it into the sibling's `## Shipped` — rather
|
|
173
|
+
than inventing a premise to differentiate it.
|
|
153
174
|
|
|
154
175
|
**3c. Research before writing.** Use web search/fetch to gather at least
|
|
155
176
|
`deepDive.minSources` **distinct** reputable sources: official docs and release notes,
|
|
156
|
-
standards bodies, primary research, well-regarded engineering writing.
|
|
177
|
+
standards bodies, primary research, well-regarded engineering writing. A content farm
|
|
178
|
+
or SEO-mill page never counts toward `minSources` — when a concept has a primary source
|
|
179
|
+
(the original paper, the official docs, the pattern's canonical text), cite that, not a
|
|
180
|
+
summary site that ranks for the keyword.
|
|
157
181
|
Every specific external claim (a version, a behavior, a study, a definition) must be
|
|
158
182
|
backed by a source you actually verified — if you can't source it, don't claim it. Don't
|
|
159
183
|
lean on one URL for most claims. Keep a working `(claim, url)` list. Fetch tools can
|
|
@@ -273,16 +297,28 @@ Write each draft with the **Write tool** (never a bash heredoc) to a temp dir
|
|
|
273
297
|
(`mktemp -d` once, reuse the absolute path — shell variables don't persist across bash
|
|
274
298
|
calls). Name it `<version>.md`. Then:
|
|
275
299
|
|
|
276
|
-
1. **
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
the
|
|
283
|
-
|
|
300
|
+
1. **Ground-truth gate.** List every claim the draft makes about the user's own repo —
|
|
301
|
+
a tag exists or doesn't, a count ("seven tests went red"), a timeline, an outcome —
|
|
302
|
+
and verify each one with a git command run NOW, in this session (`git tag -l`,
|
|
303
|
+
`git show`, `git log`), the same way 3c keeps a `(claim, url)` list for external
|
|
304
|
+
claims. A claim you can't verify gets removed, not softened. A specific number is
|
|
305
|
+
publishable only if it appears in a commit, a diff, or a fetched source — otherwise
|
|
306
|
+
drop the precision ("several", not "seven"). And never label output as real ("that's
|
|
307
|
+
the real output", "from my actual run") unless the command that produced it ran in
|
|
308
|
+
this session — unrun output is always framed as expectation ("you should see…").
|
|
309
|
+
2. **Lint:** `npx -y @natjswenson/devlog@latest lint-post '<abs-draft-path>' --voice` —
|
|
310
|
+
fix every finding (missing sections, thin gotchas, too few distinct sources, sources
|
|
311
|
+
listed but never cited inline, untagged fences, voice violations).
|
|
312
|
+
3. **Assemble-and-run check:**
|
|
313
|
+
`npx -y @natjswenson/devlog@latest assemble-post '<abs-draft-path>' --out '<scratch>/assemble/<version>'`
|
|
314
|
+
extracts the draft's code blocks in order as numbered files (`text` fences are
|
|
315
|
+
expected output, listed but not written). When the code is runnable without external
|
|
316
|
+
services, execute the blocks exactly as a reader would. Anything undefined, out of
|
|
317
|
+
order, or missing an entrypoint (or an install step the post never shows) fails the
|
|
318
|
+
stranger test mechanically — fix the post, not just the scratch copy.
|
|
319
|
+
4. **Self-review against the how-to contract**, honestly, as a skeptical reader: walk
|
|
284
320
|
points 1-8 above plus voice adherence. Revise the draft for any point that fails.
|
|
285
|
-
|
|
321
|
+
5. At most **two** revision passes; then proceed with the best version and carry any
|
|
286
322
|
residual weakness into the final summary (e.g. "v0.5.0: only 2 gotchas had commit
|
|
287
323
|
evidence").
|
|
288
324
|
|
|
@@ -291,8 +327,13 @@ critique, not a rubber stamp.
|
|
|
291
327
|
|
|
292
328
|
### Step 5: Publish
|
|
293
329
|
|
|
294
|
-
Clone once, publish each entry through the CLI, push once. `targetRepo
|
|
295
|
-
from validated config
|
|
330
|
+
Clone once, publish each entry through the CLI, push once. `targetRepo`, `branch`, and
|
|
331
|
+
`targetDir` come from validated config (all echoed in the scan output) — still
|
|
332
|
+
single-quote every interpolated value.
|
|
333
|
+
|
|
334
|
+
The `--clone` flag always points at the CONTENT ROOT: `<abs-tmp>/<repo-name>` when
|
|
335
|
+
`targetDir` is empty, `<abs-tmp>/<repo-name>/<targetDir>` when it's set. Git commands
|
|
336
|
+
always run against the clone root `<abs-tmp>/<repo-name>` regardless.
|
|
296
337
|
|
|
297
338
|
Each release also gets a cover image, composed inline in this same loop right before that
|
|
298
339
|
release's own `publish-entry` call — a self-contained HTML/CSS (or inline SVG) document,
|
|
@@ -301,57 +342,68 @@ rasterized locally, never sent to any external service:
|
|
|
301
342
|
```bash
|
|
302
343
|
mktemp -d # → record the absolute path, e.g. /var/folders/.../tmp.abc
|
|
303
344
|
git -C '<abs-tmp>' clone --depth=1 'https://github.com/<targetRepo>.git'
|
|
345
|
+
# <content-root> = '<abs-tmp>/<repo-name>/<targetDir>' if targetDir is set,
|
|
346
|
+
# '<abs-tmp>/<repo-name>' otherwise.
|
|
347
|
+
```
|
|
304
348
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
npx -y @natjswenson/devlog publish-entry \
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
349
|
+
Right after the clone, **Write `<abs-tmp>/run-state.json`**: the clone path, the planned
|
|
350
|
+
releases, and a per-release status you update as each one drafts/lints/publishes. If the
|
|
351
|
+
session is compacted or interrupted mid-run, re-read it instead of re-deriving paths
|
|
352
|
+
from `/var/folders` archaeology.
|
|
353
|
+
|
|
354
|
+
Per release:
|
|
355
|
+
|
|
356
|
+
1. **Cover context.**
|
|
357
|
+
`npx -y @natjswenson/devlog@latest cover-context '<key>' '<version>' --clone '<content-root>'`
|
|
358
|
+
returns the style guide, icon catalog, and up to 3 reference cover paths. **Read
|
|
359
|
+
only the single most recent reference image** (image reads are the expensive part;
|
|
360
|
+
open another only if you're genuinely unsure the new cover is distinct). On
|
|
361
|
+
`{"error": "style-guide-missing"}`: skip cover composition for this release entirely
|
|
362
|
+
and proceed straight to publish-entry with no `--cover` flag.
|
|
363
|
+
Never block publish on a missing style guide.
|
|
364
|
+
2. **Compose** using ONLY this release's title/tags/summary/`## Shipped` text
|
|
365
|
+
(never the raw draft file, never `## Changelog`) plus the style guide and icon catalog. A
|
|
366
|
+
cover that just re-renders the title in large text is a failure — find the one
|
|
367
|
+
concrete mechanism this release is actually about (not the project name, not "a bug
|
|
368
|
+
fix") and draw ONE custom inline-SVG illustration of it as the dominant visual
|
|
369
|
+
element; title/kicker stay secondary; two releases should never produce visually
|
|
370
|
+
similar covers. Follow the style guide's hero-zone grid contract: the illustration
|
|
371
|
+
lives in a `#hero-zone` container at exactly `x:150 y:425 width:1300 height:400`
|
|
372
|
+
(render-cover mechanically enforces this), one of two slots (single centered hero,
|
|
373
|
+
or two-node before/after), interior points snapped to a 25px grid; catalog icons
|
|
374
|
+
never go inside `#hero-zone` (optional small accent glyph near the kicker only,
|
|
375
|
+
bottom edge above y:400). Write the document with the Write tool to
|
|
376
|
+
`'<abs-scratch>/<key>/<version>.html'` — full `<!DOCTYPE html>` document, sized
|
|
377
|
+
`html, body { margin:0; width:1600px; height:900px; }`, font referenced only as
|
|
378
|
+
`font-family: 'DevlogCoverFont', sans-serif`.
|
|
379
|
+
3. **Rasterize.**
|
|
380
|
+
`npx -y @natjswenson/devlog@latest render-cover '<abs-scratch>/<key>/<version>.html' --project '<key>' --slug '<version>' --out '<abs-scratch>'`
|
|
381
|
+
The HTML is the source of truth and **survives the render**: to fix a visual
|
|
382
|
+
problem, edit (or re-Write — if an Edit fails, re-Write the whole file) the same
|
|
383
|
+
.html and re-run render-cover; it re-renders whenever the HTML is present,
|
|
384
|
+
overwriting the old PNG. On `render-failed` (timeout / Chromium missing / font
|
|
385
|
+
missing / a `#hero-zone` geometry violation), retry composing once with the error
|
|
386
|
+
text fed back, or give up and proceed with no `--cover` flag. **Show the rendered
|
|
387
|
+
`<abs-scratch>/<key>/<version>.png` in this session before continuing —
|
|
388
|
+
this interactive review IS the quality gate for the cover**, the same way Step 4
|
|
389
|
+
is for the prose.
|
|
390
|
+
4. **Publish.**
|
|
391
|
+
```bash
|
|
392
|
+
npx -y @natjswenson/devlog@latest publish-entry \
|
|
393
|
+
--clone '<content-root>' --project '<key>' \
|
|
394
|
+
--version '<version>' --entry '<abs-draft-path>' \
|
|
395
|
+
--cover '<abs-scratch>/<key>/<version>.png'
|
|
396
|
+
```
|
|
397
|
+
Omit `--cover` entirely if no cover was produced (missing style guide, a render
|
|
398
|
+
failure not worth a second attempt) — publish still proceeds normally. publish-entry
|
|
399
|
+
refuses three things; none is retried by workaround: an existing entry (`immutable`
|
|
400
|
+
— skip the release, note it), a tombstoned version (`tombstoned` — skip; that
|
|
401
|
+
identity was editorially retired), and a Changelog commit another entry already
|
|
402
|
+
lists (drop the commit from this draft's `## Changelog` and re-publish).
|
|
403
|
+
|
|
404
|
+
Then, once, after all releases:
|
|
354
405
|
|
|
406
|
+
```bash
|
|
355
407
|
git -C '<abs-tmp>/<repo-name>' add .
|
|
356
408
|
git -C '<abs-tmp>/<repo-name>' commit -m 'devlog: add release entries'
|
|
357
409
|
git -C '<abs-tmp>/<repo-name>' push --no-tags origin '<branch>'
|
|
@@ -368,9 +420,14 @@ Release dev log entries published
|
|
|
368
420
|
Project: <key>
|
|
369
421
|
Releases: <version>, ...
|
|
370
422
|
Judged weaknesses: <residuals from Step 4, or "none">
|
|
371
|
-
URL: https://github.com/<targetRepo>/blob/<branch>/<key>/<version>.md
|
|
423
|
+
URL: https://github.com/<targetRepo>/blob/<branch>/<targetDir-prefix><key>/<version>.md
|
|
372
424
|
```
|
|
373
425
|
|
|
426
|
+
(`<targetDir-prefix>` is `<targetDir>/` when set, empty otherwise.) When the target is
|
|
427
|
+
a site repo that auto-deploys on push (e.g. Cloudflare Pages watching `main`), the
|
|
428
|
+
publish push itself triggers the rebuild — mention that the entry goes live with the
|
|
429
|
+
next deploy.
|
|
430
|
+
|
|
374
431
|
## Security rules
|
|
375
432
|
|
|
376
433
|
The CLI validates all config fields and excludes unsafe tag names before they reach you,
|
|
@@ -399,5 +456,17 @@ rules:
|
|
|
399
456
|
not an error.
|
|
400
457
|
- **Entry already exists** (skipped as `entry-exists`, or `publish-entry` refuses): a cut
|
|
401
458
|
release is immutable; never overwrite, never delete.
|
|
459
|
+
- **`entry-tombstoned`:** the release's entry was editorially retired (moved,
|
|
460
|
+
consolidated, or deleted on purpose) — skip it silently, never regenerate it.
|
|
461
|
+
- **The user moved/consolidated/deleted a published entry by hand:** tombstone the
|
|
462
|
+
identity it left behind so no later run resurrects it:
|
|
463
|
+
`npx -y @natjswenson/devlog@latest tombstone --clone '<content-root>' --project '<key>'
|
|
464
|
+
--version '<vX.Y.Z>' --reason '<where it went>'` (commit + push like a publish).
|
|
465
|
+
- **The user edited a published entry's prose/frontmatter:** resync its manifest row
|
|
466
|
+
(title/summary/date/tags — what the site index and RSS read) with
|
|
467
|
+
`npx -y @natjswenson/devlog@latest sync-entry --clone '<content-root>' --project '<key>'
|
|
468
|
+
--slug '<version>'`. If the result reports `coverStale: true` and the title/topic
|
|
469
|
+
changed, offer to recompose the cover (Step 5 flow, `--cover` via publish is not
|
|
470
|
+
needed — use the backfill commands or recompose+`render-cover`+commit).
|
|
402
471
|
- **Unknown project argument:** list the available keys from the scan error.
|
|
403
472
|
- **Config missing/invalid:** point at `npx @natjswenson/devlog init` / `set` and stop.
|