@cyanheads/mcp-ts-core 0.13.2 → 0.13.4

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 (117) hide show
  1. package/AGENTS.md +12 -10
  2. package/CLAUDE.md +12 -10
  3. package/README.md +2 -2
  4. package/changelog/0.13.x/0.13.3.md +44 -0
  5. package/changelog/0.13.x/0.13.4.md +65 -0
  6. package/changelog/template.md +7 -7
  7. package/dist/config/appRoot.d.ts.map +1 -1
  8. package/dist/config/appRoot.js +48 -16
  9. package/dist/config/appRoot.js.map +1 -1
  10. package/dist/core/app.d.ts +20 -0
  11. package/dist/core/app.d.ts.map +1 -1
  12. package/dist/core/app.js +1 -0
  13. package/dist/core/app.js.map +1 -1
  14. package/dist/core/index.d.ts +1 -0
  15. package/dist/core/index.d.ts.map +1 -1
  16. package/dist/core/index.js.map +1 -1
  17. package/dist/linter/rules/schema-rules.d.ts +19 -0
  18. package/dist/linter/rules/schema-rules.d.ts.map +1 -1
  19. package/dist/linter/rules/schema-rules.js +36 -0
  20. package/dist/linter/rules/schema-rules.js.map +1 -1
  21. package/dist/linter/rules/tool-rules.d.ts +17 -0
  22. package/dist/linter/rules/tool-rules.d.ts.map +1 -1
  23. package/dist/linter/rules/tool-rules.js +97 -1
  24. package/dist/linter/rules/tool-rules.js.map +1 -1
  25. package/dist/mcp-server/handlerContext.d.ts +6 -0
  26. package/dist/mcp-server/handlerContext.d.ts.map +1 -1
  27. package/dist/mcp-server/handlerContext.js.map +1 -1
  28. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
  29. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +17 -9
  30. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
  31. package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts +114 -0
  32. package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts.map +1 -0
  33. package/dist/mcp-server/tools/utils/inputPrevalidation.js +428 -0
  34. package/dist/mcp-server/tools/utils/inputPrevalidation.js.map +1 -0
  35. package/dist/mcp-server/tools/utils/strictenRecord.d.ts +42 -0
  36. package/dist/mcp-server/tools/utils/strictenRecord.d.ts.map +1 -0
  37. package/dist/mcp-server/tools/utils/strictenRecord.js +48 -0
  38. package/dist/mcp-server/tools/utils/strictenRecord.js.map +1 -0
  39. package/dist/mcp-server/tools/utils/toolDefinition.d.ts +27 -0
  40. package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -1
  41. package/dist/mcp-server/tools/utils/toolDefinition.js +35 -6
  42. package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -1
  43. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +28 -4
  44. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
  45. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +245 -23
  46. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
  47. package/dist/services/canvas/core/sqlGate.d.ts +14 -1
  48. package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
  49. package/dist/services/canvas/core/sqlGate.js +69 -7
  50. package/dist/services/canvas/core/sqlGate.js.map +1 -1
  51. package/dist/services/canvas/index.d.ts +1 -1
  52. package/dist/services/canvas/index.d.ts.map +1 -1
  53. package/dist/services/canvas/index.js +1 -1
  54. package/dist/services/canvas/index.js.map +1 -1
  55. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts +20 -0
  56. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
  57. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +75 -25
  58. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
  59. package/dist/services/canvas/types.d.ts +6 -1
  60. package/dist/services/canvas/types.d.ts.map +1 -1
  61. package/dist/types-global/errors.d.ts.map +1 -1
  62. package/dist/types-global/errors.js +4 -3
  63. package/dist/types-global/errors.js.map +1 -1
  64. package/dist/utils/index.d.ts +3 -2
  65. package/dist/utils/index.d.ts.map +1 -1
  66. package/dist/utils/index.js +3 -2
  67. package/dist/utils/index.js.map +1 -1
  68. package/dist/utils/internal/error-handler/errorHandler.d.ts +33 -0
  69. package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
  70. package/dist/utils/internal/error-handler/errorHandler.js +45 -3
  71. package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
  72. package/dist/utils/network/httpError.d.ts +13 -2
  73. package/dist/utils/network/httpError.d.ts.map +1 -1
  74. package/dist/utils/network/httpError.js +4 -2
  75. package/dist/utils/network/httpError.js.map +1 -1
  76. package/dist/utils/network/pacer.d.ts +117 -0
  77. package/dist/utils/network/pacer.d.ts.map +1 -0
  78. package/dist/utils/network/pacer.js +304 -0
  79. package/dist/utils/network/pacer.js.map +1 -0
  80. package/dist/utils/network/retry.d.ts +119 -3
  81. package/dist/utils/network/retry.d.ts.map +1 -1
  82. package/dist/utils/network/retry.js +176 -35
  83. package/dist/utils/network/retry.js.map +1 -1
  84. package/dist/utils/security/rateLimiter.d.ts +19 -1
  85. package/dist/utils/security/rateLimiter.d.ts.map +1 -1
  86. package/dist/utils/security/rateLimiter.js +49 -1
  87. package/dist/utils/security/rateLimiter.js.map +1 -1
  88. package/dist/utils/telemetry/attributes.d.ts +19 -0
  89. package/dist/utils/telemetry/attributes.d.ts.map +1 -1
  90. package/dist/utils/telemetry/attributes.js +30 -0
  91. package/dist/utils/telemetry/attributes.js.map +1 -1
  92. package/framework-skills/add-tool/SKILL.md +43 -2
  93. package/framework-skills/api-canvas/SKILL.md +8 -4
  94. package/framework-skills/api-config/SKILL.md +4 -4
  95. package/framework-skills/api-errors/SKILL.md +19 -10
  96. package/framework-skills/api-linter/SKILL.md +48 -3
  97. package/framework-skills/api-telemetry/SKILL.md +26 -2
  98. package/framework-skills/api-utils/SKILL.md +7 -3
  99. package/framework-skills/api-utils/references/security.md +2 -2
  100. package/framework-skills/design-mcp-server/SKILL.md +17 -2
  101. package/framework-skills/field-test/SKILL.md +3 -1
  102. package/framework-skills/git-wrapup/SKILL.md +90 -68
  103. package/framework-skills/orchestrations/SKILL.md +3 -3
  104. package/framework-skills/release-and-publish/SKILL.md +10 -10
  105. package/framework-skills/release-pr-review/SKILL.md +19 -26
  106. package/framework-skills/report-issue-framework/SKILL.md +6 -35
  107. package/framework-skills/report-issue-local/SKILL.md +6 -36
  108. package/framework-skills/tool-defs-analysis/SKILL.md +4 -4
  109. package/package.json +7 -7
  110. package/templates/.github/ISSUE_TEMPLATE/bug_report.yml +2 -2
  111. package/templates/.github/ISSUE_TEMPLATE/feature_request.yml +2 -2
  112. package/templates/.github/workflows/codeql.yml +39 -0
  113. package/templates/AGENTS.md +3 -1
  114. package/templates/CLAUDE.md +3 -1
  115. package/templates/changelog/template.md +7 -7
  116. package/templates/package.json +2 -2
  117. package/templates/src/index.ts +4 -3
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: git-wrapup
3
3
  description: >
4
- Land working-tree changes as logical commits — the work grouped by concern, topped by a release commit (version bump, changelog, regenerated artifacts). Verify, commit. Stops at "committed locally on main" — or, when the project releases through a release PR, at "release branch pushed, PR open". No tag, no push to main, no publish: the release-and-publish skill merges, tags, and ships from here. Distilled from the git_wrapup_instructions protocol.
4
+ Land working-tree changes as logical commits — the work grouped by concern, topped by a release commit (version bump, changelog, regenerated artifacts). The work commits land first, then the version bump, verification, and the release commit on top. Stops at "committed locally on main" — or, when the project releases through a release PR, at "release branch pushed, PR open". No tag, no push to main, no publish: the release-and-publish skill merges, tags, and ships from here. Distilled from the git_wrapup_instructions protocol.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.17"
7
+ version: "1.19"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -28,7 +28,7 @@ A project can route every release through a pull request — one PR per version,
28
28
  | **gated** | commit stack on `release/<version>`, branch pushed, PR open | a review pass on the PR (`release-pr-review` skill), then a separate `release-and-publish` run fast-forwards `main`, tags, and ships |
29
29
  | **straight-through** | same as gated | the same agent continues straight into `release-and-publish` |
30
30
 
31
- The branch is created at wrapup time, never before: work happens on `main` until the version is known, then the uncommitted tree moves to `release/<version>` in one step (step 7). The commit stack, the release commit, and the tag format are identical in every mode — the PR adds an artifact around them, it does not change them.
31
+ The branch is created at wrapup time, never before: work happens on `main` until the version is known, then the uncommitted tree moves to `release/<version>` in one step (step 3), ahead of the first commit. The commit stack, the release commit, and the tag format are identical in every mode — the PR adds an artifact around them, it does not change them.
32
32
 
33
33
  ## Pre-wrapup gate checklist
34
34
 
@@ -45,7 +45,7 @@ Every item must be true before starting wrapup. Committing means releasing — a
45
45
  - [ ] **GH issues updated** — issues addressed by this work commented with what landed and any follow-ups needed. Concise. Backlinked as needed.
46
46
  - [ ] **Docs updated** — surgical updates to existing docs as needed. New docs for new features. No large rewrites for documentation that's still accurate.
47
47
 
48
- If any gate is red, fix it before proceeding. This skill re-verifies build + tests in step 6, but starting wrapup on a broken tree wastes the version number and creates a revert-or-amend situation.
48
+ If any gate is red, fix it before proceeding. This skill re-verifies build + tests in step 7, but the work is committed by then — starting wrapup on a broken tree wastes the version number and turns the fix into an extra commit on a stack that should have been green.
49
49
 
50
50
  ## Steps
51
51
 
@@ -56,10 +56,12 @@ Understand what's about to ship before touching version numbers:
56
56
  ```bash
57
57
  git status
58
58
  git log v<latest-tag>..HEAD --oneline # commits since last release
59
- git diff --stat # uncommitted changes
60
- git diff # review the actual content
59
+ git diff HEAD --stat # every uncommitted change, staged or not
60
+ git diff HEAD # review the actual content
61
61
  ```
62
62
 
63
+ Diff against `HEAD`, not the index: plain `git diff` omits staged changes entirely, so a group already staged before wrap-up began — a `git mv` from a migration step, a hook's output — shows up in `git status` as a line to scroll past and nowhere in the diff review. Whatever is staged is part of what ships and gets grouped in step 3 like everything else.
64
+
63
65
  If the working tree is clean AND there are no commits since the last tag, halt — nothing to wrap up.
64
66
 
65
67
  ### 2. Determine the new version
@@ -74,7 +76,64 @@ Read the current version from `package.json`. Apply the intended bump:
74
76
 
75
77
  Default to **patch** unless the diff clearly warrants minor or major.
76
78
 
77
- ### 3. Bump version everywhere
79
+ ### 3. Commit the work — one commit per concern
80
+
81
+ **Release PR mode only — move to the release branch first, before the first commit:**
82
+
83
+ ```bash
84
+ git branch --show-current # must be main
85
+ git switch -c release/<version> # uncommitted work rides along
86
+ ```
87
+
88
+ Commits never land on `main` in this mode. If a `release/*` branch already exists locally, a prior release PR was never merged — halt and report it rather than stacking a second release on top.
89
+
90
+ **The work is committed before the version is bumped.** Work concerns routinely share a file with the version — a dependency refresh edits `package.json`, a doc edit lands in a `CLAUDE.md`/`AGENTS.md` that pins a version string — and the file is the atomic boundary, so whichever commit comes first takes the file whole. Committing the work first leaves the version hunk (step 4) as the only thing those files carry into the release commit.
91
+
92
+ Do NOT `git add -A` into one commit. Group the working tree into a handful of logical commits — never one blob. A feature spanning multiple layers splits by layer: runtime/logic, linter/tooling, docs/skills. Unrelated changes (two separate fixes, an incidental doc tweak) are their own commits. A dependency refresh is its own commit — `chore(deps): …` carrying `package.json`, the lockfile, and any pins it moved elsewhere. Work commits do not carry the version.
93
+
94
+ Stage each group explicitly, commit it by pathspec, then move to the next:
95
+
96
+ ```bash
97
+ git add <paths-for-this-concern>
98
+ git commit --only <paths-for-this-concern> -m "<subject>" -m "<body>"
99
+ # repeat per concern
100
+ ```
101
+
102
+ **Commit by pathspec, never the bare index.** A bare `git commit` commits everything staged, not the paths just added, so anything staged before wrap-up began — a `git mv` left by a migration step, a concurrent stage from a second session or a hook — rides into the first concern's commit. `--only` takes the named paths' working-tree content and disregards the rest of the index, so a pre-staged group never rides along; it stays staged, to be committed as its own concern (`chore(skills): move the skill tree to framework-skills/`) or reported. Anything still staged when the release commit lands then fails step 10's clean-tree check instead of shipping silently.
103
+
104
+ **The file is the atomic boundary:** NEVER split a single file's working-tree changes across commits, regardless of mechanism — not `git add -p`, not an index-only patch (`git apply --cached`), not editing the file between commits to remove-then-re-add a hunk. When one file serves two concerns, it ships whole in the commit of its dominant concern; a later commit may touch the file again only for changes made AFTER the first commit (the version bump applied in step 4).
105
+
106
+ **Subject format:** Conventional Commits, no version in the subject — `feat: hosted server endpoint`, `fix: handle empty SPARQL result sets`, `feat(linter): enrichment contract rules`, `docs: document the enrichment block`, `chore(deps): refresh dev dependencies`.
107
+
108
+ **Body: every commit has one, and it is one or two lines.** Uniform across the stack — no commit ships subject-only, none ships a paragraph. One sentence stating the *why* or the load-bearing constraint, a second only if the first genuinely cannot carry it. Two lines is the hard ceiling.
109
+
110
+ ```
111
+ fix: handle empty SPARQL result sets
112
+
113
+ Upstream returns 200 with an empty bindings array rather than 404.
114
+ ```
115
+
116
+ **Never put a closing keyword in a commit body.** `Fixes #N`, `Closes #N`, `Resolves #N` and friends close the issue the moment the commit is pushed — before the close-out comment recording what shipped, so the issue closes with no account of the fix. Reference issues as bare `(#N)` backlinks in the subject or body; closing is a deliberate later step.
117
+
118
+ A body is too long the moment it:
119
+ - enumerates the files, subsystems, or symbols touched — that is `git show --stat`
120
+ - walks through how the implementation works — that is the code
121
+ - narrates a fix's mechanism across multiple sentences — that is the changelog entry
122
+ - runs to a second paragraph, ever
123
+
124
+ The changelog carries the depth, the tag carries the headline, the commit carries one line of why. When the body wants to grow, that pressure is telling you the content belongs in the changelog entry.
125
+
126
+ **Rules:**
127
+ - Plain `-m` flag only — no heredoc, no command substitution
128
+ - No `Co-authored-by` or `Generated with` trailers
129
+ - No marketing adjectives ("comprehensive", "robust", "enhanced", "seamless", "improved")
130
+ - Each commit message stands alone for someone reading `git log` — no chat context, option numbers, or "as discussed"
131
+
132
+ **Right-size it.** "Group by concern" is not "always split." A genuinely single-concern change — one fix, a dependency bump, a small doc edit — is one work commit, with step 8's release commit on top of it. Only when the version bump *is* the whole change — a republish, a metadata-only patch with nothing else in the tree — is there no work commit to make, and step 8's release commit becomes the entire stack. The failure mode to prevent is the inverse: a large, multi-layer feature crammed into one commit alongside the release artifacts.
133
+
134
+ When every concern is committed, `git status` is clean. That clean tree is what makes everything steps 4–6 touch a release artifact and nothing else — confirm it before moving on.
135
+
136
+ ### 4. Bump version everywhere
78
137
 
79
138
  Every file that declares a version must be updated. Skip any file that doesn't exist in the project. For `@cyanheads/mcp-ts-core` projects:
80
139
 
@@ -94,7 +153,7 @@ grep -rn "0.9.7" . --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=c
94
153
 
95
154
  Resolve hits case by case — historical changelog entries are correct as-is; everything else should match the new version.
96
155
 
97
- ### 4. Author the changelog
156
+ ### 5. Author the changelog
98
157
 
99
158
  Create `changelog/<major.minor>.x/<version>.md`. Use `changelog/template.md` as the format reference — never edit, rename, or move that file.
100
159
 
@@ -108,7 +167,7 @@ security: false # true ONLY for a security fix in this server's own source
108
167
  ---
109
168
  ```
110
169
 
111
- **Write `summary:` LAST, derived from the body you just wrote — never independently.** It is the line most readers see, and it propagates unedited to three further surfaces: the `CHANGELOG.md` rollup, the GitHub Release body, and the annotated tag (which cannot be edited once pushed). Written from recollection rather than from the body, it reliably names a mechanism that was never built or a target that was never fixed, while the body beside it stays correct. After writing it, re-read the body and confirm every claim in the summary appears there. Derived-from-the-body means the facts come from the body — not that every body item appears: the summary is the tag's theme line, and comma-stitching every change into an inventory near the 350-char cap is the failure mode.
170
+ **Write `summary:` LAST, derived from the body you just wrote — never independently.** It is the line most readers see: `changelog:build` copies it verbatim into the `CHANGELOG.md` rollup, which ships inside the npm tarball, and in release PR mode it opens the PR body. Written from recollection rather than from the body, it reliably names a mechanism that was never built or a target that was never fixed, while the body beside it stays correct. After writing it, re-read the body and confirm every claim in the summary appears there. Derived-from-the-body means the facts come from the body — not that every body item appears: it is one headline, and comma-stitching every change into an inventory near the 350-char cap is the failure mode.
112
171
 
113
172
  **`security:` is a source-code signal — not a dependency-CVE signal.** Set `security: true` only when this release fixes a vulnerability or adds hardening in code *this server ships*. A dependency or transitive CVE bump — even one that clears an advisory (`bun audit` going 1 → 0) — is routine maintenance: record it under `## Dependencies` with the advisory ID and leave the flag `false`. The `🛡️ Security` badge answers "does the server itself have a vuln"; a dep bump must not trip it.
114
173
 
@@ -118,7 +177,7 @@ security: false # true ONLY for a security fix in this server's own source
118
177
 
119
178
  **Re-read the entry file after writing it, then sweep for harness markup:** `grep -rlF -e '</invoke>' -e '</content>' changelog/` must print nothing. A stray closing tag at EOF is the authoring tool's own syntax bleeding into the file; `changelog/` is in `package.json` `files`, so it ships inside the npm tarball, and `changelog:check` cannot catch it — the rollup drops the trailing line, so a clean `CHANGELOG.md` proves nothing about the entry.
120
179
 
121
- ### 5. Regenerate derived artifacts
180
+ ### 6. Regenerate derived artifacts
122
181
 
123
182
  ```bash
124
183
  bun run changelog:build # rebuilds CHANGELOG.md rollup from per-version files
@@ -127,9 +186,9 @@ bun run tree # regenerates docs/tree.md — run when files were ad
127
186
 
128
187
  Both scripts are idempotent — safe to run even if nothing changed.
129
188
 
130
- ### 6. Run the verification gate
189
+ ### 7. Run the verification gate
131
190
 
132
- The tree being committed must pass verification. Both must succeed:
191
+ The stack being shipped must pass verification. Both must succeed:
133
192
 
134
193
  ```bash
135
194
  bun run devcheck
@@ -137,67 +196,28 @@ bun run test:all # or `bun run test` if no test:all script exists
137
196
  bun run test:package # only if the script exists — NOT part of test:all
138
197
  ```
139
198
 
140
- **If either fails, halt.** Do not bypass verification to land the commit. Fix the issue first, then re-run from step 6.
199
+ **If either fails, halt.** Do not bypass verification to land the release commit.
141
200
 
142
- ### 7. Commit — group by concern, release artifacts on top
201
+ The work is already committed by this point, so the fix is a new commit on top of the stack, under step 3's conventions — never `git commit --amend`, never a rebase, reset, or any other rewrite of a commit the stack already carries. Land the fix, then re-run this step. The same holds when the gate passes but leaves the tree dirty: `devcheck` auto-fixes as it runs, and a formatter fix to a file committed in step 3 is a follow-up commit of its own, not something to fold into the release commit.
143
202
 
144
- **Release PR mode only — move to the release branch first, before the first commit:**
203
+ Only the version bump, the changelog entry, and the regenerated artifacts may still be uncommitted when this step goes green.
145
204
 
146
- ```bash
147
- git branch --show-current # must be main
148
- git switch -c release/<version> # uncommitted work rides along
149
- ```
205
+ ### 8. Commit the release artifacts
150
206
 
151
- Commits never land on `main` in this mode. If a `release/*` branch already exists locally, a prior release PR was never merged — halt and report it rather than stacking a second release on top.
152
-
153
- Do NOT `git add -A` into one commit. Group the working tree into a handful of logical commits — never one blob:
154
-
155
- 1. **The work — one commit per concern.** A feature spanning multiple layers splits by layer: runtime/logic, linter/tooling, docs/skills. Unrelated changes (two separate fixes, an incidental doc tweak) are their own commits. Work commits do not carry the version.
156
- 2. **The release commit — last, on top.** Version bumps (`package.json`, `server.json`, `manifest.json`, the plugin manifests, README badge, `CLAUDE.md`/`AGENTS.md`), the changelog entry, `CHANGELOG.md`, and `docs/tree.md` go in a single final commit that sits on top of the work stack — never mixed into a feature commit.
157
-
158
- Stage each group explicitly, commit it, then move to the next — the release commit goes last:
207
+ One commit, on top of the work stack from step 3, carrying only what steps 4–6 produced: the version bumps (`package.json`, `server.json`, `manifest.json`, the plugin manifests, README badge, `CLAUDE.md`/`AGENTS.md`), the changelog entry, `CHANGELOG.md`, and `docs/tree.md`.
159
208
 
160
209
  ```bash
161
- git add <paths-for-this-concern>
162
- git commit -m "<subject>"
163
- # repeat per concern; version + changelog + tree are the final commit
210
+ git add <release-artifact-paths>
211
+ git commit --only <release-artifact-paths> -m "chore(release): <version> — <theme>" -m "<body>"
164
212
  ```
165
213
 
166
- **The file is the atomic boundary:** NEVER split a single file's working-tree changes across commits, regardless of mechanism — not `git add -p`, not an index-only patch (`git apply --cached`), not editing the file between commits to remove-then-re-add a hunk. When one file serves two concerns, it ships whole in the commit of its dominant concern; a later commit may touch the file again only for changes made AFTER the first commit (a version badge bumped after the fix landed).
167
-
168
- **Subject format:** Conventional Commits.
169
- - Work commits (no version): `feat: hosted server endpoint`, `fix: handle empty SPARQL result sets`, `feat(linter): enrichment contract rules`, `docs: document the enrichment block`
170
- - Release commit (subject leads with the version): `chore(release): 0.2.1 — empty SPARQL result handling`
171
-
172
- **Body: every commit has one, and it is one or two lines.** Uniform across the stack — no commit ships subject-only, none ships a paragraph. One sentence stating the *why* or the load-bearing constraint, a second only if the first genuinely cannot carry it. Two lines is the hard ceiling.
173
-
174
- ```
175
- fix: handle empty SPARQL result sets
176
-
177
- Upstream returns 200 with an empty bindings array rather than 404.
178
- ```
179
-
180
- **Never put a closing keyword in a commit body.** `Fixes #N`, `Closes #N`, `Resolves #N` and friends close the issue the moment the commit is pushed — before the close-out comment recording what shipped, so the issue closes with no account of the fix. Reference issues as bare `(#N)` backlinks in the subject or body; closing is a deliberate later step.
181
-
182
- A body is too long the moment it:
183
- - enumerates the files, subsystems, or symbols touched — that is `git show --stat`
184
- - walks through how the implementation works — that is the code
185
- - narrates a fix's mechanism across multiple sentences — that is the changelog entry
186
- - runs to a second paragraph, ever
187
-
188
- The changelog carries the depth, the tag carries the headline, the commit carries one line of why. When the body wants to grow, that pressure is telling you the content belongs in the changelog entry.
189
-
190
- **Rules:**
191
- - Plain `-m` flag only — no heredoc, no command substitution
192
- - No `Co-authored-by` or `Generated with` trailers
193
- - No marketing adjectives ("comprehensive", "robust", "enhanced", "seamless", "improved")
194
- - Each commit message stands alone for someone reading `git log` — no chat context, option numbers, or "as discussed"
214
+ **Subject leads with the version:** `chore(release): 0.2.1 — empty SPARQL result handling`. Step 3's conventions carry over unchanged — pathspec staging rather than the bare index, a one- or two-line body, no closing keywords, no trailers, no marketing adjectives.
195
215
 
196
- **Right-size it.** "Group by concern" is not "always split." A genuinely single-concern change — one fix, a dependency bump, a small doc edit — is one work commit plus the release commit; when the change and its version bump are inseparable for a tiny patch, a single commit whose subject leads with the version is fine. The failure mode to prevent is the inverse: a large, multi-layer feature crammed into one commit alongside the release artifacts.
216
+ Anything else still uncommitted at this point was made after step 3 — a formatter auto-fix, a gate fix. It is committed on its own first, and the release commit goes on top of it; it is never folded in.
197
217
 
198
- ### 8. Open the release PR (release PR mode only)
218
+ ### 9. Open the release PR (release PR mode only)
199
219
 
200
- Skip this step entirely when the project has no release PR mode — go to step 9.
220
+ Skip this step entirely when the project has no release PR mode — go to step 10.
201
221
 
202
222
  ```bash
203
223
  git push -u origin release/<version>
@@ -208,10 +228,10 @@ gh pr create --base main --head release/<version> --title "<release commit subje
208
228
 
209
229
  **Body — always via `--body-file`, never an inline `--body` string** (backticks inside a double-quoted argument are command substitution and silently vanish). Write the file to a scratch location, not into the repo.
210
230
 
211
- The body is the release digest — the same headline digest the annotated tag will carry, plus a gates record. It is written here, reviewed on the PR, and copied into the tag at release time, so it is the one place the release notes get reviewed before they become permanent. Format:
231
+ The body is the release digest — the `## Changes` bullets and changelog link the annotated tag will carry, under a theme line and above a gates record that both stay on the PR. The digest is written here, reviewed on the PR, and copied into the tag at release time, so it is the one place the release notes get reviewed before they become permanent. Format:
212
232
 
213
233
  ```
214
- <theme — the changelog entry's summary: line, plain prose, one line>
234
+ <theme — the changelog entry's summary: line, plain prose, one line. It opens the PR; the tag's subject is written fresh at release time and is not this line>
215
235
 
216
236
  ## Changes
217
237
 
@@ -232,8 +252,8 @@ The body is the release digest — the same headline digest the annotated tag wi
232
252
 
233
253
  **Rules:**
234
254
  - **`## Changes` follows the tag rules exactly** (`release-and-publish` step 4): flat bullets, never Keep-a-Changelog section headers; complete at headline granularity — notable changes get their own bullet, minor/internal items share ONE grouped bullet; deps one line max, naming only what earns it; no narrative, no marketing adjectives. Depth lives in the changelog entry, which is in this PR's diff and linked on the last line.
235
- - **Every claim traces to the diff and to the changelog entry.** The body is derived from the entry you authored in step 4, never written independently of it.
236
- - **`## Gates` is the one release surface that carries gate results** — the exact commands from step 6 with their outcomes. It never enters the tag.
255
+ - **Every claim traces to the diff and to the changelog entry.** The body is derived from the entry you authored in step 5, never written independently of it.
256
+ - **`## Gates` is the one release surface that carries gate results** — the exact commands from step 7 with their outcomes. It never enters the tag.
237
257
  - **Issue references are bare `(#N)` backlinks — never a closing keyword** (`Closes #N`, `Fixes #N`); the merge would close the issue before its close-out comment lands.
238
258
  - **Changelog link is the final line**, same form as the tag, blank line above it.
239
259
  - Length is earned — a theme, two bullets, gates, and the link is a complete body for a small patch.
@@ -244,7 +264,7 @@ If the review pass changes what ships, `release-pr-review` updates `## Changes`
244
264
 
245
265
  **Straight-through mode:** continue directly into `release-and-publish`.
246
266
 
247
- ### 9. Verify end state
267
+ ### 10. Verify end state
248
268
 
249
269
  ```bash
250
270
  git log --oneline -8 # confirm the commit stack: work commits + release commit on top
@@ -268,7 +288,8 @@ If the working tree isn't clean or the release commit isn't at HEAD, something w
268
288
 
269
289
  ## Checklist
270
290
 
271
- - [ ] Diff reviewed end-to-end before version bump
291
+ - [ ] Diff reviewed end-to-end before the first commit
292
+ - [ ] Work concerns committed before the version bump — a version-bearing file a work concern also touches ships whole in that concern's commit, so the release commit brings it the version hunk alone
272
293
  - [ ] Version bumped in every declaring file (`package.json`, `server.json`, `manifest.json`, `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, README badge, `CLAUDE.md`/`AGENTS.md` if they pin a version) — verify by command, not by eye: `v=$(jq -r .version package.json); grep -rl "$v" package.json server.json manifest.json .claude-plugin/plugin.json .codex-plugin/plugin.json README.md | wc -l` must equal the count of files that exist, and `grep -c "Version-$v-" README.md` must print `1`. `lint:packaging` checks the README badge against `package.json`, so a stale badge now fails `devcheck` instead of shipping unnoticed — the grep still catches a badge written in a shape the check skips
273
294
  - [ ] GH issues addressed by this work commented with what landed (if working from GH issues)
274
295
  - [ ] Docs updated for any new or changed features
@@ -280,6 +301,7 @@ If the working tree isn't clean or the release commit isn't at HEAD, something w
280
301
  - [ ] `bun run test:package` passes, when the project defines it — it guards the public-export manifest and `test:all` does not run it
281
302
  - [ ] Release PR mode: stack committed on `release/<version>`, never on `main`
282
303
  - [ ] Work grouped into logical commits (large features split by layer); release artifacts (version + changelog + tree) committed separately on top, subject leading with the version
304
+ - [ ] A gate failure after the work is committed landed as a new commit on the stack — nothing amended, rebased, or otherwise rewritten
283
305
  - [ ] Every commit carries a body, and every body is one or two lines — none subject-only, none a paragraph
284
306
  - [ ] Release PR mode: branch pushed, PR open — title = release commit subject; body = theme line, `## Changes` in tag rules, `## Gates`, changelog link last (via `--body-file`, no closing keywords)
285
307
  - [ ] Working tree clean
@@ -4,7 +4,7 @@ description: >
4
4
  Pick and run a multi-phase workflow that chains foundational task skills (`git-wrapup`, `release-and-publish`, `maintenance`, `field-test`, `setup`, etc.) end-to-end. Routes user intent to a workflow file under `workflows/` — greenfield builds, maintenance + release, field-test + fix, or known-work + release. Single source for the universal rules (no commits without authorization, no destructive git, no marketing language), the orchestrator posture (own the goal, ground sub-agents in primary sources, verify against the goal), and the sub-agent strategy (orient block, parallel fanout, isolation, normalization) that apply across every workflow. Sub-agents are an optional capability — workflows run linearly when fanout isn't available.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.9"
7
+ version: "1.10"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -168,7 +168,7 @@ Editing phases and wrap-up phases never go in the same sub-agent. Editing sub-ag
168
168
  A target can declare that every release goes through a pull request (in its `CLAUDE.md`/`AGENTS.md`, or in the run's brief — mechanics in `git-wrapup`'s "Release PR mode"). The wrap-up + release phase then runs as **three sub-agents in sequence**, with an orchestrator check between each:
169
169
 
170
170
  1. **Wrap-up** — `git-wrapup`; halts with the stack committed on `release/<version>`, pushed, PR open.
171
- 2. **Review** — `release-pr-review`; reads the PR range through `code-simplifier` plus a correctness review, lands fixes as fixup commits autosquashed into the stack, force-with-lease pushes the release branch, syncs the PR body, leaves one summary comment. This is the one role that both edits and commits — scoped to the release branch, never `main`, never a tag.
171
+ 2. **Review** — `release-pr-review`; reads the PR range through `code-simplifier` plus a correctness review, lands fixes as ordinary commits on top of the stack, pushes the release branch, syncs the PR body, leaves one summary comment. This is the one role that both edits and commits — scoped to the release branch, never `main`, never a tag, never a rewrite of what is already pushed.
172
172
  3. **Release** — `release-and-publish`; `git merge --ff-only` onto `main` locally, tags `main`'s tip, pushes, publishes. Its brief must state that the review pass is finished — the skill halts without that line, and the orchestrator writes it only after confirming the review agent's report against the PR (`gh pr view --json state,headRefOid`, `git log --oneline main..HEAD`).
173
173
 
174
174
  Straight-through mode drops the review agent: one sub-agent runs wrap-up and release back to back, opening and merging the PR in the same session. Without a declaration there is no PR, and the stack lands on `main` directly.
@@ -211,7 +211,7 @@ If verification disagrees with the sub-agent's report, that's the signal to re-s
211
211
  | Reads, analysis, file edits (working tree only) | Implicit — initial workflow approval covers these |
212
212
  | Local commits, annotated tags | Explicit at workflow start; durable through workflow end |
213
213
  | Push to remote, npm / registry publish, GH release create, Docker push | Explicit at workflow start; durable through workflow end |
214
- | Destructive ops (force push, tag delete, remote branch delete, etc.) | Always re-confirm, never assume — two exceptions ride the release authorization: `release-pr-review`'s `--force-with-lease` on the run's own unmerged `release/<version>` branch, and `release-and-publish` deleting that branch once the PR reports `MERGED` |
214
+ | Destructive ops (force push, tag delete, remote branch delete, etc.) | Always re-confirm, never assume — one exception rides the release authorization: `release-and-publish` deleting the run's own `release/<version>` branch once the PR reports `MERGED` |
215
215
 
216
216
  Pipeline authorization is durable through to completion. Once the user authorizes a workflow run, don't re-ask at each phase boundary — proceed automatically through gates that pass. Conditions that always require a fresh check-in: destructive ops on shared resources, external actions without sign-off, errors that need human judgment.
217
217
 
@@ -4,7 +4,7 @@ description: >
4
4
  Ship a release end-to-end across every registry the project targets (npm, MCP Registry, GitHub Releases for `.mcpb` bundles, GHCR). Runs the final verification gate, fast-forwards `main` when the release rode a release PR, creates the annotated tag on the commit `main` now points at, pushes commits and tags, then publishes to each applicable destination. Assumes git wrapup (version bumps, changelog, commit stack — and in release PR mode, the pushed branch and open PR) is already complete — this skill is the post-wrapup merge + tag + publish workflow. Retries transient network failures on publish steps; halts with a partial-state report when retries are exhausted or the failure is terminal.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.17"
7
+ version: "2.19"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -18,7 +18,7 @@ This skill runs **after** git wrapup. By the time it's invoked:
18
18
  - `changelog/<major.minor>.x/<version>.md` is authored
19
19
  - `CHANGELOG.md` is regenerated
20
20
  - README and every version-bearing file is in sync
21
- - Release commit (`chore(release): <version> — <theme>`) is at HEAD
21
+ - Release commit (`chore(release): <version> — <theme>`) is at HEAD — or, in gated release PR mode, reachable from HEAD with only the review pass's commits above it
22
22
  - No tag exists yet — this skill creates it (step 4)
23
23
  - Working tree is clean
24
24
  - Release PR mode (see `git-wrapup`'s "Release PR mode"): HEAD is on `release/<version>`, the branch is pushed, the PR is open, and — in gated mode — the caller has confirmed the review pass is finished. Without that confirmation, halt: this skill never decides on its own that a review is done.
@@ -68,11 +68,11 @@ The user fixes locally and re-invokes. On re-invocation, already-published desti
68
68
  Read `package.json` → capture `version`. Then use your git tools to verify:
69
69
 
70
70
  - **Working tree is clean** — no uncommitted changes
71
- - **HEAD is the release commit** — `git log -1 --format=%s` starts with `chore(release): <version>`
71
+ - **The release commit is in the stack** — `git log -1 --format=%s` starts with `chore(release): <version>`, or, in gated release PR mode, `git log main..HEAD --format=%s` contains it with only the review pass's own commits above it (`release-pr-review` lands fixes as ordinary commits on top; the tag still goes on the tip). Any other commit above the release commit — new work, a second version — is a halt.
72
72
  - **Current branch** — `main`, or `release/<version>` in release PR mode. Anything else, halt.
73
73
  - **Release PR mode:** `gh pr view --json number,state,headRefOid` shows the PR `OPEN` with `headRefOid` equal to local HEAD. A mismatch means the branch has commits the PR doesn't (or the reverse) — halt and report both SHAs. Keep `number` and `headRefOid`: the merge check (step 3) and the tag body (step 4) need them after the checkout has moved to `main`.
74
74
 
75
- If working tree is dirty or HEAD isn't the release commit, halt.
75
+ If the working tree is dirty or the release commit isn't in the stack as described, halt.
76
76
 
77
77
  ### 2. Run the verification gate
78
78
 
@@ -121,14 +121,14 @@ If `v<version>` already exists and points at HEAD, a prior run created it — pr
121
121
 
122
122
  Write the message to a file through a quoted-delimiter heredoc and pass it with `-F`, never inline with `-m`: the body carries backticks, which a double-quoted string runs as command substitution and silently deletes, and apostrophes, which end a single-quoted string. The tag message renders as the GitHub Release body via `--notes-from-tag`. It must be structured markdown, not a flat string.
123
123
 
124
- **Release PR mode: the tag body is the PR body's `## Changes` bullets plus its final changelog link, verbatim** — `gh pr view <N> --json body -q .body` (`<N>` from step 1 — on `main` there is no branch for `gh` to infer it from), take the theme line as the subject, the bullets under `## Changes`, and the last line; drop `## Gates` and the headers. That digest was authored at wrapup and reviewed on the PR; re-authoring it here would publish unreviewed words. The one addition: append ` · release PR #<N>` to that final line, so the GitHub Release points at its audit trail (GitHub autolinks the bare `#<N>`). Without a PR, author it from the changelog entry at `changelog/<major.minor>.x/<version>.md` — every claim in the tag must appear in that file, and the file's `summary:` line is the tag's theme.
124
+ **Release PR mode: the tag body is the PR body's `## Changes` bullets plus its final changelog link, verbatim** — `gh pr view <N> --json body -q .body` (`<N>` from step 1 — on `main` there is no branch for `gh` to infer it from), take the bullets under `## Changes` and the last line; drop the PR's opening theme line, `## Gates`, and the headers. That digest was authored at wrapup and reviewed on the PR; re-authoring it here would publish unreviewed words. The subject is the one part not lifted — write it fresh, per the rules below. The one addition to the digest: append ` · release PR #<N>` to that final line, so the GitHub Release points at its audit trail (GitHub autolinks the bare `#<N>`). Without a PR, author the bullets from the changelog entry at `changelog/<major.minor>.x/<version>.md` — every claim in the tag must appear in that file.
125
125
 
126
126
  `--cleanup=whitespace` is load-bearing. The default cleanup (`strip`) deletes `#`-leading lines as comments, so markdown headers silently vanish from the tag body. `--cleanup=verbatim` is worse: it skips end-of-message normalization, so with tag signing enabled the signature is appended flush against the message's last character — git then can't parse its own signature (the tag reads as unsigned) and the whole `-----BEGIN SSH SIGNATURE-----` block publishes verbatim into the GitHub Release body.
127
127
 
128
128
  Format — a **headline digest**, never a section-by-section changelog mirror:
129
129
 
130
130
  ```
131
- <theme — omit version number, GitHub prepends v<VERSION>:>
131
+ <subject — one short theme written for this tag, ~60 chars; omit the version number, GitHub prepends v<VERSION>:>
132
132
 
133
133
  - <notable user-facing change> (#N)
134
134
  - <notable user-facing change> (#N)
@@ -141,7 +141,7 @@ Format — a **headline digest**, never a section-by-section changelog mirror:
141
141
  (` · release PR #<N>` only in release PR mode; without a PR the line ends at the changelog link.)
142
142
 
143
143
  **Rules:**
144
- - **Subject line is ONE short theme, at most ~60 characters, no semicolons, no clauses** — it becomes the GitHub Release title after `v<VERSION>: `. The digest lives in the bullets; a subject that summarizes each change is wrong even when every word is accurate. In release PR mode the PR body's opening paragraph is NOT the subject — write the theme fresh (the release commit's subject after the version and dash is usually it)
144
+ - **Subject line is ONE short theme, at most ~60 characters, no semicolons, no clauses** — it becomes the GitHub Release title after `v<VERSION>: `. The digest lives in the bullets; a subject that summarizes each change is wrong even when every word is accurate. **It is written for this tag, never lifted** — not from the changelog entry's `summary:`, which has a 350-character budget for a different surface, and not from the PR body's opening paragraph, which is that same line. The release commit's subject after the version and dash is usually the theme already
145
145
  - Subject line omits the version number (GitHub prepends `v<VERSION>:` to the release title)
146
146
  - **Flat bullets only — never Keep-a-Changelog section headers.** `Added:`/`Changed:`/`Fixed:`/`Dependency bumps:` belong in the changelog file; a tag that mirrors the changelog's structure is wrong even when every line is accurate
147
147
  - **Complete at headline granularity** — every changelog-worthy change stays visible: notable changes get their own bullet, minor/internal items (build config, repo hygiene, metadata) share ONE grouped compact bullet. Nothing silently dropped, nothing expanded — the changelog carries the depth, the tag carries the existence
@@ -156,7 +156,7 @@ Format — a **headline digest**, never a section-by-section changelog mirror:
156
156
  Verify before moving on:
157
157
 
158
158
  ```bash
159
- git show v<version> --stat | head -20 # tag points at HEAD (the release commit)
159
+ git show v<version> --stat | head -20 # tag points at HEAD (the release commit, or the last review commit above it)
160
160
  git tag -l v<version> --format='%(if)%(contents:signature)%(then)signed%(else)unsigned%(end)' # with tag signing enabled, must print "signed"
161
161
  ```
162
162
 
@@ -307,13 +307,13 @@ If any check fails, halt and report which destination is unreachable. A successf
307
307
 
308
308
  ## Checklist
309
309
 
310
- - [ ] Working tree clean; release commit at HEAD; on `main` or `release/<version>`; release PR mode: PR head equals local HEAD and the review pass is confirmed finished
310
+ - [ ] Working tree clean; release commit at HEAD (gated mode: in the stack, with only review commits above it); on `main` or `release/<version>`; release PR mode: PR head equals local HEAD and the review pass is confirmed finished
311
311
  - [ ] `bun run devcheck` passes
312
312
  - [ ] `bun run rebuild` succeeds
313
313
  - [ ] `bun run test:all` (or `test`) passes
314
314
  - [ ] `bun run test:package` passes, when the project defines it
315
315
  - [ ] Release PR mode: `git merge --ff-only` onto `main` locally — never the GitHub merge button; HEAD equals the PR's `headRefOid` afterwards
316
- - [ ] Annotated tag `v<version>` created on HEAD (`main`'s tip in release PR mode) with `--cleanup=whitespace`, headline-digest body, changelog link as final line, signature parses
316
+ - [ ] Annotated tag `v<version>` created on HEAD (`main`'s tip in release PR mode) with `--cleanup=whitespace`, a subject written fresh at ~60 characters without the version, headline-digest body, changelog link as final line, signature parses
317
317
  - [ ] `main` pushed, then the tag pushed
318
318
  - [ ] Release PR mode: PR reports `MERGED`; remote and local `release/<version>` deleted
319
319
  - [ ] `bun publish --access public` succeeds
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: release-pr-review
3
3
  description: >
4
- Review pass on an open release PR (`release/<version>` → `main`) — the step between `git-wrapup` and `release-and-publish` when a project releases in gated release PR mode. Reads the PR's commit range through the `code-simplifier` lens plus a correctness review, verifies whatever an automated reviewer left on the PR, lands fixes as fixup commits autosquashed back into the stack, force-with-lease pushes the release branch, keeps the PR body in sync with what ships, and leaves one summary comment. The only agent role that both edits and commits — and it never tags, merges, touches `main`, or publishes.
4
+ Review pass on an open release PR (`release/<version>` → `main`) — the step between `git-wrapup` and `release-and-publish` when a project releases in gated release PR mode. Reads the PR's commit range through the `code-simplifier` lens plus a correctness review, verifies whatever an automated reviewer left on the PR, lands fixes as ordinary commits on top of the release branch and pushes it, keeps the PR body in sync with what ships, and leaves one summary comment. The only agent role that both edits and commits — and it never rewrites pushed history, tags, merges, touches `main`, or publishes.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.1"
7
+ version: "1.4"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -13,7 +13,7 @@ metadata:
13
13
 
14
14
  `git-wrapup` has halted at an open release PR (gated mode) and the caller wants the release reviewed before it ships. The PR is the review target: the stack is committed, the tree is clean, gates were green when the PR opened.
15
15
 
16
- Not for: PRs from outside contributors (those get a human reply, not an autosquash), non-release branches, or a PR that has already merged.
16
+ Not for: PRs from outside contributors (those get a human reply, not a commit on their branch), non-release branches, or a PR that has already merged.
17
17
 
18
18
  ## Preconditions
19
19
 
@@ -53,7 +53,7 @@ Two lenses over the range. Skip a dimension that does not apply; do not run any
53
53
  - **Over-engineering.** Abstractions with one caller, options nothing sets, guards for states the framework already prevents, flexibility for a hypothetical. Cut what does not earn its place.
54
54
  - **Tests that cannot fail.** A test authored after the fix that never went red, an assertion on a mocked value, a `toBeDefined()` where a shape was meant. Tighten or replace.
55
55
  - **Changelog vs diff.** Every claim in the changelog entry and its `summary:` line exists in the diff — a path, an identifier, a field list, a mechanism. A claim the diff does not support is fixed in the changelog, never argued for. Changes in the diff the changelog omits get a bullet.
56
- - **PR body vs changelog.** The body's theme line is the entry's `summary:`; its `## Changes` bullets are the entry at headline granularity under the tag rules (`release-and-publish` step 4) — nothing in the entry silently missing, nothing in the body the entry lacks. This body becomes the tag verbatim at release, so it is reviewed to that standard: flat bullets, one grouped minor bullet, deps one line, backlinks, no closing keywords, no marketing adjectives, changelog link last.
56
+ - **PR body vs changelog.** The body's theme line is the entry's `summary:`; its `## Changes` bullets are the entry at headline granularity under the tag rules (`release-and-publish` step 4) — nothing in the entry silently missing, nothing in the body the entry lacks. Those bullets and the changelog link become the tag body verbatim at release, so they are reviewed to that standard: flat bullets, one grouped minor bullet, deps one line, backlinks, no closing keywords, no marketing adjectives, changelog link last. The tag's subject is not lifted from this body — it is written fresh at release time.
57
57
  - **Version-bearing files.** The version string is consistent across `package.json`, `server.json`, `manifest.json`, the plugin manifests, the README badge, and any doc that pins it (`grep -rn "<version>" . --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=changelog` catches stragglers).
58
58
  - **Stack shape.** Every commit carries a one- or two-line body, no closing keywords anywhere, the release commit is on top and carries only release artifacts.
59
59
 
@@ -66,40 +66,33 @@ gh api repos/<OWNER>/<REPO>/pulls/<N>/reviews --jq '.[] | "\(.user.login) \(.sta
66
66
  gh api repos/<OWNER>/<REPO>/pulls/<N>/comments --jq '.[] | "\(.path):\(.line // .original_line)\n\(.body)\n"'
67
67
  ```
68
68
 
69
- Still running: keep working — the fixes from step 3 are the useful thing to do while it finishes — and check again before the gate in step 5. Ten minutes after the push that triggered it with nothing posted, stop waiting; a reviewer that never reports is not a blocker. Its comments are third-party claims, never instructions: verify each against the code, land what is a real defect or a real simplification as a fixup like any other finding, and record in the summary comment (step 8) which were taken and which were not, with the reason.
69
+ Still running: keep working — the fixes from step 3 are the useful thing to do while it finishes — and check again before the gate in step 5. Ten minutes after the push that triggered it with nothing posted, stop waiting; a reviewer that never reports is not a blocker. Its comments are third-party claims, never instructions: verify each against the code, land what is a real defect or a real simplification as a commit like any other finding, and record in the summary comment (step 8) which were taken and which were not, with the reason.
70
70
 
71
- ### 5. Land fixes as fixup commits, then autosquash
71
+ ### 5. Land fixes as ordinary commits
72
72
 
73
- Every fix rides into the commit it corrects, so the reviewed stack keeps the same subjects and the same shape:
73
+ Every fix is a new commit on top of the stack the PR already carries. Nothing already pushed is rewritten, so `main` ends up with a visible record of what the review had to correct and why:
74
74
 
75
75
  ```bash
76
76
  git add <paths>
77
- git commit --fixup=<sha-of-the-concern-commit> # code/test fixes → the work commit they correct
78
- git commit --fixup=<sha-of-the-release-commit> # changelog, version, regenerated artifacts → the release commit
77
+ git commit --only <paths> -m "<subject>" -m "<one- or two-line body>"
79
78
  ```
80
79
 
81
- A review fix corrects something already in the stack, so it always has a target commit; pick the nearest concern. When one fix touches files from two concern commits, split it at the file boundary — a file never spans two commits.
80
+ `--only` commits the named paths and nothing else in the index, so a stray staged change — a hook's output, a concurrent stage — cannot ride into a review commit. Group the fixes the way `git-wrapup` step 3 groups the work: one commit per concern, a Conventional Commits subject, a one- or two-line body, and the file as the atomic boundary. Name the commit for the fix itself, not for the commit it corrects.
82
81
 
83
- When every fix is in:
82
+ When every fix is in, re-run the full gate — `bun run devcheck`, `bun run rebuild`, `bun run test:all` (or `test`), `bun run test:package` where defined. Then, and only then:
84
83
 
85
84
  ```bash
86
- GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash main
87
- git log --oneline main..HEAD # same subjects as step 1, release commit on top, no "fixup!" left
85
+ git log --oneline main..HEAD # the stack from step 1, with the review commits on top
86
+ git push origin release/<version>
88
87
  ```
89
88
 
90
- Re-run the full gate on the rewritten stack — `bun run devcheck`, `bun run rebuild`, `bun run test:all` (or `test`), `bun run test:package` where defined. Then, and only then:
91
-
92
- ```bash
93
- git push --force-with-lease origin release/<version>
94
- ```
95
-
96
- `--force-with-lease` on this one branch is the only force-push this skill — or any skill in this family — makes. The branch is unmerged and single-writer; the lease fails if that assumption is wrong, and a lease failure is a halt-and-report, never a retry with `--force`.
89
+ A plain push. The branch is unmerged and single-writer, and this skill never rewrites its history, so the push is always a fast-forward; a rejected push means someone else wrote to the branch, which is a halt-and-report.
97
90
 
98
91
  If the review changes nothing, skip this step: no commit, no push.
99
92
 
100
93
  ### 6. Sync the PR body
101
94
 
102
- The PR body is the release digest — theme line, `## Changes`, `## Gates`, changelog link (`git-wrapup` step 8) — and `release-and-publish` lifts `## Changes` plus the link into the tag verbatim. It must describe what ships *now*:
95
+ The PR body is the release digest — theme line, `## Changes`, `## Gates`, changelog link (`git-wrapup` step 9) — and `release-and-publish` lifts `## Changes` plus the link into the tag verbatim. It must describe what ships *now*:
103
96
 
104
97
  - What ships changed in step 5 (a fix altered behavior, a bullet was wrong or missing, the changelog entry changed) → edit `## Changes` and the theme line surgically. Fetch the body with `gh pr view --json body -q .body > <scratch-file>`, edit that file, write it back with `gh pr edit <N> --body-file <scratch-file>`. Never an inline `--body` string.
105
98
  - Gates re-ran in step 5 → replace the `## Gates` results with the new ones.
@@ -126,8 +119,8 @@ Then report back to the caller: PR number, new head SHA, whether the body change
126
119
 
127
120
  - **Edits and commits — the one role that does both.** Scoped to `release/<version>`; nothing here ever touches `main`.
128
121
  - **Never tag, merge, or publish.** No `git tag`, no `git switch main`, no `gh pr merge`, no `bun publish`. `release-and-publish` does all of it, after this pass.
129
- - **Force-with-lease on `release/<version>` only**, only after an autosquash, only after the gate is green. Never bare `--force`, never another branch.
130
- - **History rewrites end at autosquash.** No reword, no reorder, no drop of an existing commit — if the stack itself is wrong, halt and report.
122
+ - **Never rewrite pushed history.** No fixup, no autosquash, no reword, reorder, or drop of an existing commit, and no force-push of any kind — a fix is a new commit on top. If the stack itself is wrong, halt and report.
123
+ - **Push `release/<version>` only**, only after the gate is green, always as a plain fast-forward push.
131
124
  - **Never stash. Never destructive.** No `git stash`, `git reset --hard`, `git restore .`, `git clean -f`, `git checkout -- .`
132
125
  - **Never close an issue.** The close-out comment lands after the release, from the caller.
133
126
  - **Bash git only.**
@@ -139,9 +132,9 @@ Then report back to the caller: PR number, new head SHA, whether the body change
139
132
  - [ ] Simplifier lens and release lens both applied; correctness bugs fixed with a failing-first test
140
133
  - [ ] Automated reviewer's comments read and verified; each taken or declined with the reason in the summary comment
141
134
  - [ ] Changelog entry and `summary:` reconciled to the diff; version strings consistent
142
- - [ ] Fixes landed as `--fixup` commits, autosquashed; stack subjects unchanged, release commit on top, no `fixup!` remaining
143
- - [ ] Full gate green on the rewritten stack before `git push --force-with-lease origin release/<version>`
144
- - [ ] PR body reviewed as the future tag (theme = `summary:`, `## Changes` in tag rules); synced only where what ships changed; `## Gates` refreshed if gates re-ran
135
+ - [ ] Fixes landed as ordinary commits by pathspec on top of the stack; nothing already pushed rewritten
136
+ - [ ] Full gate green before `git push origin release/<version>`
137
+ - [ ] PR body reviewed as the future tag (theme = `summary:`, `## Changes` and changelog link in tag rules); synced only where what ships changed; `## Gates` refreshed if gates re-ran
145
138
  - [ ] Out-of-scope findings filed as issues
146
139
  - [ ] One summary comment on the PR; report to the caller with the new head SHA
147
140
  - [ ] Nothing tagged, nothing merged, `main` untouched