@a2f0/agent-tool 0.1.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.
Files changed (45) hide show
  1. package/README.md +288 -0
  2. package/package.json +36 -0
  3. package/skills/cross-agent-review/SKILL.md +46 -0
  4. package/skills/open-pr/SKILL.md +31 -0
  5. package/skills/reset/SKILL.md +25 -0
  6. package/skills/ship-pr/SKILL.md +48 -0
  7. package/skills/squash-merge/SKILL.md +39 -0
  8. package/src/cli.ts +121 -0
  9. package/src/config.ts +101 -0
  10. package/src/doctor.ts +23 -0
  11. package/src/git/currentBranchPr.ts +114 -0
  12. package/src/git/environment.ts +10 -0
  13. package/src/git/prContext.ts +497 -0
  14. package/src/harnesses.ts +53 -0
  15. package/src/index.ts +9 -0
  16. package/src/pr/assertBranchPushed.ts +21 -0
  17. package/src/pr/assertNoClaudeBranding.ts +52 -0
  18. package/src/pr/baseFreshness.ts +67 -0
  19. package/src/pr/mergeChecks.ts +90 -0
  20. package/src/pr/openPr.ts +80 -0
  21. package/src/pr/prNumberSuffix.ts +43 -0
  22. package/src/pr/squashMerge.ts +229 -0
  23. package/src/pr/subjectLine.ts +21 -0
  24. package/src/pr/validateCommitSubject.ts +12 -0
  25. package/src/review/destinationPaths.ts +154 -0
  26. package/src/review/executable.ts +34 -0
  27. package/src/review/pinnedReviewInput.ts +91 -0
  28. package/src/review/rawGitTree.ts +301 -0
  29. package/src/review/reviewEffort.ts +54 -0
  30. package/src/review/reviewOutput.ts +62 -0
  31. package/src/review/reviewPrompt.ts +104 -0
  32. package/src/review/reviewSnapshot.ts +67 -0
  33. package/src/review/runReview.ts +62 -0
  34. package/src/review/solicitClaudeCodeReview.ts +140 -0
  35. package/src/review/solicitCodexReview.ts +205 -0
  36. package/src/review/solicitOpencodeReview.ts +233 -0
  37. package/src/runAgentToolAction.ts +100 -0
  38. package/src/skills/bundled.ts +8 -0
  39. package/src/skills/install.ts +92 -0
  40. package/src/version/bumpVersions.ts +196 -0
  41. package/src/version/lockfileVersions.ts +43 -0
  42. package/src/version/packageVersion.ts +112 -0
  43. package/src/version/prepareVersions.ts +135 -0
  44. package/src/version/resolveVersionConflicts.ts +126 -0
  45. package/src/version/versionPreparationGit.ts +127 -0
package/README.md ADDED
@@ -0,0 +1,288 @@
1
+ # agent-tool
2
+
3
+ A standalone CLI for independent agent reviews, GitHub PR operations, package
4
+ version helpers, and portable skills for Claude Code, Codex, and OpenCode.
5
+ Extracted from the agent tooling in the projects under `~/github`.
6
+
7
+ The first implementation retains TypeScript and the existing regression suite.
8
+ Bun compiles the code and skill text into one executable, so installed users do
9
+ not need Node, Bun, TypeScript, or repository-local npm dependencies. The current
10
+ macOS ARM64 executable is approximately 58 MiB because it includes its runtime.
11
+ Rust remains a reasonable later implementation choice; it is not required for
12
+ Brew or Debian packaging. See [the design](docs/design.md) and
13
+ [extraction record](docs/extraction.md).
14
+
15
+ ## Try it
16
+
17
+ Development and CI use Bun 1.4.2. The source CLI runs on Bun 1.3.11 or newer,
18
+ but `versions prepare` needs Bun 1.4 or newer on PATH to refresh workspace
19
+ versions in `bun.lock`. Runtime requires Git; GitHub commands also require
20
+ authenticated `gh`. Review requires an authenticated CLI for the chosen agent.
21
+
22
+ ```sh
23
+ bun install --frozen-lockfile
24
+ bun run build
25
+ ./dist/agent-tool --help
26
+ ./dist/agent-tool doctor
27
+
28
+ # Point the installed tool at an existing project:
29
+ ./dist/agent-tool --repo ../some-project review codex
30
+ # Review against an explicit local base without GitHub:
31
+ ./dist/agent-tool --repo ../some-project review opencode high --base main
32
+
33
+ # Preview shared skills, then install them when ready:
34
+ ./dist/agent-tool --repo ../some-project skills install
35
+ ./dist/agent-tool --repo ../some-project skills install --apply
36
+ ```
37
+
38
+ The same binary works when copied outside this repository. No sibling project
39
+ has to retain `packages/agent-tool` once its callers are migrated.
40
+
41
+ ### Use as a package dependency
42
+
43
+ Projects can install the source CLI from npm as `@a2f0/agent-tool`:
44
+
45
+ ```sh
46
+ bun add --dev --exact @a2f0/agent-tool
47
+ # or
48
+ npm install --save-dev --save-exact @a2f0/agent-tool
49
+ ```
50
+
51
+ Run `bun run agent-tool --help` after adding a script such as
52
+ `"agent-tool": "agent-tool"`. The package exposes
53
+ `node_modules/.bin/agent-tool`; invoke it directly for commands containing an
54
+ empty positional argument, such as
55
+ `node_modules/.bin/agent-tool pr merge '' "$REVIEWED_SHA" "$REVIEW_BASE_REF"`.
56
+ The source package includes the CLI and embedded skills and needs no dependency
57
+ install scripts or compile step. The executable is TypeScript run by Bun, so Bun
58
+ must be on `PATH` wherever it runs, including npm projects and CI; without it,
59
+ the shell reports `env: bun: No such file or directory`. The standalone binary
60
+ described above remains available for use without Bun.
61
+
62
+ To use an unreleased commit, pin a full commit SHA from GitHub instead:
63
+
64
+ ```json
65
+ {
66
+ "devDependencies": {
67
+ "@a2f0/agent-tool": "github:a2f0/agent-tool#<full-commit-sha>"
68
+ }
69
+ }
70
+ ```
71
+
72
+ npm records a GitHub dependency in `package-lock.json` as a `git+ssh` URL, but
73
+ `npm ci` installs this public repository without SSH credentials.
74
+
75
+ ## Commands
76
+
77
+ | Command | Behavior |
78
+ | --- | --- |
79
+ | `review claude\|codex\|opencode [effort] [--base ref]` | Review committed HEAD using a pinned raw-file snapshot |
80
+ | `pr open [title]` | Open a same-repository PR; body from stdin; branch already pushed |
81
+ | `pr merge <subject-or-empty> <head-oid> <base-branch>` | Check CI and synchronously squash the reviewed HEAD |
82
+ | `versions plan\|bump\|check <base-oid>` | Plan, rewrite, or check package versions against an exact base |
83
+ | `versions prepare <base-oid>` | Bump, refresh `bun.lock`, validate, and commit versions; print a JSON receipt |
84
+ | `versions resolve-conflicts` | Resolve only version-field conflicts in configured manifests |
85
+ | `skills list` | List embedded portable skills |
86
+ | `skills install [--harness all\|claude\|codex\|opencode] [--apply]` | Preview by default; install or update managed skills |
87
+ | `skills check [--harness all\|claude\|codex\|opencode]` | Read-only CI check for current managed skills |
88
+ | `init` / `config show` | Create or inspect data-only project policy |
89
+ | `doctor` | Inspect local versions and required CLI flags without running a model |
90
+
91
+ Effort accepts `low`, `medium`, `high`, `xhigh`, and `max`. Defaults are `xhigh`
92
+ for Claude and `high` for Codex and OpenCode. Older camelCase action names remain
93
+ available; run `--help` for the compatibility list. The modern merge command
94
+ requires reviewed HEAD and base branch; legacy `squashMerge` also retains its
95
+ manual, unguarded form.
96
+
97
+ A successful review exit means a complete review was returned, including a
98
+ `VERDICT:` line. BLOCKER or MAJOR findings still require action. The review CLI
99
+ does not repair, push, or merge; the coordinating skills orchestrate that work.
100
+
101
+ ## Project configuration
102
+
103
+ Run `agent-tool init` in a project to create `agent-tool.json`. Existing policy
104
+ files are never overwritten. Missing settings use defaults; misspelled keys,
105
+ invalid types, and unknown schema versions fail before work begins. Global
106
+ `--config <file>` or `AGENT_TOOL_CONFIG` selects an explicit policy file, useful
107
+ when reviewing a feature checkout with policy supplied from a trusted source.
108
+
109
+ ```json
110
+ {
111
+ "schemaVersion": 1,
112
+ "subject": { "conventional": true, "maxLength": 50 },
113
+ "merge": {
114
+ "requiredChecks": [{ "name": "CI gate", "workflow": "CI" }],
115
+ "requireStrictBaseFreshness": true
116
+ },
117
+ "review": {
118
+ "claudeModel": null,
119
+ "codexModel": null,
120
+ "opencodeModel": "deepseek/deepseek-v4-pro",
121
+ "opencodeVariants": { "xhigh": "max" },
122
+ "timeoutMs": 600000
123
+ },
124
+ "versions": {
125
+ "packages": ["packages/api", "packages/client"],
126
+ "lockfile": "bun.lock",
127
+ "validate": [["bun", "run", "lint", "--", "--staged"]],
128
+ "commitMessage": "chore: bump package versions"
129
+ },
130
+ "pr": { "rejectClaudeBranding": true }
131
+ }
132
+ ```
133
+
134
+ The default title policy uses conventional commits and a 72-character limit.
135
+ No commitlint binary or JavaScript config is executed. Required CI names are
136
+ project settings; default policy requires reported checks, rejects unsuccessful
137
+ checks, and allows intentionally skipped optional jobs. A named required check
138
+ must succeed. Configure explicit required names for shipping.
139
+
140
+ The OpenCode model default preserves the source projects' fallback provider; set
141
+ it to a model your account supports. `opencodeVariants` overrides mappings for
142
+ the supplied levels, with unspecified entries passed through unchanged. Claude
143
+ and Codex use their CLI defaults unless a model is configured.
144
+
145
+ Version helpers discover committed `package.json` workspaces when
146
+ `versions.packages` is null, or use the configured relative package directories;
147
+ `.` names the repository's root package, which then changes with any file,
148
+ including files in other configured packages.
149
+ They preserve deliberate major/minor releases and bump changed packages one
150
+ patch past the base. `plan`, `bump`, and `check` do not regenerate lockfiles or
151
+ commit changes.
152
+
153
+ `versions prepare` owns the whole sequence before a review snapshot. It needs a
154
+ clean worktree on an attached branch with the exact base already merged. It
155
+ rewrites versions and, when `versions.lockfile` is `"bun.lock"` (the default),
156
+ runs `bun install --lockfile-only --ignore-scripts`. It then verifies that
157
+ `bun.lock` records every workspace version, because Bun before 1.4 exits
158
+ successfully without refreshing them. Each `versions.validate` command runs
159
+ without a shell against the staged result; commands resolve on PATH outside
160
+ the repository. The commit uses `versions.commitMessage` and runs the project's
161
+ commit hooks, and must reproduce the prepared tree. Set `versions.lockfile` to
162
+ null for projects without a Bun lockfile. On success it prints a JSON receipt
163
+ with `baseOid`, `startHead`, `headOid`, `committed`, `lockfileChanged`, and the
164
+ rewritten `versions`. A failure before the commit restores the manifests,
165
+ lockfile, and index unless HEAD or other paths changed; otherwise it preserves
166
+ that state for inspection. Repeating against the same base creates no commit.
167
+
168
+ ## Portable skills
169
+
170
+ Canonical instructions live once in `skills/`. The build embeds them, and the
171
+ installer places the same bytes where each harness discovers them:
172
+
173
+ | Target | Project discovery directory |
174
+ | --- | --- |
175
+ | Claude Code | `.claude/skills` |
176
+ | Codex | `.agents/skills` |
177
+ | OpenCode alone | `.opencode/skills` |
178
+ | All three | `.agents/skills` and `.claude/skills`; OpenCode discovers both |
179
+
180
+ Discovery paths follow the official [Codex](https://learn.chatgpt.com/docs/build-skills#where-codex-loads-local-skills),
181
+ [Claude Code](https://code.claude.com/docs/en/skills), and
182
+ [OpenCode](https://opencode.ai/docs/skills/) documentation. Restart the harness
183
+ if new skills do not appear. OpenCode may see identical names through both
184
+ compatibility paths; the installed instructions are identical.
185
+
186
+ The bundle provides `cross-agent-review`, `open-pr`, `squash-merge`, `ship-pr`, and
187
+ `reset`. The installer tracks ownership with `.agent-tool-skills.json`, previews
188
+ changes without writing, and rejects unmanaged or locally edited files and
189
+ symlink destinations. Preserve existing project-specific skills before
190
+ migration rather than overwriting them. Project validation and setup commands
191
+ stay in repository guidance; the shared skills make no package-manager or
192
+ application-layout assumptions. The installer is project-scoped; it does not
193
+ modify global harness settings, MCP configuration, or authentication.
194
+
195
+ To normalize consumers, install the bundled skills and keep project validation,
196
+ review-bot rules, and deployment checks in `AGENTS.md`. Put title and required CI
197
+ settings in `agent-tool.json` instead of copying the shipping workflow. After a
198
+ dependency update, run `agent-tool skills install --apply` and commit the updated
199
+ skills with `.agent-tool-skills.json`. Run `agent-tool skills check` in hooks and
200
+ CI: it fails for missing, outdated, unmanaged, or locally edited skills without
201
+ writing anything. Use the same `--harness` when installing and checking a single
202
+ harness. Existing unmanaged skills must be preserved or deliberately migrated
203
+ before installation; there is no force-overwrite mode.
204
+
205
+ ## Review guarantees and limits
206
+
207
+ The extraction retains exact base/head snapshots, base-commit review policy,
208
+ raw Git blob materialization, collision/path validation, text diffs without
209
+ external drivers, snapshot cleanup, verdict gating, and one retry for incomplete
210
+ successful output. Git hooks and replace objects are disabled for helper Git
211
+ operations. Reviewer executables must resolve outside the project, and reviews
212
+ have configurable timeouts. Modern review rejects HEAD drift before returning.
213
+
214
+ Claude uses safe mode and read-only tools. Codex uses an ephemeral session,
215
+ snapshot-scoped filesystem permissions, no model-command environment
216
+ inheritance, disabled hosted web search, and final-message capture. OpenCode uses
217
+ a neutral directory, pure mode, inline permissions denying every tool by default,
218
+ and specific read-only allowances. Unknown/unsupported CLI flags fail the run;
219
+ there is no fallback that silently removes those protections.
220
+
221
+ These are different harness protections, not a uniform OS isolation guarantee.
222
+ Claude and OpenCode rely on their tool permission implementations. The launcher
223
+ still uses local authentication and a host environment; the stronger
224
+ credential-stripped macOS preflight from `stealth` has not been ported. A verdict
225
+ is a completion marker, not proof that a model's findings are correct.
226
+
227
+ GitHub atomically enforces expected HEAD during merging, but this mutation has
228
+ no atomic expected-base precondition. Skills recheck base freshness; repository
229
+ protection must enforce stricter concurrent-update requirements. With
230
+ `merge.requireStrictBaseFreshness`, `pr merge` refuses unless the base's
231
+ effective rules include an active repository ruleset requiring strict status
232
+ checks that the authenticated actor can never bypass. GitHub then rejects a
233
+ head that no longer contains the latest base. Classic branch protection is not
234
+ inspected. Fork reviews
235
+ can resolve upstream PR bases; the PR-opening helper initially supports
236
+ same-repository branches.
237
+
238
+ ## Packages and verification
239
+
240
+ ```sh
241
+ bun run typecheck
242
+ bun test
243
+ bun scripts/smoke-package.ts
244
+ bun run build
245
+ bun scripts/smoke.ts dist/agent-tool
246
+
247
+ # Build macOS/Linux ARM64/x64 archives and a formula with real SHA-256 values:
248
+ bun scripts/release.ts a2f0/agent-tool
249
+
250
+ # On Linux with dpkg-deb; substitute your release version:
251
+ scripts/package-deb.sh dist/agent-tool-0.1.0-linux-x64/bin/agent-tool amd64
252
+ # Install a produced Debian package:
253
+ sudo apt install ./dist/agent-tool_0.1.0_amd64.deb
254
+ ```
255
+
256
+ Release scripts create local artifacts; they do not publish anything. Put the
257
+ archives at the generated release URLs and the generated `agent-tool.rb` in a
258
+ Homebrew tap to support `brew install <owner>/<tap>/agent-tool`. A downloadable
259
+ `.deb` supports `apt install ./file.deb`; `apt install agent-tool` by name also
260
+ requires hosting a signed APT repository and configuring that source. Neither a
261
+ tap nor an APT repository is live yet. The GitHub release workflow builds and
262
+ uploads CI artifacts only.
263
+
264
+ ### npm releases
265
+
266
+ ship-pr bumps the root `package.json` patch version on every merge, and the
267
+ [publish workflow](.github/workflows/npm-publish.yml) publishes each version
268
+ newer than npm's `latest` with
269
+ [trusted publishing](https://docs.npmjs.com/trusted-publishers) and provenance.
270
+ No npm token is stored. The job runs in the `npm` environment, which only
271
+ `main` can deploy to, and npm's trusted publisher names that environment and
272
+ `npm-publish.yml`. A deliberate major or minor bump in a PR is kept. Runs never
273
+ overlap, and when merges land together only the newest pending run starts, so
274
+ intermediate versions can be skipped on npm.
275
+
276
+ npm adds a trusted publisher only to a package that already exists, so the
277
+ first version was published by hand with `npm publish --ignore-scripts`. Until
278
+ a package exists, the workflow fails with that instruction rather than
279
+ attempting a publish npm would reject.
280
+
281
+ Standalone builds use [Bun's executable support](https://bun.sh/docs/bundler/executables).
282
+ The compiled smoke test invokes all three adapters through local stubs with Bun
283
+ and Node absent from PATH; it makes no paid model calls. CI checks source tests,
284
+ type checking, compiled smoke tests, and Debian packaging on Linux, plus source
285
+ and compiled checks on macOS.
286
+
287
+ See [the validation record](docs/validation.md) for observed results and the
288
+ limits of the local tests.
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@a2f0/agent-tool",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "description": "Portable cross-agent review, PR workflows, and shared skills",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/a2f0/agent-tool.git"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "bin": {
14
+ "agent-tool": "src/index.ts"
15
+ },
16
+ "files": [
17
+ "src",
18
+ "skills",
19
+ "!src/**/*.test.ts",
20
+ "!src/**/*.testUtils.ts"
21
+ ],
22
+ "engines": {
23
+ "bun": ">=1.3.11"
24
+ },
25
+ "scripts": {
26
+ "start": "bun src/index.ts",
27
+ "test": "bun test src scripts",
28
+ "typecheck": "tsc --noEmit",
29
+ "generate": "bun scripts/bundle-skills.ts",
30
+ "build": "bun run generate && bun build --compile --minify src/index.ts --outfile dist/agent-tool"
31
+ },
32
+ "devDependencies": {
33
+ "@types/bun": "1.3.11",
34
+ "typescript": "5.9.3"
35
+ }
36
+ }
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: cross-agent-review
3
+ description: Review committed branch changes with an independent Claude Code, Codex, or OpenCode reviewer, and repair actionable findings when requested.
4
+ ---
5
+
6
+ # Cross-Agent Review
7
+
8
+ Use the installed `agent-tool` executable. Read the repository's development and
9
+ review policy and `agent-tool.json` when present. Keep project validation commands
10
+ and release rules in the repository; do not assume Bun, a workspace layout, or
11
+ particular CI job names.
12
+
13
+ Select a reviewer different from the coordinating agent when possible. Honor a
14
+ requested reviewer or effort; otherwise use an available independent reviewer.
15
+ Run `agent-tool doctor` if CLI compatibility is uncertain.
16
+
17
+ Review committed changes with `agent-tool review claude`, `agent-tool review codex`,
18
+ or `agent-tool review opencode`. Optional effort is `low`, `medium`, `high`,
19
+ `xhigh`, or `max`. For a local or non-GitHub repository, add `--base <commit-or-ref>`.
20
+ For a coordinated GitHub review, pin the fetched base using
21
+ `AGENT_TOOL_REVIEW_BASE_REF` and `AGENT_TOOL_REVIEW_BASE_OID`; fetch from the
22
+ repository that owns the PR, which can differ from the checkout's origin.
23
+
24
+ Record the exact base and HEAD before reviewing. The tool reviews raw committed
25
+ files and excludes worktree edits. A zero exit means a complete review was
26
+ returned; it does not mean the findings are non-blocking. Read the final
27
+ `VERDICT: BLOCKER|MAJOR|MINOR|SUGGESTION|CLEAN` and the findings. A reviewer
28
+ that labels findings `[P0]` through `[P3]` uses the same scale: `[P0]` and
29
+ `[P1]` are BLOCKER and MAJOR, `[P2]` and `[P3]` are MINOR and SUGGESTION.
30
+
31
+ If a CLI is unavailable, out of credits, or returns an unusable review, try another
32
+ available reviewer and disclose the fallback. Do not silently convert a failed
33
+ review into CLEAN or call an in-session review independent.
34
+
35
+ For report-only requests, report findings without changing files or history.
36
+ When repairs are authorized, address actionable BLOCKER and MAJOR findings,
37
+ validate with the repository's relevant checks, commit the repairs, and review
38
+ the new HEAD. Continue while meaningful progress is possible; report an impasse
39
+ when progress requires a material user decision. A repaired commit is unreviewed
40
+ until the next review completes. Preserve unrelated work.
41
+
42
+ Before reporting success, confirm HEAD still matches the reviewed commit and
43
+ that an existing PR points to that same commit. If the base moved, refresh it and
44
+ review again before shipping. Report reviewer, exact base and head IDs, verdict,
45
+ remaining findings, validation, and any repairs performed. This skill does not
46
+ authorize pushing, opening a PR, or merging unless the user requested that work.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: open-pr
3
+ description: Open a GitHub pull request for the current feature branch with a validated title and a reviewable description.
4
+ ---
5
+
6
+ # Open PR
7
+
8
+ Use the installed `agent-tool`. Follow repository policy for validation, commits,
9
+ and branch naming. Resolve the repository and default branch with Git and `gh`;
10
+ do not assume the branch is named main. Preserve unrelated work and finish
11
+ requested changes and appropriate checks before publishing them.
12
+
13
+ Ensure the intended commit is on a feature branch and push it through the
14
+ repository's normal push workflow. Respect existing push authorization; do not
15
+ bypass hooks. If the user only requested a draft description, prepare that text
16
+ without publishing a PR.
17
+
18
+ Write a title that satisfies the data-only `agent-tool.json` subject policy.
19
+ Describe the concrete resulting behavior and relevant validation. Put multiline
20
+ body text in a temporary file, then run:
21
+
22
+ ```sh
23
+ agent-tool pr open 'feat: describe the change' < /path/to/pr-body.txt
24
+ ```
25
+
26
+ Omitting the title uses the latest commit subject. The tool refuses duplicate
27
+ open PRs and requires the branch to be pushed at its local HEAD. It does not
28
+ push for you. Resolve a fork's head/base identities explicitly if the repository
29
+ uses fork PRs; the initial open helper supports same-repository branches.
30
+
31
+ Report the created PR link and any remaining validation or review work.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: reset
3
+ description: Return a clean checkout to its updated default branch after confirmed shipping or an explicit checkout cleanup request.
4
+ ---
5
+
6
+ # Reset
7
+
8
+ This is checkout cleanup, not a destructive Git reset. Read repository guidance
9
+ for setup and hooks. Determine the default branch through the repository's
10
+ remote metadata rather than assuming main.
11
+
12
+ Check worktree and index first. Preserve user edits; if they prevent switching,
13
+ report the condition instead of discarding or automatically stashing them. For
14
+ post-merge cleanup, confirm the PR is MERGED before removing a feature branch.
15
+
16
+ Fetch the default branch, switch to it, and update by fast-forward only. A
17
+ diverged default branch requires inspection rather than forced replacement.
18
+ Delete a merged feature branch only within the requested cleanup scope and
19
+ after verifying its exact commit is the shipped commit; squash merging does not
20
+ make Git ancestry-based branch deletion proof of shipping. Honor keep-branch.
21
+
22
+ Run only the hook installer or dependency setup documented by this repository
23
+ when the updated checkout requires it. Do not assume a package manager or copy
24
+ setup commands from another project. Report final branch, commit, and any
25
+ unfinished cleanup.
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: ship-pr
3
+ description: Complete an authorized commit, independent review, repair, PR, CI, and squash-merge workflow for a GitHub repository.
4
+ ---
5
+
6
+ # Ship PR
7
+
8
+ Use the installed `agent-tool`. Read repository guidance and policy first;
9
+ validation, versioning, package management, and hook installation remain project
10
+ concerns. Shipping authorizes this workflow within the user's stated scope.
11
+ Preserve unrelated edits and honor requested stopping points such as open-PR
12
+ only, report-only review, or keeping the feature branch.
13
+
14
+ 1. Finish the requested change on a feature branch. Validate it with appropriate
15
+ project checks and commit only the intended files.
16
+ 2. Determine the current PR's base repository and branch, or the repository's
17
+ default branch when no PR exists. Fetch and pin its exact base. Integrate it
18
+ using the repository's normal workflow, resolving conflicts without losing
19
+ user work. If project policy versions packages, prepare versions against the
20
+ pinned base after every integration or repair and before each review, using
21
+ the project's commands or `agent-tool versions prepare <base-oid>`, which
22
+ commits bumped manifests and the refreshed lockfile.
23
+ `agent-tool versions resolve-conflicts` resolves conflicts confined to
24
+ version fields. Review the resulting committed HEAD with an independent
25
+ reviewer using the bundled cross-agent-review skill. Repair blocking findings
26
+ and review every changed HEAD again. Review failure blocks shipping.
27
+ 3. Push the reviewed HEAD through normal hooks, then verify the pushed SHA still
28
+ equals the reviewed SHA. If a hook changed content or HEAD, review that result.
29
+ Open the PR using the open-pr skill, or update the existing PR as needed.
30
+ 4. Allow any configured review bots time to respond, then fetch unresolved PR
31
+ review threads. Follow repository policy for feedback: fix valid findings,
32
+ reply in the original threads, and resolve only fully addressed comments.
33
+ Validate, commit, push, and independently review every feedback or CI repair.
34
+ Wait for CI, recheck the live base, PR base branch, and local and remote heads.
35
+ A changed base requires integration, validation, and another review. When
36
+ versions are managed, `agent-tool versions check <base-oid>` must pass for the
37
+ live base. Use the squash-merge skill to merge the exact reviewed HEAD.
38
+ 5. After confirmed MERGED, honor keep-branch; otherwise use the reset skill to
39
+ return to the updated default branch and apply any documented project setup.
40
+ 6. When the project deploys the merged branch, verify the deployment for the
41
+ merge commit and run its documented live checks. Report deployment failures
42
+ separately from a successful merge; follow project policy for repairs.
43
+
44
+ Never infer a clean review from process success alone or carry a review across
45
+ content changes. Do not weaken configured CI policy to get a merge through.
46
+ If a step fails, preserve useful intermediate work and report the concrete
47
+ remaining issue. Report PR link, merged commit, review verdict, validation, and
48
+ final checkout state.
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: squash-merge
3
+ description: Squash-merge a reviewed GitHub pull request while binding the mutation to the reviewed commit and checking CI.
4
+ ---
5
+
6
+ # Squash Merge
7
+
8
+ Use the installed `agent-tool` and repository merge policy. Merge only within
9
+ the user's authorized scope. Have the exact reviewed HEAD, base commit, and base
10
+ branch available, and read the verdict and unresolved findings. BLOCKER or MAJOR
11
+ findings require repair and a new review before merging.
12
+ Follow the repository's review-bot policy and require all actionable blocking
13
+ review feedback to be addressed before merging.
14
+
15
+ Wait for required checks using `gh pr checks --watch --fail-fast`. Refresh the
16
+ PR and live base from the repository that owns the PR. Confirm local HEAD and PR
17
+ HEAD both equal the reviewed commit, the base branch is unchanged, the live base
18
+ still equals the reviewed base, and that base is an ancestor of reviewed HEAD.
19
+ If any identity changed, refresh, validate, and review again.
20
+
21
+ Use a title that satisfies `agent-tool.json` and pass both reviewed HEAD and
22
+ base branch:
23
+
24
+ ```sh
25
+ agent-tool pr merge 'feat: describe the change' <reviewed-head-oid> <base-branch>
26
+ ```
27
+
28
+ The helper enforces HEAD atomically through GitHub's `expectedHeadOid`, checks
29
+ CI, rejects queued or automatic merges, and creates a subject-only squash with
30
+ the PR reference. GitHub has no atomic expected-base argument in this mutation;
31
+ repository branch protection must enforce any required base freshness. With
32
+ `merge.requireStrictBaseFreshness`, the helper refuses to merge unless an
33
+ active strict status ruleset that the actor cannot bypass protects the base. Do
34
+ not retarget the PR concurrently with merging.
35
+
36
+ Confirm GitHub reports MERGED before any cleanup. A failed or uncertain mutation
37
+ is not evidence of a merge. Preserve the branch on failure. After a confirmed
38
+ merge, perform checkout cleanup only when requested or as part of an authorized
39
+ shipping workflow. Report the PR and merge result.
package/src/cli.ts ADDED
@@ -0,0 +1,121 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { writeFileSync, writeSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { DEFAULT_CONFIG, loadConfig } from "./config";
5
+ import { doctor } from "./doctor";
6
+ import { review } from "./harnesses";
7
+ import { runAgentToolAction } from "./runAgentToolAction";
8
+ import { BUNDLED_SKILLS } from "./skills/bundled";
9
+ import { checkSkills, installSkills } from "./skills/install";
10
+ import { bumpVersions, checkVersions, planVersions } from "./version/bumpVersions";
11
+ import { prepareVersions } from "./version/prepareVersions";
12
+ import { resolveVersionConflicts } from "./version/resolveVersionConflicts";
13
+ import { openPr } from "./pr/openPr";
14
+ import { squashMerge } from "./pr/squashMerge";
15
+ import metadata from "../package.json";
16
+
17
+ export const HELP = `agent-tool ${metadata.version}
18
+
19
+ Usage: agent-tool [--repo <directory>] [--config <file>] <command>
20
+
21
+ review <claude|codex|opencode> [effort] [--base <local-ref>]
22
+ pr open [title] Body from stdin; branch already pushed
23
+ pr merge <subject-or-empty> <head-oid> <base-branch>
24
+ versions <plan|bump|check|prepare> <base-oid>
25
+ versions resolve-conflicts
26
+ skills list
27
+ skills install [--harness <all|claude|codex|opencode>] [--apply]
28
+ skills check [--harness <all|claude|codex|opencode>]
29
+ init Create agent-tool.json without overwriting
30
+ config show
31
+ doctor JSON versions and supported review flags
32
+ --help | --version
33
+
34
+ Legacy actions remain available: solicitClaudeCodeReview, solicitCodexReview,
35
+ solicitOpencodeReview, openPr, squashMerge, bumpVersions, checkVersions,
36
+ resolveVersionConflicts. A successful review exit means a usable review was
37
+ returned; inspect VERDICT before shipping. Skill install previews unless --apply.
38
+ `;
39
+
40
+ function output(value: unknown): void { writeSync(1, `${JSON.stringify(value, null, 2)}\n`); }
41
+ function usage(condition: boolean, message: string): void { if (!condition) throw new Error(message); }
42
+ function gitRoot(): string {
43
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
44
+ }
45
+
46
+ export function main(argv = process.argv.slice(2)): number {
47
+ const args = [...argv];
48
+ while (args[0] === "--repo" || args[0] === "--config") {
49
+ const flag = args.shift()!;
50
+ const value = args.shift();
51
+ usage(!!value && !value.startsWith("--"), `${flag} requires a path.`);
52
+ if (flag === "--repo") process.chdir(value!);
53
+ else process.env.AGENT_TOOL_CONFIG = path.resolve(value!);
54
+ }
55
+ const command = args.shift();
56
+ if (!command || command === "--help" || command === "-h") {
57
+ usage(args.length === 0, "Unexpected arguments after --help.");
58
+ writeSync(1, HELP); return 0;
59
+ }
60
+ if (command === "--version") { usage(args.length === 0, "Unexpected version arguments."); writeSync(1, `${metadata.version}\n`); return 0; }
61
+ if (command === "init") {
62
+ usage(args.length === 0, "Usage: agent-tool init");
63
+ writeFileSync(path.join(process.cwd(), "agent-tool.json"), `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\n`, { flag: "wx" });
64
+ writeSync(1, "Created agent-tool.json. Set project policy before shipping.\n"); return 0;
65
+ }
66
+ if (command === "config") { usage(args.length === 1 && args[0] === "show", "Usage: agent-tool config show"); output(loadConfig()); return 0; }
67
+ if (command === "doctor") {
68
+ usage(args.length === 0, "Usage: agent-tool doctor");
69
+ const result = doctor(process.cwd()); output(result);
70
+ return result.tools.find(item => item.name === "git")?.compatible && result.tools.some(item => ["claude", "codex", "opencode"].includes(item.name) && item.compatible) ? 0 : 1;
71
+ }
72
+ if (command === "skills") {
73
+ const action = args.shift();
74
+ if (action === "list") { usage(args.length === 0, "Usage: agent-tool skills list"); output(Object.keys(BUNDLED_SKILLS)); return 0; }
75
+ usage(action === "install" || action === "check", "Usage: agent-tool skills <list|install|check>");
76
+ let target = "all", apply = false;
77
+ while (args.length) {
78
+ const flag = args.shift();
79
+ if (flag === "--apply") { usage(action === "install", "skills check is read-only; --apply is not allowed."); usage(!apply, "Duplicate --apply."); apply = true; }
80
+ else if (flag === "--harness") { const value = args.shift(); usage(!!value, "--harness requires a name."); target = value!; }
81
+ else throw new Error(`Unknown skills ${action} argument '${flag}'.`);
82
+ }
83
+ if (action === "check") {
84
+ const changes = checkSkills(process.cwd(), target);
85
+ const ok = changes.every(change => change.status === "unchanged");
86
+ output({ ok, changes }); return ok ? 0 : 1;
87
+ }
88
+ output({ applied: apply, changes: installSkills(process.cwd(), target, apply) }); return 0;
89
+ }
90
+ const root = gitRoot();
91
+ process.chdir(root);
92
+ loadConfig(root); // Validate policy before running a reviewer or mutating GitHub.
93
+ if (command === "review") {
94
+ const name = args.shift(); usage(!!name, "Usage: agent-tool review <claude|codex|opencode> [effort] [--base ref]");
95
+ let effort: string | undefined, base: string | undefined;
96
+ while (args.length) {
97
+ const value = args.shift()!;
98
+ if (value === "--base") { usage(base === undefined && !!args[0] && !args[0].startsWith("--"), "--base requires one local ref."); base = args.shift(); }
99
+ else { usage(effort === undefined && !value.startsWith("-"), `Unexpected review argument '${value}'.`); effort = value; }
100
+ }
101
+ return review(root, name!, effort, base);
102
+ }
103
+ if (command === "pr") {
104
+ const action = args.shift();
105
+ if (action === "open") { usage(args.length <= 1, "Usage: agent-tool pr open [title]"); return openPr(root, args[0]); }
106
+ usage(action === "merge" && args.length === 3, "Usage: agent-tool pr merge <subject-or-empty> <reviewed-head-oid> <base-branch>");
107
+ usage(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(args[1]!), "Merge requires a full reviewed HEAD OID.");
108
+ return squashMerge(root, args[0], args[1], args[2]);
109
+ }
110
+ if (command === "versions") {
111
+ const action = args.shift();
112
+ if (action === "resolve-conflicts") { usage(args.length === 0, "resolve-conflicts takes no arguments."); return resolveVersionConflicts(root); }
113
+ usage(args.length === 1, "Usage: agent-tool versions <plan|bump|check|prepare> <base-oid>");
114
+ if (action === "plan") { output(planVersions(root, args[0])); return 0; }
115
+ if (action === "bump") return bumpVersions(root, args[0]);
116
+ if (action === "check") return checkVersions(root, args[0]);
117
+ if (action === "prepare") return prepareVersions(root, args[0]);
118
+ throw new Error(`Unknown version action '${action}'.`);
119
+ }
120
+ return runAgentToolAction(root, [command, ...args]);
121
+ }