@jakobm/peacekeeper 0.0.0-stage → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +382 -2
- package/bin/peacekeeper.js +59 -0
- package/package.json +37 -4
- package/scripts/check-project-requirements.txt +326 -0
- package/scripts/check-project.js +235 -0
- package/scripts/check-test-receipt.js +48 -0
- package/scripts/checkpoint-codex-changes.js +236 -0
- package/scripts/checkpoint-codex-session-start.js +37 -0
- package/scripts/checkpoint-codex-stop.js +99 -0
- package/scripts/checkpoint-instructions.js +19 -0
- package/scripts/cleanup-ended-session.js +317 -0
- package/scripts/cleanup-merged-branches.js +382 -0
- package/scripts/install-hooks.js +155 -0
- package/scripts/log-script-invocation.js +80 -0
- package/scripts/project-config.js +99 -0
- package/scripts/queue-merged-worktree-cleanup.js +58 -0
- package/scripts/receipt-state.js +81 -0
- package/scripts/record-test-receipt.js +35 -0
- package/scripts/release-core.js +527 -0
- package/scripts/release-protection.js +67 -0
- package/scripts/release.js +18 -0
- package/scripts/remove-empty-directories.js +163 -0
- package/scripts/run-tests.js +56 -0
- package/scripts/verify-release-core.js +39 -0
- package/scripts/verify-release-protection.js +19 -0
- package/scripts/verify-release-version.js +14 -0
- package/scripts/verify-release.js +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jakob Mattsson
|
|
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
CHANGED
|
@@ -1,3 +1,383 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Peacekeeper
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Node.js scripts for Codex Git worktree hooks, merged branch cleanup, and cached test receipts. The package requires Node.js 20 or newer and Git. Each calling project supplies its own configuration at its Git root.
|
|
4
|
+
|
|
5
|
+
The public npm package is `@jakobm/peacekeeper`; its CLI command is `peacekeeper`.
|
|
6
|
+
Install it with `npm install --save-exact @jakobm/peacekeeper@<version>`, selecting
|
|
7
|
+
a published version. Replace the example dependency pin below with that version.
|
|
8
|
+
|
|
9
|
+
When moving an existing project to Peacekeeper, update its dependency and lockfile,
|
|
10
|
+
package scripts, and configuration filename together. Replace previous lifecycle
|
|
11
|
+
hook commands with the definitions below before running `peacekeeper install-hooks`.
|
|
12
|
+
The installer recognizes Peacekeeper commands; it does not translate commands
|
|
13
|
+
from a differently named package. Each existing worktree needs the new dependency
|
|
14
|
+
and configuration. Test receipts start fresh under the new package identity;
|
|
15
|
+
finish any pending release before switching release tools.
|
|
16
|
+
|
|
17
|
+
## Recommended caller setup
|
|
18
|
+
|
|
19
|
+
Add the three files below to the calling project. Replace the Git branch settings and test commands with that project's values. Install the package from npm; no GitHub SSH access is needed for the dependency.
|
|
20
|
+
|
|
21
|
+
### 1. `package.json`
|
|
22
|
+
|
|
23
|
+
Include these entries in the caller's `package.json`, alongside its other scripts and dependencies. Pin the npm dependency to an exact version and change it deliberately when upgrading. The `lint` script and ESLint dependency are needed only for the `eslint .` test command in this example.
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"private": true,
|
|
28
|
+
"scripts": {
|
|
29
|
+
"lint": "eslint .",
|
|
30
|
+
"test": "peacekeeper run-tests",
|
|
31
|
+
"cleanup:branches": "peacekeeper cleanup-merged-branches",
|
|
32
|
+
"cleanup:empty-directories": "peacekeeper remove-empty-directories"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"eslint": "^9.39.5"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@jakobm/peacekeeper": "0.12.1"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Run `npm install` from the project root and commit the resulting `package-lock.json`. npm adds `node_modules/.bin` to the `PATH` of package scripts, so package scripts can call `peacekeeper` by name.
|
|
44
|
+
|
|
45
|
+
### 2. `peacekeeper.config.json`
|
|
46
|
+
|
|
47
|
+
Place this required file at the caller's Git root. All fields shown here are required and unknown fields are rejected. `git.checkpointPushRemotes`, `git.releaseMergeMethod`, `git.releaseRequiredChecks`, and `git.releaseTestWorkflow` are optional fields described below; release commands require `git.releaseRequiredChecks`. Optional `projectChecks` settings are described under local project checks. There are no built-in test-command defaults: each caller must define `tests.commands` explicitly for its own toolchain.
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"git": {
|
|
52
|
+
"remote": "origin",
|
|
53
|
+
"defaultBranch": "main",
|
|
54
|
+
"branchPrefix": "codex/"
|
|
55
|
+
},
|
|
56
|
+
"tests": {
|
|
57
|
+
"commands": ["eslint .", "node --test"],
|
|
58
|
+
"receiptMaxAgeMinutes": 60,
|
|
59
|
+
"npmTestUsesRunner": true
|
|
60
|
+
},
|
|
61
|
+
"loggingDirectory": "tmp/logs"
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`git.remote` and `git.defaultBranch` identify the branch used for checkpointing and cleanup. `git.branchPrefix` names new task branches. `tests.commands` are complete shell commands, run in order from the caller's Git root; install any executables they use. The test runner stops on the first failure and records a receipt only after every command succeeds. `receiptMaxAgeMinutes` controls how long a receipt can be reused. Set `npmTestUsesRunner` to `true` only when the caller's `npm test` script invokes `peacekeeper run-tests`; this makes the rerun hint `npm test -- rerun`. Otherwise the hint uses the explicit package path. `loggingDirectory` stores hook invocation logs relative to the primary Git worktree's root.
|
|
66
|
+
|
|
67
|
+
To publish checkpoint branches automatically, add `checkpointPushRemotes` to `git`:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"remote": "origin",
|
|
72
|
+
"defaultBranch": "main",
|
|
73
|
+
"branchPrefix": "codex/",
|
|
74
|
+
"checkpointPushRemotes": ["origin", "backup"]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Each name must identify a Git remote configured in the calling repository. With this
|
|
79
|
+
setting, checkpoints push to exactly those remotes, creating the task branch if
|
|
80
|
+
needed. An empty list disables checkpoint pushes. When the field is omitted,
|
|
81
|
+
checkpoints keep the previous behavior: push only when the branch already exists
|
|
82
|
+
on `git.remote`. Pushes never use force. Only a push to `git.remote` sets the
|
|
83
|
+
branch's upstream. If a push fails, earlier pushes are kept and the checkpoint
|
|
84
|
+
records a failure; fix the cause and rerun the checkpoint command to retry without
|
|
85
|
+
creating another commit when there are no new changes.
|
|
86
|
+
|
|
87
|
+
Receipts are stored outside the repository under `peacekeeper-test-receipts` in Node.js's `os.tmpdir()`. Each Git root has a separate receipt file. Receipt validation fingerprints changed files and the process environment. In CI, the runner always runs the configured commands instead of reusing a receipt.
|
|
88
|
+
|
|
89
|
+
Starting a new test run invalidates any previous receipt, including when a forced
|
|
90
|
+
rerun fails. The runner compares repository contents and the test environment
|
|
91
|
+
before and after the commands. If either changes during the run, it exits with
|
|
92
|
+
an error and records no receipt; rerun the tests on the resulting contents.
|
|
93
|
+
Test-generated files should be ignored or written outside the repository.
|
|
94
|
+
|
|
95
|
+
### 3. `.codex/hooks.json`
|
|
96
|
+
|
|
97
|
+
Install or update the project hooks from the caller's Git worktree:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
npm exec --no -- peacekeeper install-hooks --dry-run
|
|
101
|
+
npm exec --no -- peacekeeper install-hooks
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The command creates or updates `.codex/hooks.json` at the Git root. It preserves
|
|
105
|
+
other handlers, their order, group settings, and other configuration fields.
|
|
106
|
+
Known Peacekeeper invocations are replaced with current definitions and
|
|
107
|
+
duplicates are removed. Our hooks are installed in their own groups, without
|
|
108
|
+
inheriting another group's matcher. Repeating the command makes no further change.
|
|
109
|
+
|
|
110
|
+
Supported migrations include standalone `peacekeeper`, `npm exec --no --`,
|
|
111
|
+
`npx --no-install`, documented `node` bin paths, and direct `node scripts/` hook
|
|
112
|
+
invocations. Unknown wrappers, composed shell commands, and additional handler
|
|
113
|
+
options are not replaced automatically. If an entry appears related to Peacekeeper
|
|
114
|
+
but cannot be safely migrated, the command exits with an error identifying
|
|
115
|
+
it and leaves the entire file unchanged. Review that entry, preserve any custom
|
|
116
|
+
logic as a separate hook, then rerun the installer. Wrappers whose names do not
|
|
117
|
+
identify Peacekeeper cannot be detected; review those yourself before installing.
|
|
118
|
+
|
|
119
|
+
The installer writes atomically and rejects malformed files. It changes only
|
|
120
|
+
`.codex/hooks.json`, not `AGENTS.md`, project configuration, or hooks in other
|
|
121
|
+
configuration layers. Configure Git remotes, tests, and dependencies separately,
|
|
122
|
+
and review and trust the installed hooks with `/hooks` in Codex.
|
|
123
|
+
|
|
124
|
+
The installed file contains these definitions (other existing hooks are retained):
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"description": "Repository lifecycle hooks.",
|
|
129
|
+
"hooks": {
|
|
130
|
+
"SessionStart": [
|
|
131
|
+
{
|
|
132
|
+
"hooks": [
|
|
133
|
+
{
|
|
134
|
+
"type": "command",
|
|
135
|
+
"command": "node \"$(git rev-parse --show-toplevel)/node_modules/@jakobm/peacekeeper/bin/peacekeeper.js\" checkpoint-codex-session-start",
|
|
136
|
+
"timeout": 30,
|
|
137
|
+
"statusMessage": "Loading Codex checkpoint instructions"
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
"Stop": [
|
|
143
|
+
{
|
|
144
|
+
"hooks": [
|
|
145
|
+
{
|
|
146
|
+
"type": "command",
|
|
147
|
+
"command": "node \"$(git rev-parse --show-toplevel)/node_modules/@jakobm/peacekeeper/bin/peacekeeper.js\" checkpoint-codex-stop",
|
|
148
|
+
"timeout": 30,
|
|
149
|
+
"statusMessage": "Checking for an uncommitted Codex checkpoint"
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
"SessionEnd": [
|
|
155
|
+
{
|
|
156
|
+
"hooks": [
|
|
157
|
+
{
|
|
158
|
+
"type": "command",
|
|
159
|
+
"command": "node \"$(git rev-parse --show-toplevel)/node_modules/@jakobm/peacekeeper/bin/peacekeeper.js\" cleanup-ended-session",
|
|
160
|
+
"timeout": 3,
|
|
161
|
+
"statusMessage": "Scheduling merged branch cleanup"
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The commands resolve the package from the current Git worktree. Install dependencies in each worktree where the hooks will run; the commands need that worktree's `node_modules/@jakobm/peacekeeper`. The checkpoint hooks apply only to linked worktrees. `SessionStart` supplies the checkpoint instructions before work begins, including on resume, fork, and after compaction; no matcher is needed. `Stop` uses the same package-owned instructions as a fallback. You do not need to copy checkpoint rules into each project's `AGENTS.md`. Explicit user instructions not to commit or push take precedence: the agent must skip the checkpoint command when they conflict, even if `Stop` requests it. The hook checks Git state and cannot determine user intent itself. When uncommitted changes remain, the `Stop` hook asks the agent to review the diff and run `checkpoint-codex-changes` with a descriptive English commit message. That command stages all changes and commits them on a task branch; it refuses the configured default branch. With `git.checkpointPushRemotes`, it pushes without force to each listed remote, including new branches. Without that setting, it pushes only if the branch already exists on `git.remote`, otherwise the branch stays local.
|
|
171
|
+
|
|
172
|
+
`SessionEnd` starts a detached worker to clean up the session worktree and branch only if the captured branch tip is still unchanged and fully merged into the remote default branch. Dirty or locked worktrees are kept. Worker output is appended to `cleanup-ended-session.log` in the shared Git directory. Cleanup does not require the primary worktree to have the default branch checked out. A local default branch that can fast-forward is updated before cleanup: a branch not checked out in any worktree is moved atomically, while a checked-out branch is merged in its clean worktree so its files and index stay synchronized. A dirty or diverged default branch is left unchanged. Codex loads project hooks only after the project hook source is trusted; review them with `/hooks`. The three-second `SessionEnd` timeout is the maximum documented for that event. See the [Codex hooks reference](https://learn.chatgpt.com/docs/hooks) for hook loading, trust, and timeout behavior.
|
|
173
|
+
|
|
174
|
+
### Adopting shared checkpoint instructions
|
|
175
|
+
|
|
176
|
+
For a project that already uses the Stop hook, upgrade to a package release that
|
|
177
|
+
includes `checkpoint-codex-session-start` and `install-hooks`, then run the installer above.
|
|
178
|
+
The installer also updates `Stop` and `SessionEnd`. Review and trust the updated hooks with `/hooks`,
|
|
179
|
+
then start a new session or resume one to load the instructions. The pinned package version must include these commands.
|
|
180
|
+
|
|
181
|
+
Remove the duplicated checkpoint section from the project's `AGENTS.md` once the
|
|
182
|
+
new hook is enabled. Keep any project-specific rules there. Installing the npm
|
|
183
|
+
dependency alone does not activate hooks: each caller must configure and trust
|
|
184
|
+
them. The shared policy then follows the installed package version.
|
|
185
|
+
|
|
186
|
+
## Check the setup
|
|
187
|
+
|
|
188
|
+
From the caller's Git worktree, run `npm test` for the configured commands, `npm test -- rerun` to bypass a reusable receipt, `npm run cleanup:branches` to preview merged branch cleanup, and `npm run cleanup:empty-directories` to preview removal of empty directories and directories containing only ignored files. The latter command protects the root `.git`, `node_modules`, and `tmp` directories; other selected directories are removed recursively, including their ignored contents. Add `-- --execute` to either cleanup package script to apply it. Run `npm exec --no -- peacekeeper --help` for the complete command list. To create a manual checkpoint, use `npm exec --no -- peacekeeper checkpoint-codex-changes --subject "<subject>" --body "<body paragraph>"`.
|
|
189
|
+
|
|
190
|
+
Directory cleanup also preserves nested Git repositories and linked worktrees,
|
|
191
|
+
including their parent directories when those parents are ignored. It does not
|
|
192
|
+
clean inside those repositories. Branch cleanup rechecks local branch tips and
|
|
193
|
+
worktree identity, locks, and changes before removal; a changed plan is skipped
|
|
194
|
+
before deleting the affected worktree or branches. Remote branch deletion still
|
|
195
|
+
uses an exact-commit lease.
|
|
196
|
+
|
|
197
|
+
This repository uses the same hooks and configuration, with commands pointing to
|
|
198
|
+
its own `bin/peacekeeper.js` instead of an installed copy. Its `git.checkpointPushRemotes` is set to `["origin"]` to publish checkpoint branches automatically. There is no dependency
|
|
199
|
+
on a published version of itself. `npm exec --no -- peacekeeper` also resolves
|
|
200
|
+
the root package's own bin, including the checkpoint command requested by the
|
|
201
|
+
Stop hook. Install dependencies with `npm ci` to run the full test suite.
|
|
202
|
+
|
|
203
|
+
Run `npm test` in this repository to verify the shared package itself, or
|
|
204
|
+
`npm test -- rerun` to force a fresh run. Its configured test command runs Node's
|
|
205
|
+
test runner directly, avoiding recursion through `npm test`. The cleanup package
|
|
206
|
+
scripts work the same way as in calling projects.
|
|
207
|
+
|
|
208
|
+
When first adopting these hooks, make sure the primary checkout also contains
|
|
209
|
+
`peacekeeper.config.json` before trusting them with `/hooks`: invocation logs
|
|
210
|
+
are stored there and session cleanup reads that checkout's configuration.
|
|
211
|
+
The hook definitions can then be reviewed and trusted in Codex; committing them
|
|
212
|
+
does not grant trust automatically.
|
|
213
|
+
|
|
214
|
+
## Check project conventions locally
|
|
215
|
+
|
|
216
|
+
Run `peacekeeper check-project` to check the local project configuration.
|
|
217
|
+
Run `peacekeeper check-project --explain` for the complete expected
|
|
218
|
+
architecture, requirements, configuration examples, and verification limits.
|
|
219
|
+
The command prints the bundled [formal requirements](scripts/check-project-requirements.txt),
|
|
220
|
+
which can also be read without a repository or project configuration.
|
|
221
|
+
|
|
222
|
+
## Publish an npm release
|
|
223
|
+
|
|
224
|
+
Install [Volta](https://volta.sh/) and ensure its shims are first in `PATH`
|
|
225
|
+
(`volta setup` configures supported shell profiles; restart your shell afterward).
|
|
226
|
+
Then run `npm ci` at the repository root. Volta automatically selects the exact
|
|
227
|
+
Node.js and npm versions in `package.json` for ordinary `node`, `npm`, and `npx`
|
|
228
|
+
commands, including in non-interactive agent shells with Volta on `PATH`.
|
|
229
|
+
GitHub Actions uses `volta-cli/action` to select the same project versions.
|
|
230
|
+
Check `volta which node`, `node --version`, and `npm --version` to verify your
|
|
231
|
+
setup. Use `volta pin node@<version> npm@<version>` to update the project versions;
|
|
232
|
+
`engines.node` separately describes supported consumer versions.
|
|
233
|
+
Review `npm pack --dry-run --json` before
|
|
234
|
+
publishing; npm includes only `bin/`, `scripts/`, the README, and package metadata.
|
|
235
|
+
|
|
236
|
+
Configure npm trusted publishing for `@jakobm/peacekeeper` with repository owner
|
|
237
|
+
`jakobmattsson`, repository `peacekeeper`, and workflow filename `package.yml`,
|
|
238
|
+
with direct publishing enabled. Confirm npm publish access to the `@jakobm`
|
|
239
|
+
scope separately from GitHub repository access. The workflow verifies
|
|
240
|
+
the release and publishes via OIDC. It needs no npm access token.
|
|
241
|
+
|
|
242
|
+
`test.yml` runs `test` and `Test package` on pull requests. `package.yml` runs
|
|
243
|
+
on `v*` tags and only verifies and publishes. Merging to `main` does not start
|
|
244
|
+
another run.
|
|
245
|
+
|
|
246
|
+
Release verification uses GitHub's enforced branch policy instead of retaining
|
|
247
|
+
artifacts of tested contents. Configure `git.releaseRequiredChecks` in the root
|
|
248
|
+
`peacekeeper.config.json` with the exact CI job names required for a release
|
|
249
|
+
(for this repository: `["test", "Test package"]`). These checks must be mandatory
|
|
250
|
+
with **Require branches to be up to date before merging** enabled. Classic branch
|
|
251
|
+
protection must enforce checks for administrators too. Alternatively, active
|
|
252
|
+
rulesets must require strict checks and have no bypass actors. Rules inherited
|
|
253
|
+
from the organization are included. Merge queues are not currently an alternative
|
|
254
|
+
to this strict policy.
|
|
255
|
+
|
|
256
|
+
The PR test job runs `peacekeeper verify-release-protection` before its tests.
|
|
257
|
+
The command checks `GITHUB_BASE_REF`, or `git.defaultBranch` outside PR CI.
|
|
258
|
+
`peacekeeper release` checks the default branch before preparing a release
|
|
259
|
+
and again immediately before each merge attempt. Before publishing,
|
|
260
|
+
`peacekeeper verify-release` checks the policy, matching package/tag/lockfile
|
|
261
|
+
versions, a same-repository merged `release/v<version>` PR, and successful required
|
|
262
|
+
jobs in its latest PR workflow run. Manual tags without that PR are rejected.
|
|
263
|
+
`verify-release-version` remains available for local version checks alone.
|
|
264
|
+
|
|
265
|
+
API failures, missing permissions, and unverifiable rules stop the commands.
|
|
266
|
+
The workflow expects a `RELEASE_POLICY_TOKEN` Actions secret, exposed to the
|
|
267
|
+
commands as `GH_TOKEN`. Use a fine-grained PAT or GitHub App token with access to
|
|
268
|
+
this repository and Administration read, Contents read, Pull requests read and
|
|
269
|
+
Actions read permissions. GitHub only exposes ruleset bypass actors to callers
|
|
270
|
+
with write access to the ruleset; ruleset users must additionally grant that
|
|
271
|
+
access (Administration write for repository rulesets, or the appropriate access
|
|
272
|
+
for inherited organization rulesets). Missing bypass information also fails.
|
|
273
|
+
The normal workflow `GITHUB_TOKEN` cannot be granted Administration permissions
|
|
274
|
+
through workflow YAML. After correcting access or rules, rerun the failed job.
|
|
275
|
+
Fork PRs do not receive this secret and fail the policy check; do not expose it
|
|
276
|
+
by executing fork code with `pull_request_target`.
|
|
277
|
+
|
|
278
|
+
To set this up for this repository:
|
|
279
|
+
|
|
280
|
+
1. Open the [token creation form with the required read permissions prefilled](https://github.com/settings/personal-access-tokens/new?name=peacekeeper-release-policy&target_name=jakobmattsson&expires_in=90&administration=read&contents=read&pull_requests=read&actions=read).
|
|
281
|
+
For repository rulesets, use the [form with Administration write instead](https://github.com/settings/personal-access-tokens/new?name=peacekeeper-release-policy&target_name=jakobmattsson&expires_in=90&administration=write&contents=read&pull_requests=read&actions=read)
|
|
282
|
+
so GitHub exposes bypass actors. For another repository, change the resource
|
|
283
|
+
owner. Select **Only select repositories**, choose `peacekeeper`, review
|
|
284
|
+
the permissions and expiration, and generate the token. If the owner requires
|
|
285
|
+
approval, wait until the token is approved.
|
|
286
|
+
2. Save the token as an Actions secret with the following command. `gh` prompts
|
|
287
|
+
for the value interactively; paste the token at the prompt, keeping it out of
|
|
288
|
+
the command and shell history. The `gh` account running this command needs
|
|
289
|
+
permission to manage repository Actions secrets. The policy token itself
|
|
290
|
+
does not need Secrets permission.
|
|
291
|
+
|
|
292
|
+
```sh
|
|
293
|
+
gh secret set RELEASE_POLICY_TOKEN --repo jakobmattsson/peacekeeper
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
3. Confirm the secret name exists, then find and rerun the failed test workflow:
|
|
297
|
+
|
|
298
|
+
```sh
|
|
299
|
+
gh secret list --repo jakobmattsson/peacekeeper
|
|
300
|
+
gh run list --repo jakobmattsson/peacekeeper --workflow test.yml --limit 5
|
|
301
|
+
gh run rerun RUN_ID --failed --repo jakobmattsson/peacekeeper
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Replace `RUN_ID` with the run ID from the listing. To retry publication, list
|
|
305
|
+
runs with `--workflow package.yml` instead. When the token expires, create a
|
|
306
|
+
replacement and run `gh secret set` again to update the same secret.
|
|
307
|
+
|
|
308
|
+
GitHub requires fine-grained PAT creation in the browser; `gh` can store the
|
|
309
|
+
resulting token as a secret but cannot create that PAT. See [GitHub's token setup
|
|
310
|
+
documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#pre-filling-fine-grained-personal-access-token-details-using-url-parameters)
|
|
311
|
+
and the [`gh secret set` reference](https://cli.github.com/manual/gh_secret_set).
|
|
312
|
+
|
|
313
|
+
This policy trusts GitHub to enforce the checked settings when merging. It does
|
|
314
|
+
not retain historical proof of the tested file tree if administrators change
|
|
315
|
+
settings between CI and merge. Keep the `package.yml` filename unchanged: npm
|
|
316
|
+
trusted publishing is configured for that workflow file.
|
|
317
|
+
|
|
318
|
+
Release merge selection reads the calling repository's root
|
|
319
|
+
`peacekeeper.config.json`, not the installed package's configuration:
|
|
320
|
+
|
|
321
|
+
- If GitHub allows only one merge method, that method is used regardless of the preference.
|
|
322
|
+
- If multiple methods are allowed, optional `git.releaseMergeMethod` selects
|
|
323
|
+
`"merge"` (merge commit), `"rebase"`, or `"squash"`. A configured method that
|
|
324
|
+
GitHub disallows causes the release to stop before creating a branch or PR.
|
|
325
|
+
- Without a preference, merge commit is chosen first, then rebase.
|
|
326
|
+
|
|
327
|
+
For example, add `"releaseMergeMethod": "rebase"` inside the existing `git` object.
|
|
328
|
+
A caller without a merge preference uses the same defaults.
|
|
329
|
+
The chosen method is saved with release state and reused on retries; unfinished
|
|
330
|
+
releases created by older versions retain their original squash behavior.
|
|
331
|
+
|
|
332
|
+
From a clean repository root, run `npm run release -- patch`, `minor`, or `major`.
|
|
333
|
+
The command fetches the default branch and uses a temporary detached worktree to
|
|
334
|
+
increment the package and lockfile versions. Your current branch and local default
|
|
335
|
+
branch stay unchanged, including when the default branch is checked out elsewhere.
|
|
336
|
+
It pushes a `release/v<version>` branch and opens a release PR. The merge method
|
|
337
|
+
follows the calling repository's configuration and GitHub settings. The command
|
|
338
|
+
waits up to 20 minutes for GitHub to accept the merge,
|
|
339
|
+
respecting required checks, reviews, resolved conversations, and branch freshness.
|
|
340
|
+
It never uses an administrator bypass or pushes directly to the default branch.
|
|
341
|
+
|
|
342
|
+
After merge, the command verifies the package files at the PR's actual merge
|
|
343
|
+
commit, tags that commit with `v<version>`, and pushes the tag. The existing
|
|
344
|
+
workflow publishes through npm trusted publishing. The command then waits for
|
|
345
|
+
the publish job and for the version to appear on npm. Git and an authenticated
|
|
346
|
+
GitHub CLI (`gh`) with Contents read/write, Pull requests read/write, Actions
|
|
347
|
+
read and the policy-reading access described above are required. Registry checks
|
|
348
|
+
use a temporary npm cache and request current metadata.
|
|
349
|
+
|
|
350
|
+
Retry the same command and release type after an interruption: release state is
|
|
351
|
+
stored in `peacekeeper-release.json` in the shared Git directory, so retries
|
|
352
|
+
from another worktree of the same checkout continue the same PR and version.
|
|
353
|
+
Already-merged PRs and already-pushed matching tags are reused. A local lock
|
|
354
|
+
prevents concurrent commands in that checkout; an open release PR from a different
|
|
355
|
+
checkout blocks a new release. Do not delete the state or manually create a new
|
|
356
|
+
version commit or tag to retry. If publishing failed, fix the cause and rerun the
|
|
357
|
+
tag workflow on GitHub before retrying the command.
|
|
358
|
+
|
|
359
|
+
If merge requirements remain unmet, the command stops without tagging. Resolve
|
|
360
|
+
those requirements and retry. Conflicts, a changed PR head, or a changed base
|
|
361
|
+
require inspection before continuing; the command refuses to silently publish
|
|
362
|
+
an altered release. Temporary worktrees are removed when the command exits
|
|
363
|
+
normally or with a handled error; a killed process may leave a temporary worktree
|
|
364
|
+
that needs manual cleanup.
|
|
365
|
+
|
|
366
|
+
Other npm projects can use this command through a package script such as
|
|
367
|
+
`"release": "peacekeeper release"`. The project must use a root package
|
|
368
|
+
with a stable version, a `peacekeeper.config.json` with
|
|
369
|
+
`git.releaseRequiredChecks`, and the supported tag-triggered GitHub Actions
|
|
370
|
+
publish job. Add `peacekeeper verify-release-protection` to a required PR
|
|
371
|
+
test job and `peacekeeper verify-release` before publishing, with `GH_TOKEN`
|
|
372
|
+
and `RELEASE_TAG` set for release verification. Both commands are shipped with
|
|
373
|
+
the package; no verifier script or artifact upload needs to be copied.
|
|
374
|
+
Set optional `git.releaseTestWorkflow` to the PR workflow filename (for this
|
|
375
|
+
repository: `"test.yml"`). If omitted, verification uses the publish workflow
|
|
376
|
+
filename for compatibility with combined workflows. All configured check names
|
|
377
|
+
must be jobs in the selected PR workflow's run.
|
|
378
|
+
The `publish` job must contain a step whose command is exactly `npm publish`.
|
|
379
|
+
Its `if` can be omitted when the workflow triggers only on pushes of
|
|
380
|
+
`v`-prefixed tags (for example, `on.push.tags: ['v*']`), with no branch triggers.
|
|
381
|
+
For combined workflows or other triggers, the job must use the exact condition
|
|
382
|
+
`${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}`.
|
|
383
|
+
Explicit alternative job conditions are not supported. Workspaces are not supported.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('node:child_process');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const COMMANDS = new Map([
|
|
7
|
+
['check-project', 'Validate local toolchain, hooks, CI and publication conventions.'],
|
|
8
|
+
['install-hooks', 'Install or update project Codex hooks while preserving other hooks.'],
|
|
9
|
+
['verify-release', 'Verify release versions, required CI and strict branch protection.'],
|
|
10
|
+
['verify-release-protection', 'Require strict mandatory release checks without bypass.'],
|
|
11
|
+
['verify-release-version', 'Check a release tag against package and lockfile versions.'],
|
|
12
|
+
['release', 'Publish a version through a GitHub Actions npm workflow.'],
|
|
13
|
+
['run-tests', 'Run configured tests, reusing a valid test receipt.'],
|
|
14
|
+
['check-test-receipt', 'Check whether a valid test receipt exists.'],
|
|
15
|
+
['record-test-receipt', 'Record a test receipt without running tests.'],
|
|
16
|
+
['checkpoint-codex-changes', 'Commit a linked worktree and push according to project configuration.'],
|
|
17
|
+
['checkpoint-codex-session-start', 'Load checkpoint instructions for a linked Codex worktree.'],
|
|
18
|
+
['checkpoint-codex-stop', 'Handle the Codex Stop hook and request a checkpoint if needed.'],
|
|
19
|
+
['cleanup-ended-session', 'Queue cleanup for a merged Codex session worktree.'],
|
|
20
|
+
['queue-merged-worktree-cleanup', 'Queue cleanup for the current merged worktree.'],
|
|
21
|
+
['cleanup-merged-branches', 'Preview or delete merged worktrees and branches.'],
|
|
22
|
+
['remove-empty-directories', 'Preview or remove directories containing only ignored files.'],
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
function helpText() {
|
|
26
|
+
const width = Math.max(...[...COMMANDS.keys()].map((command) => command.length));
|
|
27
|
+
const commands = [...COMMANDS]
|
|
28
|
+
.map(([command, description]) => ` ${command.padEnd(width)} ${description}`)
|
|
29
|
+
.join('\n');
|
|
30
|
+
return `Usage: peacekeeper <command> [arguments]\n\nCommands:\n${commands}\n\nOptions:\n -h, --help Show this help.\n`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function main() {
|
|
34
|
+
const [command, ...args] = process.argv.slice(2);
|
|
35
|
+
if (command === '--help' || command === '-h') {
|
|
36
|
+
process.stdout.write(helpText());
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!COMMANDS.has(command)) {
|
|
40
|
+
const detail = command ? `Unknown command: ${command}` : 'A command is required.';
|
|
41
|
+
process.stderr.write(`${detail}\n\n${helpText()}`);
|
|
42
|
+
process.exitCode = 2;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const script = path.resolve(__dirname, '..', 'scripts', `${command}.js`);
|
|
46
|
+
const result = spawnSync(process.execPath, [script, ...args], {
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
stdio: 'inherit',
|
|
49
|
+
});
|
|
50
|
+
if (result.error) throw result.error;
|
|
51
|
+
process.exitCode = result.status ?? 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
main();
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`Error: ${error.message}`);
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jakobm/peacekeeper",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.12.1",
|
|
4
|
+
"description": "Shared repository test, Git worktree, and Codex hook commands",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/jakobmattsson/peacekeeper.git"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public",
|
|
12
|
+
"registry": "https://registry.npmjs.org/"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"peacekeeper": "bin/peacekeeper.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin/",
|
|
19
|
+
"scripts/"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"pretest": "node bin/peacekeeper.js check-project",
|
|
23
|
+
"test": "node bin/peacekeeper.js run-tests",
|
|
24
|
+
"release": "node bin/peacekeeper.js release",
|
|
25
|
+
"cleanup:branches": "node bin/peacekeeper.js cleanup-merged-branches",
|
|
26
|
+
"cleanup:empty-directories": "node bin/peacekeeper.js remove-empty-directories"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"semver": "^7.8.5",
|
|
33
|
+
"yaml": "^2.9.1"
|
|
34
|
+
},
|
|
35
|
+
"volta": {
|
|
36
|
+
"node": "22.14.0",
|
|
37
|
+
"npm": "11.21.0"
|
|
38
|
+
}
|
|
39
|
+
}
|