@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 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: project, new
71
- releases (version + date + commit count), and skipped tags worth mentioning (reason
72
- `prerelease`, `empty-range`, etc. — omit `entry-exists` noise unless asked). Note
73
- `tagFetch: "failed"` ("using local tags only") and `existenceCheck: "failed"` ("couldn't
74
- confirm which entries exist; publish will still refuse overwrites"). Write nothing.
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 # all projects
82
- npx -y @natjswenson/devlog scan --json --project '<key>' # one project
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
- The JSON plan contains everything discovery used to require: per project, the
86
- `newReleases` array (`tag`, `version`, `date`, `prevTag`, `commits[{hash, subject, date,
87
- public}]`, `diffstat`) plus `skippedTags` with reasons, and the resolved `deepDive`
88
- settings (`minSources`, `topicDomains`). Handle the edges:
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. In a monorepo, one commit can appear
150
- in several projects' ranges it belongs to the post whose release story it is; other
151
- posts leave it out of their narrative and `## Changelog` (check what sibling entries
152
- already cover, across all projects).
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. Avoid SEO farms.
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. **Lint:** `npx -y @natjswenson/devlog lint-post '<abs-draft-path>'` fix every
277
- finding (missing sections, thin gotchas, too few distinct sources, sources listed
278
- but never cited inline, untagged fences).
279
- 2. **Assemble-and-run check:** when the post's code is runnable without external
280
- services, copy its code blocks in order into a scratch dir and execute them exactly
281
- as a reader would. Anything undefined, out of order, or missing an entrypoint fails
282
- the stranger test mechanically fix the post, not just the scratch copy.
283
- 3. **Self-review against the how-to contract**, honestly, as a skeptical reader: walk
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
- 4. At most **two** revision passes; then proceed with the best version and carry any
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` and `branch` come
295
- from validated config still single-quote every interpolated value.
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
- # Per release (refuses to overwrite an existing entry on {"error": ...,
306
- # "message": "... immutable ..."} skip that release and note it):
307
-
308
- # 1. Style guide + up to 3 reference images of recently published covers.
309
- npx -y @natjswenson/devlog cover-context '<key>' '<version>' \
310
- --clone '<abs-tmp>/<repo-name>'
311
- # On {"error": "style-guide-missing", ...}: skip cover composition for this release
312
- # entirely — proceed straight to publish-entry with no --cover flag. Never block
313
- # publish on a missing style guide.
314
-
315
- # 2. Compose the cover using ONLY this release's title/tags/summary/`## Shipped` text
316
- # (never the raw draft file, never `## Changelog`) plus the returned style guide,
317
- # icon catalog, and reference images. A cover that just re-renders the title in large text is a failure —
318
- # find the one concrete mechanism this release is actually about
319
- # (not the project name, not "a bug fix") and draw ONE custom inline-SVG illustration
320
- # of it, sized as the dominant visual element of the canvas; title/kicker stay
321
- # secondary. Two different releases should never produce visually similar covers.
322
- #
323
- # Draw the illustration inside a `#hero-zone` container at exactly
324
- # `x:150 y:425 width:1300 height:400` (render-cover mechanically checks this box and
325
- # refuses to render otherwise) — pick ONE of two composition slots per post: single
326
- # centered hero (one freehand mechanism, nothing else), or two-node before/after (a
327
- # left node, a right node, a connecting line, all freehand). Snap interior key points
328
- # to a 25px coordinate grid. Catalog icons (image-style/icons.md) are never placed
329
- # inside `#hero-zone` they may only appear as an optional small accent glyph near
330
- # the kicker/title area, entirely outside the hero zone, its bottom edge no lower than
331
- # y:400. See the style guide's hero-zone grid contract section before composing.
332
- # Write the result with the Write tool to '<abs-scratch>/<key>/<version>.html' — a
333
- # full document starting with `<!DOCTYPE html>`, sized
334
- # `html, body { margin:0; width:1600px; height:900px; }`, referencing the bundled
335
- # font only as `font-family: 'DevlogCoverFont', sans-serif`.
336
-
337
- # 3. Rasterize it. On failure (render timeout / Chromium not installed / font missing /
338
- # a #hero-zone problem missing, duplicate, wrong position/size, or a catalog icon
339
- # overlapping it),
340
- # the .html is left in place for debugging retry composing once with the error text
341
- # fed back, or give up and proceed with no --cover flag.
342
- npx -y @natjswenson/devlog render-cover '<abs-scratch>/<key>/<version>.html' \
343
- --project '<key>' --slug '<version>' --out '<abs-scratch>'
344
- # Show the rendered <abs-scratch>/<key>/<version>.png in this session before continuing
345
- # this interactive review IS the quality gate for the cover, the same way Step 4 is for
346
- # the prose.
347
-
348
- npx -y @natjswenson/devlog publish-entry \
349
- --clone '<abs-tmp>/<repo-name>' --project '<key>' \
350
- --version '<version>' --entry '<abs-draft-path>' \
351
- --cover '<abs-scratch>/<key>/<version>.png'
352
- # Omit --cover entirely if no cover was produced for this release (missing style guide,
353
- # a render failure not worth a second attempt) publish still proceeds normally.
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-Writeif 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.