sequant 2.7.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +9 -1
  4. package/dist/bin/cli.d.ts +1 -1
  5. package/dist/bin/cli.js +10 -1
  6. package/dist/bin/preflight.d.ts +21 -0
  7. package/dist/bin/preflight.js +45 -0
  8. package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
  9. package/dist/marketplace/external_plugins/sequant/skills/_shared/references/force-push.md +34 -0
  10. package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +24 -7
  11. package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +29 -0
  12. package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +100 -2
  13. package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +24 -0
  14. package/dist/marketplace/external_plugins/sequant/skills/qa/references/anti-pattern-detection.md +285 -0
  15. package/dist/marketplace/external_plugins/sequant/skills/qa/references/call-site-review.md +202 -0
  16. package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +287 -0
  17. package/dist/marketplace/external_plugins/sequant/skills/qa/references/test-quality-checklist.md +272 -0
  18. package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +40 -0
  19. package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +95 -11
  20. package/dist/marketplace/external_plugins/sequant/skills/references/shared/framework-gotchas.md +186 -0
  21. package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +661 -0
  22. package/dist/marketplace/external_plugins/sequant/skills/test/references/browser-testing-patterns.md +423 -0
  23. package/dist/marketplace/external_plugins/sequant/skills/upstream/SKILL.md +419 -0
  24. package/dist/src/lib/errors.d.ts +85 -0
  25. package/dist/src/lib/errors.js +111 -0
  26. package/dist/src/lib/version-check.d.ts +19 -0
  27. package/dist/src/lib/version-check.js +44 -0
  28. package/dist/src/lib/workflow/batch-executor.js +61 -6
  29. package/dist/src/lib/workflow/drivers/agent-driver.d.ts +17 -0
  30. package/dist/src/lib/workflow/drivers/claude-code.d.ts +22 -0
  31. package/dist/src/lib/workflow/drivers/claude-code.js +111 -7
  32. package/dist/src/lib/workflow/log-writer.d.ts +1 -1
  33. package/dist/src/lib/workflow/phase-executor.d.ts +18 -0
  34. package/dist/src/lib/workflow/phase-executor.js +76 -14
  35. package/dist/src/lib/workflow/run-log-schema.d.ts +3 -0
  36. package/dist/src/lib/workflow/run-log-schema.js +7 -0
  37. package/dist/src/lib/workflow/state-manager.d.ts +1 -0
  38. package/dist/src/lib/workflow/state-manager.js +6 -0
  39. package/dist/src/lib/workflow/state-schema.d.ts +3 -0
  40. package/dist/src/lib/workflow/state-schema.js +7 -0
  41. package/dist/src/lib/workflow/types.d.ts +17 -0
  42. package/dist/src/ui/tui/theme.d.ts +18 -4
  43. package/dist/src/ui/tui/theme.js +18 -4
  44. package/package.json +4 -3
  45. package/templates/skills/_shared/references/force-push.md +34 -0
  46. package/templates/skills/assess/SKILL.md +24 -7
  47. package/templates/skills/exec/SKILL.md +29 -0
  48. package/templates/skills/loop/SKILL.md +100 -2
  49. package/templates/skills/qa/SKILL.md +24 -0
  50. package/templates/skills/qa/references/anti-pattern-detection.md +285 -0
  51. package/templates/skills/qa/references/call-site-review.md +202 -0
  52. package/templates/skills/qa/references/quality-gates.md +287 -0
  53. package/templates/skills/qa/references/test-quality-checklist.md +272 -0
  54. package/templates/skills/qa/references/testing-requirements.md +40 -0
  55. package/templates/skills/qa/scripts/quality-checks.sh +95 -11
  56. package/templates/skills/references/shared/framework-gotchas.md +186 -0
  57. package/templates/skills/release/SKILL.md +661 -0
  58. package/templates/skills/test/references/browser-testing-patterns.md +423 -0
  59. package/templates/skills/upstream/SKILL.md +419 -0
@@ -0,0 +1,661 @@
1
+ ---
2
+ name: release
3
+ description: "Automates the full release workflow: version bump, git tag, GitHub release, and npm publish."
4
+ license: MIT
5
+ metadata:
6
+ author: sequant
7
+ version: "1.0"
8
+ allowed-tools:
9
+ - Read
10
+ - Bash(npm test:*)
11
+ - Bash(npm run build:*)
12
+ - Bash(npm version:*)
13
+ - Bash(npm pack:*)
14
+ - Bash(npm publish:*)
15
+ - Bash(npm whoami:*)
16
+ - Bash(npm view:*)
17
+ - Bash(npm audit:*)
18
+ - Bash(git status:*)
19
+ - Bash(git branch:*)
20
+ - Bash(git fetch:*)
21
+ - Bash(git log:*)
22
+ - Bash(git add:*)
23
+ - Bash(git commit:*)
24
+ - Bash(git tag:*)
25
+ - Bash(git push:*)
26
+ - Bash(gh auth status:*)
27
+ - Bash(gh release create:*)
28
+ - Bash(gh release view:*)
29
+ ---
30
+
31
+ <!-- sequant:local-override -->
32
+ > **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/release/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/release` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
33
+
34
+ # Release Skill
35
+
36
+ Automates the full release workflow: version bump, git tag, GitHub release, and npm publish.
37
+
38
+ ## Usage
39
+
40
+ ```
41
+ /release [patch|minor|major] [--prerelease <tag>] [--dry-run]
42
+ ```
43
+
44
+ - `/release` - Interactive, asks for version type
45
+ - `/release patch` - Patch release (1.3.1 → 1.3.2)
46
+ - `/release minor` - Minor release (1.3.1 → 1.4.0)
47
+ - `/release major` - Major release (1.3.1 → 2.0.0)
48
+ - `/release minor --prerelease beta` - Pre-release (1.3.1 → 1.4.0-beta.0)
49
+ - `/release --dry-run` - Preview without publishing
50
+
51
+ ## Pre-flight Checks
52
+
53
+ Run ALL checks before proceeding. **STOP if any fails.**
54
+
55
+ ### Git Checks
56
+
57
+ ```bash
58
+ # 1. On main branch
59
+ [ "$(git branch --show-current)" = "main" ] || { echo "Not on main"; exit 1; }
60
+
61
+ # 2. Clean working tree
62
+ [ -z "$(git status --porcelain)" ] || { echo "Uncommitted changes"; exit 1; }
63
+
64
+ # 3. In sync with remote
65
+ git fetch origin
66
+ [ -z "$(git log HEAD..origin/main)" ] || { echo "Behind origin - pull first"; exit 1; }
67
+ ```
68
+
69
+ ### Quality Checks
70
+
71
+ ```bash
72
+ # 4. Tests pass
73
+ npm test
74
+
75
+ # 5. Build works
76
+ npm run build
77
+
78
+ # 6. No security vulnerabilities (high/critical)
79
+ npm audit --audit-level=high || echo "Warning: audit issues found"
80
+ ```
81
+
82
+ ### npm Checks
83
+
84
+ ```bash
85
+ # 7. Logged into npm
86
+ npm whoami || { echo "Not logged in - run: npm login"; exit 1; }
87
+
88
+ # 8. Version doesn't already exist
89
+ pkg_name=$(node -p "require('./package.json').name")
90
+ current=$(node -p "require('./package.json').version")
91
+ npm view "${pkg_name}@${current}" version 2>/dev/null && { echo "Version already published"; exit 1; }
92
+
93
+ # 9. Preview package contents
94
+ npm pack --dry-run 2>&1 | tail -20
95
+ ```
96
+
97
+ ### GitHub Checks
98
+
99
+ ```bash
100
+ # 10. Authenticated with GitHub
101
+ gh auth status || { echo "Not logged in - run: gh auth login"; exit 1; }
102
+ ```
103
+
104
+ ### Documentation Checks
105
+
106
+ > **Note:** Documentation freshness checks that compare against the version *being released* run **after** Step 4 (the version bump), not here in pre-flight:
107
+ > - `docs/internal/what-weve-built.md` title + ASCII version stamps → **Step 4.63**
108
+ > - `CHANGELOG.md` freshness (version entry present) → **Step 4.65**
109
+ > - `README.md` "What's new in <minor>" heading freshness → **Step 4.66**
110
+ >
111
+ > They must compare against the *new* version, which doesn't exist in `package.json` until Step 4 runs. Running them in pre-flight would compare against the *previous* release — which the docs already reflect — so the gate would never fire for the release in progress (root cause class of #684 / #687).
112
+
113
+ ## Release Steps
114
+
115
+ ### Step 1: Determine Version
116
+
117
+ If version type not provided as argument, ask the user:
118
+
119
+ | Type | When to Use | Example |
120
+ |------|-------------|---------|
121
+ | `patch` | Bug fixes, maintenance, no new features | 1.3.1 → 1.3.2 |
122
+ | `minor` | New features, backwards compatible | 1.3.1 → 1.4.0 |
123
+ | `major` | Breaking changes | 1.3.1 → 2.0.0 |
124
+ | `prerelease` | Alpha/beta/RC versions | 1.3.1 → 1.4.0-beta.0 |
125
+
126
+ ```bash
127
+ current=$(node -p "require('./package.json').version")
128
+ echo "Current version: ${current}"
129
+ ```
130
+
131
+ ### Step 2: Generate Release Notes
132
+
133
+ Gather commits since last tag:
134
+ ```bash
135
+ last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
136
+ if [ -n "$last_tag" ]; then
137
+ git log ${last_tag}..HEAD --oneline --no-merges
138
+ fi
139
+ ```
140
+
141
+ Categorize by conventional commit prefix:
142
+ - `feat:` → **Features**
143
+ - `fix:` → **Bug Fixes**
144
+ - `perf:` → **Performance**
145
+ - `docs:` → **Documentation**
146
+ - `refactor:` → **Refactoring**
147
+ - `chore:` → **Maintenance**
148
+ - `BREAKING CHANGE:` → **Breaking Changes** (for major releases)
149
+
150
+ **Release notes template:**
151
+ ```markdown
152
+ ## What's Changed
153
+
154
+ ### Features
155
+ - Feature description (#PR)
156
+
157
+ ### Bug Fixes
158
+ - Fix description (#PR)
159
+
160
+ ### Breaking Changes
161
+ - Description of breaking change and migration path
162
+
163
+ **Full Changelog**: https://github.com/sequant-io/sequant/compare/v{old}...v{new}
164
+ ```
165
+
166
+ **Show draft to user for approval before proceeding.**
167
+
168
+ ### Step 3: Update CHANGELOG.md (if exists)
169
+
170
+ If `CHANGELOG.md` exists, use the **Edit tool** to:
171
+
172
+ 1. Replace `## [Unreleased]` with `## [{new_version}] - {YYYY-MM-DD}`
173
+ 2. Insert a fresh `## [Unreleased]` section above the newly stamped version:
174
+
175
+ ```markdown
176
+ ## [Unreleased]
177
+
178
+ ## [1.4.0] - 2026-01-10
179
+
180
+ ### Added
181
+ - New feature X
182
+ ...
183
+ ```
184
+
185
+ This ensures the next development cycle has an `[Unreleased]` section ready for contributors.
186
+
187
+ ### Step 4: Bump Version
188
+
189
+ ```bash
190
+ # For regular releases
191
+ npm version {patch|minor|major} --no-git-tag-version
192
+
193
+ # For pre-releases
194
+ npm version prerelease --preid=beta --no-git-tag-version
195
+ ```
196
+
197
+ ### Step 4.5: Sync Plugin Version
198
+
199
+ **IMPORTANT:** Keep plugin.json in sync with package.json.
200
+
201
+ Use the **Read tool** to read `.claude-plugin/plugin.json`, then use the **Edit tool** to update the version field:
202
+
203
+ ```
204
+ Read(file_path=".claude-plugin/plugin.json")
205
+
206
+ Edit(file_path=".claude-plugin/plugin.json",
207
+ old_string="\"version\": \"<old_version>\"",
208
+ new_string="\"version\": \"<new_version>\"")
209
+ ```
210
+
211
+ If `.claude-plugin/plugin.json` does not exist, skip this step.
212
+
213
+ **Why sync?** Sequant is distributed as both npm package and Claude Code plugin. Both must have matching versions to avoid user confusion and ensure compatibility.
214
+
215
+ ### Step 4.6: Update what-weve-built.md
216
+
217
+ **IMPORTANT:** Keep `docs/internal/what-weve-built.md` version references in sync and document new features.
218
+
219
+ Get the new version, then use the Edit tool to update the file:
220
+
221
+ ```bash
222
+ new_version=$(node -p "require('./package.json').version")
223
+ ```
224
+
225
+ Then use the **Edit tool** (not sed) to update `docs/internal/what-weve-built.md`:
226
+
227
+ ```
228
+ # Update title version - use Edit tool with replace_all=true
229
+ Edit(file_path="docs/internal/what-weve-built.md",
230
+ old_string="Sequant v<old_version>",
231
+ new_string="Sequant v${new_version}",
232
+ replace_all=true)
233
+
234
+ # Update ASCII art version - use Edit tool with replace_all=true
235
+ Edit(file_path="docs/internal/what-weve-built.md",
236
+ old_string="SEQUANT v<old_version>",
237
+ new_string="SEQUANT v${new_version}",
238
+ replace_all=true)
239
+
240
+ # Update "Current Version" line - use Edit tool
241
+ Edit(file_path="docs/internal/what-weve-built.md",
242
+ old_string="**Current Version:** <old_version>",
243
+ new_string="**Current Version:** ${new_version}")
244
+ ```
245
+
246
+ **Auto-generate feature bullets from CHANGELOG:**
247
+
248
+ Extract features from the `[Unreleased]` section of CHANGELOG.md:
249
+
250
+ ```bash
251
+ # Extract [Unreleased] entries from CHANGELOG.md
252
+ if [ -f "CHANGELOG.md" ]; then
253
+ # Get content between [Unreleased] and next version header
254
+ unreleased_content=$(sed -n '/^## \[Unreleased\]/,/^## \[/p' CHANGELOG.md | head -n -1)
255
+
256
+ # Extract Added entries (these become what-weve-built features)
257
+ added_entries=$(echo "$unreleased_content" | sed -n '/^### Added/,/^### /p' | grep -E '^\s*-' | head -n -1 || true)
258
+
259
+ if [ -n "$added_entries" ]; then
260
+ echo "Features to add to what-weve-built.md:"
261
+ echo "$added_entries"
262
+ else
263
+ echo "No new features in [Unreleased] section"
264
+ fi
265
+ fi
266
+ ```
267
+
268
+ **If CHANGELOG has features**, use the Edit tool to update `docs/internal/what-weve-built.md`:
269
+
270
+ 1. **Update the "Recent Additions" section header:**
271
+ ```
272
+ Edit(file_path="docs/internal/what-weve-built.md",
273
+ old_string="### Recent Additions (v<old_version>)",
274
+ new_string="### Recent Additions (v${new_version})")
275
+ ```
276
+
277
+ 2. **Add feature bullets under "Recent Additions":**
278
+ Transform CHANGELOG entries to what-weve-built format:
279
+
280
+ | CHANGELOG Format | what-weve-built Format |
281
+ |------------------|------------------------|
282
+ | `- Feature description (#123)` | `- **Feature Name** - Brief description` |
283
+ | `- Multi-line feature (#123)\n - Sub-detail` | `- **Feature Name** - Brief description` |
284
+
285
+ Example transformation:
286
+ ```markdown
287
+ # From CHANGELOG:
288
+ - CHANGELOG update step in /exec skill (#320)
289
+ - Instructs /exec to add [Unreleased] entries during feature commits
290
+
291
+ # To what-weve-built:
292
+ - **CHANGELOG Automation** - Automatic CHANGELOG entry requirements in /exec and /qa
293
+ ```
294
+
295
+ 3. **Update counts in "At a Glance" table** if applicable:
296
+ - New skill added → increment skill count
297
+ - New command added → increment command count
298
+ - New MCP integration → increment integration count
299
+
300
+ **Fallback to commit-based detection:**
301
+
302
+ If CHANGELOG.md doesn't exist or has no `[Unreleased]` section, fall back to commit-based detection:
303
+
304
+ ```bash
305
+ last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
306
+ if [ -n "$last_tag" ]; then
307
+ echo "New features since ${last_tag}:"
308
+ git log ${last_tag}..HEAD --oneline --no-merges | grep -E "^[a-f0-9]+ feat" || echo " (none)"
309
+ fi
310
+ ```
311
+
312
+ **Ask the user to review what-weve-built.md before proceeding** if there are new features to document.
313
+
314
+ ### Step 4.63: Verify what-weve-built.md Version Stamps
315
+
316
+ **IMPORTANT:** This runs **after** Step 4 (version bump) and Step 4.6 (which updates the stamps), so `package.json` already holds the *new* version. Running it earlier (in pre-flight) would compare against the previous release — which the file already reflects — so the gate would never fire for the version actually being released (root cause class of #684 / #687, the same ordering bug fixed for the README check in #685).
317
+
318
+ Warn-only (like the CHANGELOG freshness check): prompt the releaser if the title or ASCII-art version stamp omits/mismatches the version being released.
319
+
320
+ ```bash
321
+ # Warn if docs/internal/what-weve-built.md version stamps don't match the version being released
322
+ if [ -f "docs/internal/what-weve-built.md" ]; then
323
+ new_version=$(node -p "require('./package.json').version")
324
+
325
+ # Check title version
326
+ title_version=$(grep -oE "Sequant v[0-9]+\.[0-9]+\.[0-9]+" docs/internal/what-weve-built.md | head -1 | grep -oE "[0-9]+\.[0-9]+\.[0-9]+" || true)
327
+ if [ "$title_version" != "$new_version" ]; then
328
+ echo "Warning: docs/internal/what-weve-built.md title shows v${title_version}, expected v${new_version}"
329
+ fi
330
+
331
+ # Check ASCII art version
332
+ ascii_version=$(grep -E "SEQUANT v[0-9]+\.[0-9]+\.[0-9]+" docs/internal/what-weve-built.md | grep -oE "[0-9]+\.[0-9]+\.[0-9]+" || true)
333
+ if [ "$ascii_version" != "$new_version" ]; then
334
+ echo "Warning: docs/internal/what-weve-built.md ASCII art shows v${ascii_version}, expected v${new_version}"
335
+ fi
336
+ fi
337
+ ```
338
+
339
+ ### Step 4.65: Verify CHANGELOG Freshness
340
+
341
+ **IMPORTANT:** This runs **after** Step 4 (version bump) so `package.json` already holds the *new* version. Running it earlier (in pre-flight) would compare against the previous release, which the CHANGELOG already lists — so the gate would never fire for the version actually being released (root cause class of #684).
342
+
343
+ **Note (#701):** #694 moved the changelog wall out of `README.md` into `CHANGELOG.md` and removed the README "What's new" section, so this gate now checks `CHANGELOG.md` (the source of truth). Grepping `README.md` for a now-absent `# What's new` heading made the warn-only gate fire on every release.
344
+
345
+ Warn-only (like the what-weve-built checks): prompt the releaser to add a `CHANGELOG.md` entry if the new version is absent.
346
+
347
+ ```bash
348
+ # Warn if CHANGELOG.md omits the version being released
349
+ if [ -f "CHANGELOG.md" ]; then
350
+ new_version=$(node -p "require('./package.json').version")
351
+
352
+ # Match the Keep-a-Changelog heading for this version, e.g. "## [2.4.0] - 2026-05-30"
353
+ if ! grep -qF "[${new_version}]" CHANGELOG.md; then
354
+ echo "Warning: CHANGELOG.md omits v${new_version} — add a \"## [${new_version}]\" entry before releasing"
355
+ fi
356
+ fi
357
+ ```
358
+
359
+ ### Step 4.66: Verify README "What's new" Freshness
360
+
361
+ **IMPORTANT:** This runs **after** Step 4 (version bump) so `package.json` already holds the *new* version. Running it earlier (in pre-flight) would compare against the previous release — which the README already reflects — so the gate would never fire for the version actually being released (root cause class of #684 / #687, the same ordering bug fixed for the README check in #685).
362
+
363
+ **Note (#702 / #707):** This gate owns the hand-written `### What's new in <major>.<minor>` positioning section that #702 re-added to `README.md` — distinct from the changelog wall that #694 moved to `CHANGELOG.md` (covered by Step 4.65). It keys on the **minor** line, not a full semver: "What's new in 2.6" is correct for the entire 2.6.x line, so a **patch** release (2.6.0 → 2.6.1) does NOT fire against a matching heading. During v2.6.0 the heading still read "What's new in 2.5" and shipped stale (fixed manually in 18c2d42); this gate flags that case.
364
+
365
+ Warn-only (like the CHANGELOG freshness check): prompt the releaser if the README's latest "What's new" heading lags the minor line being released. No README edit is auto-applied — the section's content and positioning are an editorial decision.
366
+
367
+ ```bash
368
+ # Warn if README.md's latest "What's new" heading lags the minor line being released.
369
+ # Keys on the MINOR line (e.g. "2.6"), so a patch release against a matching heading stays silent.
370
+ if [ -f "README.md" ]; then
371
+ new_version=$(node -p "require('./package.json').version")
372
+ new_minor=$(echo "$new_version" | grep -oE '^[0-9]+\.[0-9]+')
373
+
374
+ # Anchor to a markdown heading (^#+) so a "What's new in X.Y" mention in prose can't be read as
375
+ # the section, and take the numerically-highest minor (sort -t. then tail -1) so the result is
376
+ # independent of heading order in the file rather than assuming newest-first.
377
+ readme_minor=$(grep -oE "^#+[[:space:]]+What's new in [0-9]+\.[0-9]+" README.md \
378
+ | grep -oE '[0-9]+\.[0-9]+' | sort -t. -k1,1n -k2,2n | tail -1 || true)
379
+
380
+ # The -n guard is load-bearing: if the section is ever removed again (as #694 did),
381
+ # this gate stays silent rather than firing on every release (the bug #701 fixed for the old gate).
382
+ if [ -n "$readme_minor" ] && [ "$readme_minor" != "$new_minor" ]; then
383
+ echo "Warning: README.md 'What's new' section is for $readme_minor, expected $new_minor — add/update the heading before releasing"
384
+ fi
385
+ fi
386
+ ```
387
+
388
+ ### Step 4.7: Regenerate Marketplace Artifact
389
+
390
+ **IMPORTANT:** The marketplace plugin artifact under `dist/marketplace/` is bundled into the published tgz via `files: ["dist", ...]` in package.json — even though `dist/` is gitignored. Regenerate it from current sources **before** packing/publishing so the bundled README always matches; otherwise a stale artifact ships (root cause of #684 — the v2.4.0 tgz carried a "Node.js 20+" README after the floor moved to 22.12).
391
+
392
+ This is an action step, so it lives in Release Steps (not the read-only pre-flight checks). It MUST run before Step 5 (`npm pack`) and Step 9 (`npm publish`).
393
+
394
+ ```bash
395
+ # Regenerate the marketplace plugin artifact from current sources
396
+ npm run prepare:marketplace
397
+
398
+ # Freshness sanity check: generated README's Node.js floor should match package.json engines.
399
+ # Warn-only (regeneration above already fixes the artifact; this is a belt-and-suspenders signal).
400
+ gen_readme="dist/marketplace/external_plugins/sequant/README.md"
401
+ if [ -f "$gen_readme" ]; then
402
+ engines_floor=$(node -p "require('./package.json').engines.node.replace(/[^0-9.]/g,'')") # e.g. 22.12.0
403
+ readme_floor=$(grep -oE "Node\.js [0-9]+\.[0-9]+" "$gen_readme" | head -1 | grep -oE "[0-9]+\.[0-9]+" || true)
404
+ if [ -n "$readme_floor" ] && [ "${engines_floor%.*}" != "$readme_floor" ]; then
405
+ echo "Warning: generated marketplace README shows Node.js ${readme_floor}, package.json engines floor is ${engines_floor}"
406
+ fi
407
+ fi
408
+ ```
409
+
410
+ ### Step 5: Verify Package Size
411
+
412
+ ```bash
413
+ # Check what will be published
414
+ npm pack --dry-run
415
+
416
+ # Verify size is reasonable (warn if > 500KB)
417
+ size=$(npm pack --dry-run 2>&1 | grep "total files" -A1 | tail -1 || true)
418
+ echo "Package size: ${size}"
419
+ ```
420
+
421
+ ### Step 6: Commit and Push
422
+
423
+ ```bash
424
+ new_version=$(node -p "require('./package.json').version")
425
+ git add package.json package-lock.json CHANGELOG.md .claude-plugin/plugin.json .claude-plugin/marketplace.json docs/internal/what-weve-built.md
426
+ git commit -m "chore: release v${new_version}"
427
+ git push origin main
428
+ ```
429
+
430
+ ### Step 7: Create and Push Tag
431
+
432
+ ```bash
433
+ git tag -a "v${new_version}" -m "Release v${new_version}"
434
+ git push origin "v${new_version}"
435
+ ```
436
+
437
+ ### Step 8: Create GitHub Release
438
+
439
+ ```bash
440
+ # Regular release
441
+ gh release create "v${new_version}" \
442
+ --title "v${new_version} - {title}" \
443
+ --notes "{release_notes}"
444
+
445
+ # Pre-release (alpha/beta/rc)
446
+ gh release create "v${new_version}" \
447
+ --title "v${new_version}" \
448
+ --notes "{release_notes}" \
449
+ --prerelease
450
+ ```
451
+
452
+ For the title, use a short summary:
453
+ - `v1.4.0 - New Feature Name`
454
+ - `v1.3.2 - Bug Fix Release`
455
+ - `v2.0.0 - Breaking Changes`
456
+
457
+ ### Step 9: Publish to npm
458
+
459
+ Attempt to publish:
460
+
461
+ ```bash
462
+ # Regular release
463
+ npm publish
464
+
465
+ # Pre-release with tag (prevents becoming "latest")
466
+ npm publish --tag beta
467
+ ```
468
+
469
+ **If npm returns `EOTP` (2FA required):**
470
+
471
+ Non-interactive environments cannot handle the OTP prompt. Ask the user to publish manually:
472
+
473
+ ```
474
+ npm publish --otp=<code>
475
+ ```
476
+
477
+ Do NOT attempt to pass OTP codes programmatically or retry `npm publish` in a loop. Hand off to the user and continue with post-release verification once they confirm.
478
+
479
+ ## Post-Release Verification
480
+
481
+ Verify both platforms show the new version:
482
+
483
+ ```bash
484
+ # npm verification (may take 1-2 minutes to propagate)
485
+ sleep 5
486
+ npm view sequant version
487
+ npm view sequant dist-tags
488
+
489
+ # GitHub verification
490
+ gh release view "v${new_version}"
491
+
492
+ # Test install
493
+ npx sequant@${new_version} --version
494
+ ```
495
+
496
+ ## Output Summary
497
+
498
+ ```
499
+ Release v{version} Complete
500
+
501
+ Version: {old} → {new}
502
+ Commit: {hash}
503
+ Tag: v{new}
504
+
505
+ GitHub: https://github.com/sequant-io/sequant/releases/tag/v{new}
506
+ npm: https://www.npmjs.com/package/sequant/v/{new}
507
+ Plugin: Version synced in .claude-plugin/plugin.json
508
+ Docs: Version synced in docs/internal/what-weve-built.md
509
+
510
+ Install (npm):
511
+ npm install sequant@{new}
512
+ npx sequant@{new}
513
+
514
+ Install (plugin):
515
+ /plugin marketplace update sequant-io/sequant
516
+ /plugin install sequant
517
+
518
+ Verification:
519
+ [x] npm view shows correct version
520
+ [x] GitHub release created
521
+ [x] Tag pushed
522
+ [x] plugin.json version synced
523
+ [x] what-weve-built.md version synced
524
+ [x] New features documented (if any)
525
+
526
+ Next steps:
527
+ - Announce release (if major/minor)
528
+ - Update dependent projects
529
+ - Monitor for issues
530
+ ```
531
+
532
+ ## Dry Run Mode
533
+
534
+ When `--dry-run` is specified:
535
+
536
+ 1. Run all pre-flight checks
537
+ 2. Show what version would be created
538
+ 3. Show draft release notes
539
+ 4. Show `npm pack --dry-run` output
540
+ 5. **Do NOT** execute any publish commands
541
+
542
+ ```
543
+ Dry Run Summary
544
+
545
+ Would release: 1.3.1 → 1.4.0
546
+
547
+ Package contents:
548
+ - 176 files
549
+ - 195.9 kB packed
550
+
551
+ Release notes:
552
+ [draft notes here]
553
+
554
+ To execute: /release minor
555
+ ```
556
+
557
+ ## Rollback Procedures
558
+
559
+ ### Before npm publish
560
+
561
+ ```bash
562
+ # Undo version bump (if not committed)
563
+ git checkout -- package.json package-lock.json
564
+
565
+ # Undo commit (if not pushed)
566
+ git reset --soft HEAD~1
567
+
568
+ # Delete local tag
569
+ git tag -d v{version}
570
+ ```
571
+
572
+ ### After git push, before npm publish
573
+
574
+ ```bash
575
+ # Delete remote tag
576
+ git push origin :refs/tags/v{version}
577
+
578
+ # Delete GitHub release
579
+ gh release delete v{version} --yes
580
+
581
+ # Revert commit
582
+ git revert HEAD
583
+ git push origin main
584
+ ```
585
+
586
+ ### After npm publish
587
+
588
+ **Do NOT use `npm unpublish`** - it breaks dependent projects.
589
+
590
+ Instead:
591
+ 1. Publish a patch fix: `npm version patch && npm publish`
592
+ 2. Deprecate bad version: `npm deprecate sequant@{bad} "Use {good} instead"`
593
+
594
+ ## Error Handling
595
+
596
+ | Error | Cause | Resolution |
597
+ |-------|-------|------------|
598
+ | "Not on main" | Wrong branch | `git checkout main` |
599
+ | "Uncommitted changes" | Dirty working tree | `git stash` or commit |
600
+ | "Behind origin" | Remote has new commits | `git pull origin main` |
601
+ | "Tests failed" | Failing tests | Fix tests first |
602
+ | "Version exists" | Already published | Choose different version |
603
+ | "Not logged in (npm)" | npm auth expired | `npm login` |
604
+ | "Not logged in (gh)" | GitHub auth expired | `gh auth login` |
605
+ | "OTP required" | npm 2FA enabled | Enter code when prompted |
606
+ | "Package too large" | Bloated package | Check `.npmignore` |
607
+
608
+ ## Best Practices
609
+
610
+ ### Versioning
611
+ - Follow [Semantic Versioning](https://semver.org/)
612
+ - Use pre-release tags for testing: `1.4.0-beta.1`
613
+ - Major version 0.x.x indicates unstable API
614
+
615
+ ### Release Notes
616
+ - Write for users, not developers
617
+ - Highlight breaking changes prominently
618
+ - Include migration guides for major releases
619
+ - Link to relevant issues/PRs
620
+
621
+ ### npm
622
+ - Keep package size small (< 500KB ideal)
623
+ - Use `files` in package.json or `.npmignore`
624
+ - Test with `npm pack` before publishing
625
+ - Use `--tag` for pre-releases to avoid becoming "latest"
626
+
627
+ ### GitHub
628
+ - Use pre-release flag for beta/RC versions
629
+ - Attach relevant assets (if any)
630
+ - Use consistent title format
631
+
632
+ ### Security
633
+ - Run `npm audit` before releasing
634
+ - Don't include sensitive files (check `npm pack --dry-run`)
635
+ - Verify `npm whoami` shows correct account
636
+
637
+ ## Examples
638
+
639
+ ### Standard Patch Release
640
+ ```
641
+ /release patch
642
+ ```
643
+
644
+ ### Minor Release with Custom Notes
645
+ ```
646
+ /release minor
647
+ # Review generated notes
648
+ # Edit if needed
649
+ # Approve to publish
650
+ ```
651
+
652
+ ### Beta Pre-release
653
+ ```
654
+ /release minor --prerelease beta
655
+ ```
656
+ Creates: `1.4.0-beta.0`
657
+
658
+ ### Preview Without Publishing
659
+ ```
660
+ /release minor --dry-run
661
+ ```