@natjswenson/shipflow 0.3.0 → 0.3.2
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 +336 -0
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/package.json +4 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@natjswenson/shipflow` are documented here.
|
|
4
|
+
|
|
5
|
+
## 0.3.2 (2026-07-28) — actually render the README on npmjs.com
|
|
6
|
+
|
|
7
|
+
- **Fixed: the npm page still showed "No README data found" after 0.3.1.** That
|
|
8
|
+
release put README.md, LICENSE and CHANGELOG.md into the tarball (verified by
|
|
9
|
+
downloading it), which fixed what `npm install` delivers — but not the website.
|
|
10
|
+
npm reads README.md to populate the registry manifest's `readme` field when it
|
|
11
|
+
builds the publish manifest, which happens BEFORE the package's own `prepack`
|
|
12
|
+
hook runs. So the tarball had the file and the registry record did not.
|
|
13
|
+
Worse than merely empty: the stored value was the literal string
|
|
14
|
+
`ERROR: No README data found!`, and npmjs.com falls back to reading the
|
|
15
|
+
tarball only when that field is *absent* (compare `zod`, which renders fine
|
|
16
|
+
with no packument readme at all), so the error string kept winning.
|
|
17
|
+
The release workflow now stages the three files into the package directory
|
|
18
|
+
before `npm publish` is invoked at all, rather than relying on `prepack`.
|
|
19
|
+
Code unchanged.
|
|
20
|
+
|
|
21
|
+
## 0.3.1 (2026-07-28) — publish the README, LICENSE and CHANGELOG to npm
|
|
22
|
+
|
|
23
|
+
- **Fixed: the npm package shipped with no README, LICENSE or CHANGELOG.**
|
|
24
|
+
`package.json`'s `files` listed all three, but they live at the plugin root
|
|
25
|
+
(beside `.claude-plugin/`), one level above the package directory — and npm only
|
|
26
|
+
includes files from inside the package directory, so the entries silently matched
|
|
27
|
+
nothing. Every release through 0.3.0 published a tarball without them, and
|
|
28
|
+
`npm view @natjswenson/shipflow readme` returned *"No README data found"*, leaving
|
|
29
|
+
the npm page blank for anyone evaluating the CLI. A `prepack` script now stages the
|
|
30
|
+
three files into the package directory before the tarball is built, and `postpack`
|
|
31
|
+
removes them again so the working tree stays clean; they are gitignored at that
|
|
32
|
+
path so a interrupted pack cannot leave committable strays. Code unchanged.
|
|
33
|
+
|
|
34
|
+
## 0.3.0 (2026-07-28) — multi-pattern workflow templates; `undefined` param fix
|
|
35
|
+
|
|
36
|
+
Minor, not patch: this release carries the multi-pattern feature work that had been
|
|
37
|
+
sitting unreleased on `main` since 0.2.6, plus one bug fix found by the new baseline
|
|
38
|
+
eval. Existing `dev-main-promotion` repos are unaffected — see the compatibility note
|
|
39
|
+
below.
|
|
40
|
+
|
|
41
|
+
- **Fixed: a missing config field rendered the literal string `"undefined"` into the
|
|
42
|
+
workflow.** `renderTemplate` tested param presence with `key in params`, which is
|
|
43
|
+
true for a key whose value is `undefined`, so `String(undefined)` flowed through as
|
|
44
|
+
a real value and passed the safety regexes. A config with no `branches.main`
|
|
45
|
+
rendered `name: auto-merge dev to undefined` and `branches: [undefined]` — a
|
|
46
|
+
syntactically valid workflow that installs cleanly and can never fire, with no
|
|
47
|
+
error at `apply` time. Present-but-`undefined` and `null` now count as missing and
|
|
48
|
+
raise the same "missing param(s)" error as an absent key. Found by
|
|
49
|
+
`tests/baseline.test.mjs` on its first run.
|
|
50
|
+
|
|
51
|
+
Generalizes shipflow from one hardcoded branching pattern to a registry of three
|
|
52
|
+
selectable patterns, with deterministic autodetection. Backward compatible: a repo's
|
|
53
|
+
existing `.github/shipflow.json` with no `workflowPattern` field keeps resolving to
|
|
54
|
+
`dev-main-promotion` with identical behavior — confirmed against this repo's own live
|
|
55
|
+
config (a `noops`-only plan, byte-identical to before this change).
|
|
56
|
+
|
|
57
|
+
- **Added: `github-flow` pattern** — a single long-lived `main`; every PR merges (and
|
|
58
|
+
auto-merges) directly to it, no separate promotion branch. New
|
|
59
|
+
`main-automerge.yml.tmpl` template.
|
|
60
|
+
- **Added: `gitflow` pattern** — `develop` + `main` + transient `release/*`/`hotfix/*`
|
|
61
|
+
branches, for software maintaining multiple released versions concurrently. New
|
|
62
|
+
`release-automerge.yml.tmpl`/`hotfix-automerge.yml.tmpl` templates (prefix-matched
|
|
63
|
+
`head.ref` guards) and `hotfix-merge-back.yml.tmpl`/`release-merge-back.yml.tmpl`
|
|
64
|
+
(GitFlow's defining dual-merge-back semantic: a hotfix/release merges into both
|
|
65
|
+
`main` and `develop`; a merge-back conflict or push failure opens a PR for manual
|
|
66
|
+
resolution instead of force-pushing or silently dropping the merge).
|
|
67
|
+
- **Added: deterministic autodetection.** `shipflow detect` now returns a
|
|
68
|
+
`rankedPatterns` array (every pattern's score + evidence). Classification is
|
|
69
|
+
`confident` (top score `>= 0.7` and a `> 0.3` gap over second place), `greenfield`
|
|
70
|
+
(top score `< 0.4`), or `ambiguous` (the residual case) — the first-run interview
|
|
71
|
+
confirms a confident detection's evidence with the user rather than silently
|
|
72
|
+
applying it, and presents all 3 patterns for an explicit choice otherwise.
|
|
73
|
+
- **Added: `workflowPattern` + `patternConfig` config fields.** `patternConfig.gitflow`
|
|
74
|
+
holds `releaseBranchPrefix`/`hotfixBranchPrefix` (default `release/`/`hotfix/`).
|
|
75
|
+
`branches.dev` remains the sole source of truth for gitflow's develop-branch name —
|
|
76
|
+
no separate `developBranch` field.
|
|
77
|
+
- **Architecture:** new `lib/pattern-registry.mjs` (`listPatterns`/`resolvePattern`/
|
|
78
|
+
`scoreAll`) and one `lib/patterns/<id>/index.mjs` module per pattern. `detect.mjs`,
|
|
79
|
+
`plan.mjs`, and `apply.mjs` are now thin dispatchers over whatever the registry
|
|
80
|
+
returns, instead of hardcoding `dev-main-promotion`'s logic inline — adding a 4th
|
|
81
|
+
pattern in the future needs no changes to any of the three.
|
|
82
|
+
- Existing single-pattern behavior (branch protection, auto-merge, branch cleanup,
|
|
83
|
+
release tagging) is unchanged for repos already on `dev-main-promotion`.
|
|
84
|
+
|
|
85
|
+
## 0.2.6 (2026-07-15) — pin `@latest` on every invocation; docs pass
|
|
86
|
+
|
|
87
|
+
Self-discovered during the PAT-wiring dogfood step that immediately followed
|
|
88
|
+
0.2.5's release — not a Siege audit finding, but adjacent to the same
|
|
89
|
+
class of risk the audit was meant to close.
|
|
90
|
+
|
|
91
|
+
- **Fixed: silent global-install shadowing.** `npx -y @natjswenson/shipflow
|
|
92
|
+
<command>` (no version/tag) can resolve an already-installed copy on
|
|
93
|
+
`PATH` — e.g. a stale `npm install -g @natjswenson/shipflow` left over
|
|
94
|
+
from manual testing — instead of fetching the current version from the
|
|
95
|
+
registry, with **no warning that this happened**. Confirmed concretely on
|
|
96
|
+
`claude-skills` itself: a bare invocation silently ran a stale global
|
|
97
|
+
0.2.0 install, missing every fix through 0.2.5, including the Critical
|
|
98
|
+
template-injection fix (0.2.3). `npx -y @natjswenson/shipflow@latest
|
|
99
|
+
<command>` correctly resolved 0.2.5. This meant a repo could run
|
|
100
|
+
`shipflow` believing it was getting current, audited behavior while
|
|
101
|
+
silently getting pre-audit, vulnerable behavior instead.
|
|
102
|
+
- **Fix: every invocation in `SKILL.md` now pins `@latest`.** New
|
|
103
|
+
regression tests (`tests/skill_contract.test.mjs`) assert every `npx`
|
|
104
|
+
invocation of shipflow in `SKILL.md` is pinned, and fail if a future edit
|
|
105
|
+
reintroduces a bare invocation. New skill-invariant entry
|
|
106
|
+
(`npx-must-pin-latest`).
|
|
107
|
+
- **Docs pass**, per user request before this release: root `README.md`
|
|
108
|
+
gained a `shipflow` row in the skills table, marketplace/manual-install
|
|
109
|
+
instructions, and a rewritten Branch & release flow section describing
|
|
110
|
+
the actual shipflow-managed automation this repo runs (replacing stale
|
|
111
|
+
prose describing the pre-dogfood bespoke flow); `skills/shipflow/README.md`
|
|
112
|
+
gained current usage instructions (with the `@latest` pin and its
|
|
113
|
+
rationale), an updated Status section reflecting 0.2.5's live validation
|
|
114
|
+
and completed security audit, and a pointer to remove a shadowing global
|
|
115
|
+
install if one exists (`npm uninstall -g @natjswenson/shipflow`).
|
|
116
|
+
- No code changes to `lib/`/`bin/` in this release — SKILL.md, tests,
|
|
117
|
+
README, and version metadata only.
|
|
118
|
+
|
|
119
|
+
## 0.2.5 (2026-07-15) — mandatory TOCTOU guard, forced-override auditability, subprocess timeouts, YAML-validity CI check
|
|
120
|
+
|
|
121
|
+
The four remaining findings from the same Siege audit as 0.2.3/0.2.4, all
|
|
122
|
+
presented to the user for a fix-vs-accept decision and fixed on explicit
|
|
123
|
+
go-ahead.
|
|
124
|
+
|
|
125
|
+
- **High, fixed (SIEGE-2026-07-15-002):** `--force allow-no-checks` /
|
|
126
|
+
`--force <template-id>` had zero code-level friction beyond the flag
|
|
127
|
+
itself — "get explicit user confirmation before forcing" lived entirely in
|
|
128
|
+
SKILL.md prose, not in the CLI. `apply` now refuses any `--force` unless
|
|
129
|
+
accompanied by `--force-reason "<text>"`; the reason is echoed back on
|
|
130
|
+
each forced entry in the apply result (`{ forced: true, forceReason }`)
|
|
131
|
+
for auditability. This doesn't stop a determined bypass, but it raises
|
|
132
|
+
the bar from a single flag to an explicit, logged justification.
|
|
133
|
+
- **Medium, fixed (SIEGE-2026-07-15-003):** `--expect-state-hash` (the
|
|
134
|
+
documented TOCTOU guard) was optional — omitting it silently proceeded
|
|
135
|
+
with zero drift protection. A real (non-dry-run) `apply` now hard-refuses
|
|
136
|
+
without it, unless the caller explicitly passes the new, named
|
|
137
|
+
`--skip-hash-check` escape hatch.
|
|
138
|
+
- **Medium, fixed (SIEGE-2026-07-15-004):** no subprocess timeout was set
|
|
139
|
+
anywhere in `gh.mjs`'s `spawnSync` calls — a hung/rate-limited `gh api` or
|
|
140
|
+
stuck `git` call could hang the whole process indefinitely. All
|
|
141
|
+
`spawnArgs` calls now default to a 30s timeout, with the resulting
|
|
142
|
+
`ETIMEDOUT` surfaced in the returned `stderr`.
|
|
143
|
+
- **Low, fixed (SIEGE-2026-07-15-005):** no test rendered the template and
|
|
144
|
+
validated the output as syntactically valid YAML — the exact bug class
|
|
145
|
+
that bit 0.2.1/0.2.2 (a silently-broken generated workflow) was caught
|
|
146
|
+
only by live production testing, not the unit suite. New
|
|
147
|
+
`tests/template-validity.test.mjs` parses the rendered workflow with the
|
|
148
|
+
`yaml` package (dev-only dependency, not shipped to consumers) across a
|
|
149
|
+
range of legal inputs.
|
|
150
|
+
- New CLI integration test file (`tests/cli-apply-guards.test.mjs`) spawns
|
|
151
|
+
the real `bin/shipflow.js` to exercise both new refusals end-to-end.
|
|
152
|
+
- This closes out the Siege security audit run before rolling shipflow out
|
|
153
|
+
to repos beyond `claude-skills` — zero Critical/High findings remain open.
|
|
154
|
+
|
|
155
|
+
## 0.2.4 (2026-07-15) — REST-path encoding, resolveOwnerRepo hardening, file-size cap
|
|
156
|
+
|
|
157
|
+
Three more findings from the same Siege audit as 0.2.3, surfaced by an
|
|
158
|
+
independently-dispatched Boundary Attacker pass that (eventually) returned
|
|
159
|
+
its report and cross-confirmed the 0.2.3 fix while adding new findings:
|
|
160
|
+
|
|
161
|
+
- **Medium, fixed:** `fetchBranchProtection` and `checkSecretPresent`
|
|
162
|
+
interpolated `branch`/`secretName` unencoded into `gh api` REST path
|
|
163
|
+
segments — inconsistent with `checkLabelExists`, which already used
|
|
164
|
+
`encodeURIComponent` for the same class of input. Both now encode.
|
|
165
|
+
- **Low, fixed:** `resolveOwnerRepo`'s regex capture (`[\w.-]+`) admitted
|
|
166
|
+
all-dots segments (`.`, `..`) since `.` is in the character class with no
|
|
167
|
+
further constraint — a crafted remote like `github.com/../claude-skills`
|
|
168
|
+
could yield an `ownerRepo` that normalizes away the intended
|
|
169
|
+
`repos/<owner>/<repo>` prefix once interpolated downstream. Now rejects
|
|
170
|
+
any owner/repo segment matching `^\.+$`.
|
|
171
|
+
- **Medium, fixed:** no file shipflow reads from a target repo
|
|
172
|
+
(`.github/shipflow.json`, candidate settings-as-code artifacts, workflow
|
|
173
|
+
YAML, the rendered template) had a size guard — all of these are
|
|
174
|
+
repo-write-controlled, not admin-only, so a maliciously huge or
|
|
175
|
+
pathologically nested file could exhaust memory on an unbounded
|
|
176
|
+
`readFileSync`/`JSON.parse`. New `readFileCapped` helper in `gh.mjs`
|
|
177
|
+
(1 MB cap) used at every such read site.
|
|
178
|
+
- 8 new regression tests.
|
|
179
|
+
|
|
180
|
+
## 0.2.3 (2026-07-15) — Critical: unescaped template substitution allowed workflow injection
|
|
181
|
+
|
|
182
|
+
Found by a Siege security audit run before rolling shipflow out to other
|
|
183
|
+
repos, immediately after 0.2.1/0.2.2 landed the previous two fixes.
|
|
184
|
+
|
|
185
|
+
- **Critical, fixed:** `render.mjs`'s `renderTemplate` did pure string
|
|
186
|
+
substitution with zero escaping. `config.branches.dev`/`main` and
|
|
187
|
+
`config.release.releaseCredential` — all sourced from
|
|
188
|
+
`.github/shipflow.json`, a file anyone with repo **write** access can
|
|
189
|
+
edit, not just the admin who ran shipflow's setup — were substituted
|
|
190
|
+
directly into single-quoted YAML string comparisons and a
|
|
191
|
+
`${{ secrets.X }}` GitHub Actions expression with no validation.
|
|
192
|
+
Concretely: a `branches.dev` value of `dev' || 'x'=='x` rendered the
|
|
193
|
+
auto-merge job's `if:` condition to `... == 'dev' || 'x'=='x'` —
|
|
194
|
+
unconditionally true, enabling auto-merge on **any** pull request into
|
|
195
|
+
`main`, not just genuine `dev`-branch promotions. A `releaseCredential`
|
|
196
|
+
value containing a newline could inject arbitrary new YAML keys/steps
|
|
197
|
+
into the committed, then-executed workflow file. Both are a privilege
|
|
198
|
+
escalation: a repo-write-level actor reaching an admin-scoped mutation
|
|
199
|
+
through the credential the rendered workflow runs with.
|
|
200
|
+
- **Fix:** `renderTemplate` now validates each substituted value against a
|
|
201
|
+
per-token safety rule before rendering — `DEV_BRANCH`/`MAIN_BRANCH` reject
|
|
202
|
+
any single quote or newline; `RELEASE_CREDENTIAL_SECRET` must match
|
|
203
|
+
GitHub's own secret-naming rule (`^[A-Za-z_][A-Za-z0-9_]*$`). A rejected
|
|
204
|
+
value throws rather than silently rendering unsafe YAML.
|
|
205
|
+
- **Also fixed:** `bin/shipflow.js`'s `cmdPlan`/`cmdApply` never wrapped
|
|
206
|
+
`computePlan` in a try/catch, so this (and the pre-existing "missing
|
|
207
|
+
param") error would have crashed with a raw stack trace instead of the
|
|
208
|
+
clean `{"error": ...}` JSON contract every other failure mode uses —
|
|
209
|
+
breaking the "every command prints JSON to stdout" guarantee agents rely
|
|
210
|
+
on to parse output.
|
|
211
|
+
- New regression tests assert the exploit renders are rejected, and that
|
|
212
|
+
ordinary branch/secret names still render normally.
|
|
213
|
+
|
|
214
|
+
## 0.2.2 (2026-07-15) — `label-release-pending` never fires under `GITHUB_TOKEN`
|
|
215
|
+
|
|
216
|
+
Found by the same dogfood run as 0.2.1, one merge later — a second, more
|
|
217
|
+
serious bug than the missing `--repo`: the manual-gate release-ask flow's
|
|
218
|
+
whole premise (a durable label survives the async gap between auto-merge
|
|
219
|
+
enabling and completing) silently didn't work at all.
|
|
220
|
+
|
|
221
|
+
- **Root cause: GitHub's loop-prevention rule.** A PR auto-merged via `gh pr
|
|
222
|
+
merge --auto` run under the default `secrets.GITHUB_TOKEN` completes
|
|
223
|
+
(later, once checks pass) attributed to the `github-actions[bot]`
|
|
224
|
+
identity. A `pull_request: closed` event from that bot-attributed merge
|
|
225
|
+
does **not** trigger this or any other workflow's `on: pull_request`
|
|
226
|
+
handlers. Confirmed empirically, not just from docs: an otherwise-identical
|
|
227
|
+
promotion PR merged by a real, PAT-authenticated actor fired the
|
|
228
|
+
closed-event trigger within 2 seconds; one completed by
|
|
229
|
+
`GITHUB_TOKEN`-enabled auto-merge fired **no run at all**, even after
|
|
230
|
+
100+ seconds of polling. This means `label-release-pending` never ran for
|
|
231
|
+
any normally-auto-merged promotion — only for a promotion a human merged
|
|
232
|
+
by hand — which is the opposite of the common case the feature exists for.
|
|
233
|
+
- **Fix: both `gh` calls now use `config.release.releaseCredential`** instead
|
|
234
|
+
of a hardcoded `secrets.GITHUB_TOKEN`. Wired a new `RELEASE_CREDENTIAL_SECRET`
|
|
235
|
+
template token through `render.mjs` and `plan.mjs`'s
|
|
236
|
+
`computeTemplatePlanEntry` (previously `releaseCredential` was read by
|
|
237
|
+
`detect.mjs` only to check whether a named secret *existed* — it was never
|
|
238
|
+
actually substituted into the rendered workflow).
|
|
239
|
+
- **First-run setup (SKILL.md) now has an explicit step** requiring the user
|
|
240
|
+
to create a real PAT/App-installation-token secret and record its name in
|
|
241
|
+
`release.releaseCredential` — defaulting to `GITHUB_TOKEN` is called out as
|
|
242
|
+
a silent-failure trap, not a safe default. `config.example.json`'s
|
|
243
|
+
placeholder changed from `"GITHUB_TOKEN"` to `"SHIPFLOW_AUTOMERGE_PAT"` so
|
|
244
|
+
copying the example doesn't propagate the trap.
|
|
245
|
+
- New regression test asserts both `GH_TOKEN` lines use the configured
|
|
246
|
+
secret name and never fall back to a hardcoded `GITHUB_TOKEN`.
|
|
247
|
+
|
|
248
|
+
## 0.2.1 (2026-07-14) — rendered workflow was missing `--repo`
|
|
249
|
+
|
|
250
|
+
Found by dogfooding shipflow on its own home repo (`claude-skills`) — the
|
|
251
|
+
very first live promotion PR after switching over would have silently
|
|
252
|
+
broken auto-merge and release labeling.
|
|
253
|
+
|
|
254
|
+
- **Fix: both `gh` calls in the rendered `dev-to-main-automerge.yml` now pass
|
|
255
|
+
`--repo "${{ github.repository }}"` explicitly.** Neither the `auto-merge`
|
|
256
|
+
job's `gh pr merge` nor the `label-release-pending` job's `gh pr edit` had
|
|
257
|
+
it, and the workflow has no `actions/checkout` step for `gh` to infer the
|
|
258
|
+
repo from — every run failed with `fatal: not a git repository (or any of
|
|
259
|
+
the parent directories): .git`. This masked itself in the first dogfood
|
|
260
|
+
migration only because the hand-built workflow it was replacing (which did
|
|
261
|
+
pass `--repo`) happened to still be present on `main` and fired on the same
|
|
262
|
+
transitional PR.
|
|
263
|
+
- **New regression tests** (`tests/render.test.mjs`) read the actual
|
|
264
|
+
`.tmpl` file's rendered output and assert `--repo` is present on both `gh`
|
|
265
|
+
invocations — no prior test read the template's real command lines, only a
|
|
266
|
+
synthetic placeholder string, so this shipped with zero coverage of the
|
|
267
|
+
actual `gh` calls.
|
|
268
|
+
|
|
269
|
+
## 0.2.0 (2026-07-14) — first live-repo fixes
|
|
270
|
+
|
|
271
|
+
Fixes found by running shipflow end-to-end against a real repo
|
|
272
|
+
(`natejswenson/1.00s`) for the first time, beyond the read-only smoke test
|
|
273
|
+
against `claude-skills` itself:
|
|
274
|
+
|
|
275
|
+
- **First-run setup is now an explicit, unskippable interview.** SKILL.md's
|
|
276
|
+
setup steps must present detected branch names, `requiredChecks`, and the
|
|
277
|
+
resolved `protectionOwner` and wait for confirmation before writing
|
|
278
|
+
`.github/shipflow.json` — even when the detected values already look
|
|
279
|
+
correct. Previously nothing stopped an orchestrating agent from silently
|
|
280
|
+
narrating findings and proceeding straight to the config write.
|
|
281
|
+
- **Default-branch mismatch detection.** `detect` now reports the repo's
|
|
282
|
+
actual GitHub default branch (`repoSettings.defaultBranch`). First-run
|
|
283
|
+
setup surfaces a mismatch against the assumed `main` name and asks the
|
|
284
|
+
user to either map shipflow's `main` role onto the existing default branch
|
|
285
|
+
name, or rename the repo's default branch via the new
|
|
286
|
+
`rename-default-branch` command.
|
|
287
|
+
- **New `rename-default-branch` command**, wrapping GitHub's native
|
|
288
|
+
branch-rename endpoint (which retargets the default-branch pointer and
|
|
289
|
+
open PRs automatically when the renamed branch is the current default).
|
|
290
|
+
- **Honest classification of the tier-gated ruleset failure.** Creating the
|
|
291
|
+
deletion-protection ruleset 403s on private repos without GitHub
|
|
292
|
+
Pro/Team/Enterprise (rulesets are free for public repos only). This now
|
|
293
|
+
surfaces as a `skipped` entry with a clear reason instead of an `errors`
|
|
294
|
+
entry — it's an expected environment limitation, not a shipflow bug. No
|
|
295
|
+
fallback to classic branch protection was added (declined — out of scope
|
|
296
|
+
for this fix).
|
|
297
|
+
- **`requiredChecks` candidates are now filtered to actually PR-triggered
|
|
298
|
+
jobs.** `detect`'s `workflows.jobNames` previously listed every job name
|
|
299
|
+
from every workflow file regardless of its `on:` trigger — a
|
|
300
|
+
`schedule`/`workflow_dispatch`-only job (like `1.00s`'s `weekly-archive.yml`)
|
|
301
|
+
could be picked as a required check that would never run on a PR and
|
|
302
|
+
would block every future merge forever. Now only jobs from
|
|
303
|
+
`pull_request`/`pull_request_target`-triggered workflows are offered as
|
|
304
|
+
candidates.
|
|
305
|
+
- **Agent-driven CI scaffolding when no PR check exists.** Rather than
|
|
306
|
+
teaching shipflow's deterministic CLI about every language/build-tool
|
|
307
|
+
ecosystem, first-run setup now has the orchestrating agent investigate the
|
|
308
|
+
repo and draft a starter `pull_request`-triggered build+test workflow when
|
|
309
|
+
the (now-accurate) required-checks candidate list is empty, with the same
|
|
310
|
+
confirm-before-write discipline as every other step — never a silent
|
|
311
|
+
overwrite, always shown to the user first.
|
|
312
|
+
|
|
313
|
+
## 0.1.0 (2026-07-14) — Phase A: manual-gate core
|
|
314
|
+
|
|
315
|
+
Initial release. Implements the fully-specified, reference-repo-validated slice
|
|
316
|
+
of the [shipflow design](../../docs/plans/2026-07-14-shipflow-skill-design.md):
|
|
317
|
+
|
|
318
|
+
- Long-lived `dev`/`main` branches with configurable names.
|
|
319
|
+
- `dev → main` promotion PRs that auto-merge once configured required checks pass.
|
|
320
|
+
- Automatic branch cleanup (`delete_branch_on_merge` + a deletion ruleset) for
|
|
321
|
+
every branch except `dev`/`main` — zero custom mutation logic, a native
|
|
322
|
+
GitHub setting.
|
|
323
|
+
- `protectionOwner` detection: defers to an existing settings-as-code
|
|
324
|
+
mechanism (e.g. `repo-settings.sh`, Terraform) rather than installing a
|
|
325
|
+
competing ruleset, with an explicit user prompt when protection exists
|
|
326
|
+
with no artifact behind it.
|
|
327
|
+
- `release.mode: "manual-gate"` — the deliberate, ask-before-tagging release
|
|
328
|
+
flow (a durable `release-pending` label survives the async gap between a
|
|
329
|
+
promotion merging and the next interactive `shipflow` run).
|
|
330
|
+
|
|
331
|
+
`release.mode: "auto"` (release-please-driven automatic tagging) is accepted
|
|
332
|
+
in the config schema but **not yet implemented** — `apply.mjs` refuses to run
|
|
333
|
+
against an `"auto"` config with a clear "not yet implemented" error rather
|
|
334
|
+
than silently no-oping. Tracked as Phase B; needs a live GitHub sandbox to
|
|
335
|
+
build and verify the two-hop `RELEASE_PAT` credential wiring and the
|
|
336
|
+
release-please byte-equality pre-flight safely.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nate Swenson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# shipflow
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@natjswenson/shipflow)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
A Claude Code skill that scaffolds a configurable branching, auto-merge, branch-cleanup, and release-tagging workflow into any repo — across three selectable patterns, not just one.
|
|
7
|
+
|
|
8
|
+
Run it in a target repo and it detects existing branch protection, CI checks, release conventions, and which branching pattern the repo already uses, shows you a plan, and only mutates anything after you confirm. The skill package is identical everywhere — the actual policy (workflow pattern, branch names, required checks, release mode, ...) lives in the target repo's own `.github/shipflow.json`, committed and auditable.
|
|
9
|
+
|
|
10
|
+
## Patterns
|
|
11
|
+
|
|
12
|
+
| Pattern | Shape |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `dev-main-promotion` | Long-lived `dev` + `main`; a promotion PR auto-merges `dev` into `main` |
|
|
15
|
+
| `github-flow` | Single long-lived `main`; every PR merges (and auto-merges) directly to it |
|
|
16
|
+
| `gitflow` | `develop` + `main` + transient `release/*`/`hotfix/*` branches, for software maintaining multiple released versions concurrently |
|
|
17
|
+
|
|
18
|
+
`shipflow detect` scores all three against the repo's existing shape (branches, tags, workflow files) and either confirms a confident match with you or asks you to pick when detection is ambiguous or the repo is greenfield — it never silently picks one.
|
|
19
|
+
|
|
20
|
+
## How it works
|
|
21
|
+
|
|
22
|
+
1. **`/shipflow` in Claude Code** runs an interactive setup interview — detects the workflow pattern, branch protection, CI, and the default branch, confirms them (plus `requiredChecks` and `protectionOwner`) with you, and writes `.github/shipflow.json`.
|
|
23
|
+
2. **`shipflow plan`** diffs that config against live repo state and shows exactly what would change, before anything is touched.
|
|
24
|
+
3. **`shipflow apply`** — only after you confirm — renders the resolved pattern's workflow file(s) and makes the confirmed mutations. Nothing happens outside what the plan showed.
|
|
25
|
+
4. Ongoing: promotions/merges auto-merge once required checks pass; a durable `release-pending` label survives the async gap until a later `shipflow releases` check asks whether to cut a release.
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
All deterministic work runs through the published CLI:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npx -y @natjswenson/shipflow@latest detect --repo . --main main --dev dev
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
> **Always pin `@latest`.** Without an explicit version/tag, `npx` can silently resolve a stale install already on your `PATH` instead of fetching the current version from the registry — with no warning. If you've ever run `npm install -g @natjswenson/shipflow` for manual testing, remove it: `npm uninstall -g @natjswenson/shipflow`.
|
|
36
|
+
|
|
37
|
+
Full interactive setup flow: [`skills/shipflow/SKILL.md`](skills/shipflow/SKILL.md).
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
| Command | What it does |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `detect --repo <path> [--main <name>] [--dev <name>]` | Inspect live repo state: branch protection, CI checks, release conventions |
|
|
44
|
+
| `plan --repo <path>` | Diff `.github/shipflow.json` against live state; prints what would change + a state hash |
|
|
45
|
+
| `apply --repo <path> --expect-state-hash <hash> [--dry-run] [--force <id> --force-reason <text>]` | Apply a confirmed plan |
|
|
46
|
+
| `releases --repo <path>` | List `dev → main` promotions still labeled `release-pending` |
|
|
47
|
+
| `release-dispatch --repo <path> --pr <n> --workflow-file <f>... --ref <ref>` | Dispatch each changed skill's release workflow; clear the label on success |
|
|
48
|
+
| `rename-default-branch --repo <path> --branch <old> --to <new>` | One-time bootstrap: rename a repo's default branch |
|
|
49
|
+
|
|
50
|
+
Every command prints JSON to stdout.
|
|
51
|
+
|
|
52
|
+
## Status
|
|
53
|
+
|
|
54
|
+
**`release.mode: "manual-gate"`** (the only implemented mode) is live-validated end-to-end — dogfooded on this repo (`claude-skills`) and an external repo (`natejswenson/1.00s`). A full Siege security audit found and fixed 9 findings (1 Critical, 1 High, the rest Medium/Low) before wider rollout; zero Critical/High findings remain open. See [`CHANGELOG.md`](./CHANGELOG.md) for the fix-by-fix history.
|
|
55
|
+
|
|
56
|
+
**`release.mode: "auto"`** (fully automatic tagging via `release-please`) is accepted in the config schema but not yet implemented — `apply` refuses with a clear error until it ships.
|
|
57
|
+
|
|
58
|
+
## Design
|
|
59
|
+
|
|
60
|
+
[`docs/plans/2026-07-14-shipflow-skill-design.md`](../../docs/plans/2026-07-14-shipflow-skill-design.md) — the original single-pattern design (7 rounds of adversarial review, score 12 → 0).
|
|
61
|
+
|
|
62
|
+
[`docs/plans/2026-07-16-shipflow-multi-pattern-design.md`](../../docs/plans/2026-07-16-shipflow-multi-pattern-design.md) — the multi-pattern registry design (10 rounds of adversarial review).
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@natjswenson/shipflow",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Scaffold a configurable dev/main branching, auto-merge, branch-cleanup, and release-tagging workflow into any repo",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nate Swenson",
|
|
@@ -41,7 +41,9 @@
|
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"test": "node --test \"tests/**/*.test.mjs\"",
|
|
44
|
-
"audit": "npm audit --audit-level=moderate"
|
|
44
|
+
"audit": "npm audit --audit-level=moderate",
|
|
45
|
+
"prepack": "cp ../../README.md ../../LICENSE ../../CHANGELOG.md .",
|
|
46
|
+
"postpack": "rm -f README.md LICENSE CHANGELOG.md"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"yaml": "^2.9.0"
|