@natjswenson/devlog 0.10.0 → 0.11.1
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/CHANGELOG.md +479 -0
- package/LICENSE +21 -0
- package/README.md +272 -0
- package/SKILL.md +132 -79
- package/bin/devlog.js +160 -29
- package/evals/baseline/published/devlog-v0.10.0.md +129 -0
- package/evals/baseline/published/devlog-v0.11.0.md +139 -0
- package/evals/baseline/published/devlog-v0.8.1.md +97 -0
- package/evals/baseline/published/devlog-v0.9.0.md +193 -0
- package/evals/baseline/published/ghostwriter-v0.11.0.md +211 -0
- package/evals/baseline/published/ghostwriter-v0.8.1.md +221 -0
- package/evals/baseline/published/local-fitness-v0.25.0.md +233 -0
- package/evals/baseline/published/resume-v1.0.1.md +129 -0
- package/examples/react/useDevLogEntries.js +2 -0
- package/lib/assemble_post.mjs +71 -0
- package/lib/cover_gen.mjs +6 -1
- package/lib/lint_post.mjs +47 -1
- package/lib/publish_entry.mjs +209 -12
- package/lib/scan.mjs +95 -16
- package/package.json +4 -2
- package/skill-invariants.json +46 -1
package/README.md
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# devlog
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@natjswenson/devlog)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](./SECURITY.md)
|
|
6
|
+
[](#security)
|
|
7
|
+
|
|
8
|
+
A Claude Code skill that turns each version release (a git tag) into a published how-to guide, written in your own voice — and a React example for displaying it on your site.
|
|
9
|
+
|
|
10
|
+
> **Build in public, by release.** Tag a release like you always do. Run `/devlog`. Each new version shows up on your site as a polished, end-to-end implementation guide — in your voice, with cited sources and the gotchas you actually hit — not raw commit messages.
|
|
11
|
+
|
|
12
|
+
## Live example
|
|
13
|
+
|
|
14
|
+
The skill is in production at [natejswenson.com/devlog](https://natejswenson.com/devlog), publishing to [github.com/natejswenson/daily-dev-log](https://github.com/natejswenson/daily-dev-log). What you see on that page is exactly what `npx @natjswenson/devlog preview` renders for you locally.
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
1. **You ship a release** — tag it (e.g. `git tag v0.3.0`), like you already do.
|
|
19
|
+
2. **Run `/devlog` in Claude Code.** The skill runs `devlog scan` to find tags that don't yet have an entry, researches the engineering topic behind each release, and writes a full setup → build → use → verify how-to guide (with a required `## Gotchas` section mined from your real fix commits, and 3+ cited sources). Every draft passes a deterministic lint (`devlog lint-post`) plus a quality self-review before it publishes. It's idempotent — re-running does nothing until you cut a new release, and a published entry is never overwritten (`devlog publish-entry` refuses).
|
|
20
|
+
3. **Your site fetches it.** Static `manifest.json` + per-release markdown files served from `raw.githubusercontent.com` — no backend needed.
|
|
21
|
+
|
|
22
|
+
You can also manage configuration conversationally in Claude Code: "add this repo to devlog", "stop tracking X", "set min sources to 4", or `/devlog status` to see what would be generated without writing anything.
|
|
23
|
+
|
|
24
|
+
**In your voice.** Entries are written using a voice profile, resolved in this order: your `config.voicePath` → [ghostwriter](../ghostwriter)'s `voice/` dir if installed → a bundled default. devlog reads `voice-profile.md` (and `voice-notes.md` overrides) — never ghostwriter's `algorithm.md`, since LinkedIn reach tuning doesn't apply to a dev log.
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npx @natjswenson/devlog init
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
That command:
|
|
33
|
+
- Creates `<your-username>/daily-dev-log` on GitHub (or uses an existing one)
|
|
34
|
+
- Installs the skill at `~/.claude/skills/devlog/`
|
|
35
|
+
- Writes `~/.claude/skills/devlog/config.json` with your answers
|
|
36
|
+
- Lets you register one or more projects in a single run
|
|
37
|
+
|
|
38
|
+
Then:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
npx @natjswenson/devlog preview
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
to see your dev log rendered locally at `http://localhost:5173`.
|
|
45
|
+
|
|
46
|
+
## Prerequisites
|
|
47
|
+
|
|
48
|
+
- **Node 18+** — for the CLI and preview app
|
|
49
|
+
- **GitHub CLI** (`gh`), authenticated with `gh auth login` — used to create your dev-log repo and push entries
|
|
50
|
+
- **Claude Code** — to run the `/devlog` skill
|
|
51
|
+
|
|
52
|
+
## Commands
|
|
53
|
+
|
|
54
|
+
| Command | What it does |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `npx @natjswenson/devlog init` | One-time setup: create dev-log repo, install skill, write config |
|
|
57
|
+
| `npx @natjswenson/devlog add-project` | Register a project (interactive; `--yes --path <p>` for non-interactive/agent use) |
|
|
58
|
+
| `npx @natjswenson/devlog remove-project <key> --yes` | Unregister a project (published entries stay) |
|
|
59
|
+
| `npx @natjswenson/devlog set <field> <value>` | Update one config field (`targetRepo`, `branch`, `gitAuthor`, `githubUser`, `voicePath`, `deepDive.minSources`, `deepDive.topicDomains`) |
|
|
60
|
+
| `npx @natjswenson/devlog config [--json]` | Show your current config with validation status |
|
|
61
|
+
| `npx @natjswenson/devlog scan [--project <key>]` | JSON plan of new releases needing entries (used by the skill) |
|
|
62
|
+
| `npx @natjswenson/devlog lint-post <file>` | Deterministic post-contract check (used by the skill) |
|
|
63
|
+
| `npx @natjswenson/devlog publish-entry ...` | Copy a drafted entry into a clone + update the manifest; never overwrites (used by the skill) |
|
|
64
|
+
| `npx @natjswenson/devlog preview` | Run a local preview at `http://localhost:5173` |
|
|
65
|
+
| `npx @natjswenson/devlog --help` | Usage |
|
|
66
|
+
| `npx @natjswenson/devlog --version` | Version |
|
|
67
|
+
|
|
68
|
+
> **Tip:** run from any directory *outside* a clone of this repo. Running inside the repo causes a `package.json` name collision and `npx` fails with `command not found`.
|
|
69
|
+
|
|
70
|
+
## What you end up with
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
~/.claude/skills/devlog/
|
|
74
|
+
├── SKILL.md # The /devlog slash-command instructions
|
|
75
|
+
├── config.json # Your settings (mode 0600)
|
|
76
|
+
└── voice/ # Bundled fallback voice profile (last resort)
|
|
77
|
+
├── voice-profile.md
|
|
78
|
+
└── voice-notes.md
|
|
79
|
+
|
|
80
|
+
github.com/<you>/daily-dev-log/ # Created by init, populated by /devlog
|
|
81
|
+
├── myproject/
|
|
82
|
+
│ ├── manifest.json
|
|
83
|
+
│ ├── v0.3.0.md
|
|
84
|
+
│ ├── v0.2.0.md
|
|
85
|
+
│ └── ...
|
|
86
|
+
└── ...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Manual setup (if you prefer)
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
gh repo create <you>/daily-dev-log --public --add-readme
|
|
93
|
+
mkdir -p ~/.claude/skills/devlog/voice
|
|
94
|
+
curl -o ~/.claude/skills/devlog/SKILL.md https://raw.githubusercontent.com/natejswenson/devlog/main/SKILL.md
|
|
95
|
+
# Optional fallback voice profile (used when voicePath and ghostwriter are both absent):
|
|
96
|
+
curl -o ~/.claude/skills/devlog/voice/voice-profile.md https://raw.githubusercontent.com/natejswenson/devlog/main/voice/voice-profile.example.md
|
|
97
|
+
curl -o ~/.claude/skills/devlog/voice/voice-notes.md https://raw.githubusercontent.com/natejswenson/devlog/main/voice/voice-notes.example.md
|
|
98
|
+
# Then copy config.example.json → ~/.claude/skills/devlog/config.json and fill it in
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Add to your site
|
|
102
|
+
|
|
103
|
+
### React (drop-in)
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
cp -r examples/react/ your-site/src/devlog/
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Edit `your-site/src/devlog/devlog-config.js` to point at your repo, then:
|
|
110
|
+
|
|
111
|
+
```jsx
|
|
112
|
+
import DevLogPage from './devlog/DevLogPage.jsx';
|
|
113
|
+
|
|
114
|
+
<DevLogPage project="myproject" />
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Full instructions: [`examples/react/README.md`](./examples/react/README.md).
|
|
118
|
+
|
|
119
|
+
### No site yet?
|
|
120
|
+
|
|
121
|
+
The `preview/` directory is a complete deployable Vite app. Set `VITE_DEVLOG_OWNER` / `VITE_DEVLOG_REPO` / `VITE_DEVLOG_PROJECTS` env vars on Vercel, Netlify, or Cloudflare Pages, build with `vite build`, deploy `dist/`. See [`preview/README.md`](./preview/README.md).
|
|
122
|
+
|
|
123
|
+
### Other stacks (Next, Astro, plain HTML)
|
|
124
|
+
|
|
125
|
+
It's static JSON and Markdown on GitHub. Build whatever UI you want — see the **Data contract** below.
|
|
126
|
+
|
|
127
|
+
## Data contract
|
|
128
|
+
|
|
129
|
+
The dev-log repo has this layout, all served as raw files from `https://raw.githubusercontent.com/<owner>/<repo>/<branch>/`:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
<repo>/
|
|
133
|
+
└── <project-key>/
|
|
134
|
+
├── manifest.json # Index of all entries (newest first)
|
|
135
|
+
├── v0.3.0.md # One entry per release (named by version)
|
|
136
|
+
├── v0.2.0.md
|
|
137
|
+
└── ...
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**`manifest.json`:**
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"entries": [
|
|
144
|
+
{ "date": "2026-06-08", "file": "v0.2.0.md", "title": "...", "summary": "...", "version": "v0.2.0" }
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Strict validation rules (entries that don't match are silently dropped by the React example):
|
|
150
|
+
- `date` matches `YYYY-MM-DD` (the release/tag date)
|
|
151
|
+
- `file` matches `^[a-zA-Z0-9._-]+\.md$`
|
|
152
|
+
- `title` and `summary` are non-empty strings
|
|
153
|
+
- `version` (optional) matches `^[a-zA-Z0-9._-]+$`
|
|
154
|
+
|
|
155
|
+
**Entry markdown:**
|
|
156
|
+
|
|
157
|
+
```markdown
|
|
158
|
+
---
|
|
159
|
+
title: "Essay-style title (never 'release vX.Y.Z')"
|
|
160
|
+
date: 2026-06-08
|
|
161
|
+
project: myproject
|
|
162
|
+
version: v0.2.0
|
|
163
|
+
tags: [reliability, python]
|
|
164
|
+
summary: "1-2 sentence hook framing the how-to"
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Shipped
|
|
168
|
+
What the release delivered, then the pivot to the topic the guide teaches.
|
|
169
|
+
|
|
170
|
+
## <Setup / build / use-and-verify sections>
|
|
171
|
+
An end-to-end implementation guide a reader can follow without your repo:
|
|
172
|
+
complete, language-tagged code blocks; reader-side verification steps.
|
|
173
|
+
|
|
174
|
+
## Gotchas
|
|
175
|
+
Real traps from the release's history, each: trap → symptom → escape.
|
|
176
|
+
|
|
177
|
+
## Sources
|
|
178
|
+
- [Title](https://...) — what it supports (3+ distinct URLs)
|
|
179
|
+
|
|
180
|
+
## Changelog
|
|
181
|
+
- commit message ([abc1234](https://github.com/.../commit/abc1234567...))
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
That's the entire contract — `npx @natjswenson/devlog lint-post <file>` checks the mechanical parts of it.
|
|
185
|
+
|
|
186
|
+
## Configuration reference
|
|
187
|
+
|
|
188
|
+
`~/.claude/skills/devlog/config.json`:
|
|
189
|
+
|
|
190
|
+
| Field | Type | Description |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `targetRepo` | `"<owner>/<repo>"` | Repo where dev log entries are published. Must match `^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$`. |
|
|
193
|
+
| `branch` | string (optional) | Branch in the dev-log repo. Defaults to `main`. Must not contain `..` or start with `-`. |
|
|
194
|
+
| `gitAuthor` | string | Your name. Retained for backward compatibility; it is **not** currently rendered on entries (the author filter was removed, and release notes summarize all commits in a tag range). Still **required** by config validation — it must be present and non-empty (don't drop it). Whitespace OK; no shell metacharacters. |
|
|
195
|
+
| `githubUser` | string | Your GitHub username. |
|
|
196
|
+
| `voicePath` | string (optional) | Directory holding `voice-profile.md` (and optionally `voice-notes.md`) used to write entries in your voice. A leading `~` is expanded. If unset, devlog uses ghostwriter's `voice/` if installed, else the bundled default. Read only — never shell-interpolated. |
|
|
197
|
+
| `projects` | array | One entry per project you want dev logs for. |
|
|
198
|
+
| `projects[].key` | string | Subdirectory name in the dev-log repo. Strict token: `^[a-z0-9][a-z0-9._-]*$`, no `..`. |
|
|
199
|
+
| `projects[].label` | string (optional) | Display name for the tab. Defaults to `key`. |
|
|
200
|
+
| `projects[].path` | string | Local filesystem path to the project. Whitespace OK. |
|
|
201
|
+
| `projects[].remote` | `"<owner>/<repo>"` | The project's GitHub remote. Used to mark public commits and link them. |
|
|
202
|
+
| `projects[].pathFilter` | string (optional) | Repo-relative subdir scoping this project's commits in a monorepo (e.g. `skills/devlog`). |
|
|
203
|
+
| `projects[].tagPrefix` | string (optional) | Prefix of the git tags that mark this project's releases (e.g. `devlog-v`). Defaults to `v`. Used in `git tag --list '<tagPrefix>*'`. |
|
|
204
|
+
| `deepDive` | object (optional) | Controls the researched how-to. `topicDomains` (array, default `["AI", "DevOps/SRE", "software engineering"]`) steers topic selection; `minSources` (integer 1-10, default 3) is the floor of **distinct** cited source URLs per post. |
|
|
205
|
+
|
|
206
|
+
See [`config.example.json`](./config.example.json) for a complete template, or run `npx @natjswenson/devlog config` to inspect your current config with validation.
|
|
207
|
+
|
|
208
|
+
## Security
|
|
209
|
+
|
|
210
|
+
The package is designed to be safe to install on a developer's machine and have ambient gh/git credentials. See [SECURITY.md](./SECURITY.md) for the full threat model and audit history.
|
|
211
|
+
|
|
212
|
+
**At a glance:**
|
|
213
|
+
- ✓ All shell calls in the CLI use `spawnSync` with argv arrays (no shell, no injection surface)
|
|
214
|
+
- ✓ The skill validates every config field against an allowlist before interpolating into shell commands; instructs the LLM to single-quote interpolated values
|
|
215
|
+
- ✓ Markdown rendering uses `react-markdown` with `skipHtml` and an allowlist `urlTransform` (only http(s)/mailto allowed; data:, javascript:, vbscript:, file:, blob: all neutralized)
|
|
216
|
+
- ✓ Frontmatter parser uses `Object.create(null)` + key allowlist (no prototype pollution)
|
|
217
|
+
- ✓ All external JSON is schema-validated before use (manifest, env-var projects array)
|
|
218
|
+
- ✓ Vite dev server bound to `localhost`, CORS off
|
|
219
|
+
- ✓ Demo-mode `window.fetch` override gated to dev builds only
|
|
220
|
+
- ✓ `config.json` written atomically (tmp + rename), mode 0600
|
|
221
|
+
- ✓ All dependencies pinned to exact versions
|
|
222
|
+
- ✓ `npm audit`: 0 known vulnerabilities
|
|
223
|
+
|
|
224
|
+
**To report a vulnerability:** open a [GitHub security advisory](https://github.com/natejswenson/devlog/security/advisories/new). Do not open a public issue.
|
|
225
|
+
|
|
226
|
+
## Customization
|
|
227
|
+
|
|
228
|
+
- **Tweak the entry template:** edit `~/.claude/skills/devlog/SKILL.md` (Generate mode, Step 3 — the how-to contract).
|
|
229
|
+
- **Tweak your voice:** edit the `voice-profile.md` / `voice-notes.md` in your `voicePath` (or `~/.claude/skills/devlog/voice/`).
|
|
230
|
+
- **Tune research depth:** `npx @natjswenson/devlog set deepDive.minSources 4`, or `set deepDive.topicDomains "security, platform engineering"`.
|
|
231
|
+
- **Tweak the UI:** override the `--devlog-*` CSS variables in `examples/react/DevLogPage.css` to match your theme.
|
|
232
|
+
- **Add or remove projects:** `npx @natjswenson/devlog add-project` / `remove-project <key> --yes` — or just tell Claude ("add this repo to devlog").
|
|
233
|
+
|
|
234
|
+
## Testing & evals
|
|
235
|
+
|
|
236
|
+
The deterministic core (`lib/`, the CLI) is covered by `npm test` (node:test; scan tests
|
|
237
|
+
run against real throwaway git repos). The non-deterministic half — post quality — is
|
|
238
|
+
covered by a cost-capped eval harness:
|
|
239
|
+
|
|
240
|
+
```sh
|
|
241
|
+
node evals/run_eval.mjs --mock # $0, runs in CI; deterministic layer only
|
|
242
|
+
node evals/run_eval.mjs --live # LLM judge on golden fixtures; quotes spend, hard cap $0.50
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The judge scores reproducibility, code completeness, gotcha quality, citations, voice,
|
|
246
|
+
and scope honesty (pass ≥ 7/10). Without `ANTHROPIC_API_KEY` in the environment, mock
|
|
247
|
+
mode is forced — CI can never spend money.
|
|
248
|
+
|
|
249
|
+
## Troubleshooting
|
|
250
|
+
|
|
251
|
+
**`sh: devlog: command not found` when running `npx`:** you're inside a checkout of this repo. The local `package.json` name collides with the published one. Run `npx` from somewhere else, e.g. `cd ~ && npx @natjswenson/devlog ...`.
|
|
252
|
+
|
|
253
|
+
**Init prompts show `78` after placeholder text:** that's an artifact of how some output capture tools render `\x1b7`/`\x1b8` (cursor save/restore) escape sequences. In a real interactive terminal, you won't see it.
|
|
254
|
+
|
|
255
|
+
**Preview is blank / no tabs / no entries:** check the browser console. If you see CJS interop errors related to `react-dom/client`, `style-to-js`, or `react-markdown`, you're on a pre-0.1.6 release — upgrade with `npm cache clean --force && rm -rf ~/.npm/_npx && npx --yes @natjswenson/devlog@latest preview`.
|
|
256
|
+
|
|
257
|
+
**Init can't find `gh`:** install via [cli.github.com](https://cli.github.com/) and run `gh auth login`.
|
|
258
|
+
|
|
259
|
+
**`Config validation failed: ...`** — the validator rejected something in your `config.json`. Run `npx @natjswenson/devlog config` for a detailed diagnosis. Common causes:
|
|
260
|
+
- A field has shell metacharacters (`;` `&` `|` `` ` `` `$` etc) — see SKILL.md for the full list
|
|
261
|
+
- A project key contains `..` or `/`
|
|
262
|
+
- A path doesn't exist on disk
|
|
263
|
+
|
|
264
|
+
**Preview shows "Setup required":** you deployed the preview app standalone but didn't set the env vars. Set `VITE_DEVLOG_OWNER`, `VITE_DEVLOG_REPO`, `VITE_DEVLOG_PROJECTS` (JSON-stringified array) in your hosting environment.
|
|
265
|
+
|
|
266
|
+
## Versioning
|
|
267
|
+
|
|
268
|
+
Releases are documented in [CHANGELOG.md](./CHANGELOG.md). The package follows semver — bug fixes/security patches in patch releases (0.1.x), behavior changes in minor (0.x.0).
|
|
269
|
+
|
|
270
|
+
## License
|
|
271
|
+
|
|
272
|
+
MIT
|
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,10 @@ 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`, `targetDir`, `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
45
|
|
|
46
46
|
`targetDir` is the subdirectory of `targetRepo` that holds the devlog content tree
|
|
47
47
|
(e.g. `content/devlog` when the target is the site repo itself); unset/empty means the
|
|
@@ -71,26 +71,32 @@ fix the named field via `set`.
|
|
|
71
71
|
|
|
72
72
|
## Status mode
|
|
73
73
|
|
|
74
|
-
Run `npx -y @natjswenson/devlog scan --json` and render a compact table:
|
|
75
|
-
releases (version + date +
|
|
76
|
-
`prerelease`, `empty-range`, etc. — omit `entry-exists`
|
|
77
|
-
`tagFetch: "failed"` ("using local tags only") and
|
|
78
|
-
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.
|
|
79
80
|
|
|
80
81
|
## Generate mode
|
|
81
82
|
|
|
82
83
|
### Step 1: Scan for new releases
|
|
83
84
|
|
|
84
85
|
```bash
|
|
85
|
-
npx -y @natjswenson/devlog scan --json #
|
|
86
|
-
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
|
|
87
88
|
```
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
`
|
|
91
|
-
|
|
92
|
-
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:
|
|
93
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).
|
|
94
100
|
- `error: "unknown-project"` → list `availableKeys` and stop.
|
|
95
101
|
- `totalNewReleases: 0` → tell the user nothing new was tagged (mention notable skipped
|
|
96
102
|
tags) and stop. Do not create empty entries.
|
|
@@ -150,14 +156,28 @@ trivia or niche internals. If the obvious topic is repo-specific, step up one le
|
|
|
150
156
|
the general pattern behind it — the test is "could a reader finish this how-to and have
|
|
151
157
|
something working of their own?" When a release spans two candidate topics (or two
|
|
152
158
|
releases in one run share one), don't write the same guide twice: give each post the
|
|
153
|
-
most usable topic the run hasn't already covered.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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.
|
|
157
174
|
|
|
158
175
|
**3c. Research before writing.** Use web search/fetch to gather at least
|
|
159
176
|
`deepDive.minSources` **distinct** reputable sources: official docs and release notes,
|
|
160
|
-
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.
|
|
161
181
|
Every specific external claim (a version, a behavior, a study, a definition) must be
|
|
162
182
|
backed by a source you actually verified — if you can't source it, don't claim it. Don't
|
|
163
183
|
lean on one URL for most claims. Keep a working `(claim, url)` list. Fetch tools can
|
|
@@ -277,16 +297,28 @@ Write each draft with the **Write tool** (never a bash heredoc) to a temp dir
|
|
|
277
297
|
(`mktemp -d` once, reuse the absolute path — shell variables don't persist across bash
|
|
278
298
|
calls). Name it `<version>.md`. Then:
|
|
279
299
|
|
|
280
|
-
1. **
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
the
|
|
287
|
-
|
|
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
|
|
288
320
|
points 1-8 above plus voice adherence. Revise the draft for any point that fails.
|
|
289
|
-
|
|
321
|
+
5. At most **two** revision passes; then proceed with the best version and carry any
|
|
290
322
|
residual weakness into the final summary (e.g. "v0.5.0: only 2 gotchas had commit
|
|
291
323
|
evidence").
|
|
292
324
|
|
|
@@ -312,57 +344,66 @@ mktemp -d # → record the absolute path, e.g. /var/folders/.../tmp.abc
|
|
|
312
344
|
git -C '<abs-tmp>' clone --depth=1 'https://github.com/<targetRepo>.git'
|
|
313
345
|
# <content-root> = '<abs-tmp>/<repo-name>/<targetDir>' if targetDir is set,
|
|
314
346
|
# '<abs-tmp>/<repo-name>' otherwise.
|
|
347
|
+
```
|
|
315
348
|
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
npx -y @natjswenson/devlog publish-entry \
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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:
|
|
365
405
|
|
|
406
|
+
```bash
|
|
366
407
|
git -C '<abs-tmp>/<repo-name>' add .
|
|
367
408
|
git -C '<abs-tmp>/<repo-name>' commit -m 'devlog: add release entries'
|
|
368
409
|
git -C '<abs-tmp>/<repo-name>' push --no-tags origin '<branch>'
|
|
@@ -415,5 +456,17 @@ rules:
|
|
|
415
456
|
not an error.
|
|
416
457
|
- **Entry already exists** (skipped as `entry-exists`, or `publish-entry` refuses): a cut
|
|
417
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).
|
|
418
471
|
- **Unknown project argument:** list the available keys from the scan error.
|
|
419
472
|
- **Config missing/invalid:** point at `npx @natjswenson/devlog init` / `set` and stop.
|