@a2f0/agent-tool 0.1.37 → 0.1.39

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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # agent-tool
2
2
 
3
3
  agent-tool is a command-line tool and a set of portable skills for repositories
4
- where Claude Code, Codex, or OpenCode make the changes. It provides shared Git,
4
+ where Claude Code, Codex, OpenCode, or Hermes make the changes. It provides shared Git,
5
5
  GitHub, review, validation, and release workflows: independent agent reviews of
6
6
  a committed branch, guarded pushes and squash merges of the exact reviewed
7
7
  commit, package versioning, brand image rendering, screenshot staging with a
@@ -69,7 +69,7 @@ from a clone:
69
69
  mise install
70
70
  bun install --frozen-lockfile
71
71
  bun src/index.ts --help
72
- bun test --parallel
72
+ bun test --parallel=2
73
73
  ```
74
74
 
75
75
  [Development](docs/development.md) covers the source layout, the full
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@a2f0/agent-tool",
3
- "version": "0.1.37",
4
- "packageManager": "bun@1.4.3",
3
+ "version": "0.1.39",
4
+ "packageManager": "bun@1.4.4",
5
5
  "type": "module",
6
6
  "description": "Portable cross-agent review, PR workflows, and shared skills",
7
7
  "license": "UNLICENSED",
@@ -34,14 +34,14 @@
34
34
  "start": "bun src/index.ts",
35
35
  "agent:run": "bun src/index.ts run",
36
36
  "agent:logs": "bun src/index.ts logs",
37
- "test": "bun test --parallel src scripts",
37
+ "test": "bun test --parallel=2 src scripts",
38
38
  "typecheck": "tsc --noEmit && tsc -p tsconfig.browser.json",
39
39
  "generate": "bun scripts/build/bundle-skills.ts",
40
40
  "build": "bun run generate && bun build --compile --minify src/index.ts --outfile dist/agent-tool"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@happy-dom/global-registrator": "21.0.4",
44
- "@types/bun": "1.4.3",
44
+ "@types/bun": "1.4.4",
45
45
  "happy-dom": "21.0.4",
46
46
  "typescript": "7.0.2"
47
47
  },
@@ -10,8 +10,11 @@ review policy and `agent-tool.json` when present. Keep project validation comman
10
10
  and release rules in the repository; do not assume Bun, a workspace layout, or
11
11
  particular CI job names.
12
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.
13
+ Select an independent reviewer using a different model/provider from the author
14
+ when possible, not merely a different CLI: Hermes and OpenCode can use the same
15
+ model as another harness. Hermes can coordinate this skill but is not a `review`
16
+ adapter. Honor a requested reviewer or effort; otherwise use an available
17
+ independent reviewer.
15
18
  Run `agent-tool doctor` if CLI compatibility is uncertain.
16
19
 
17
20
  Run noisy validation or repair checks through
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: update-dependencies
3
- description: Update repository dependencies, runtimes, tools, and CI pins with compatibility checks and safe infrastructure previews, then ship through review, CI, merge, and release verification unless a narrower scope is requested.
3
+ description: Update dependencies or update everything in a repository, including runtimes, tools, and CI pins, with compatibility checks and safe previews; ship through review, CI, merge, and release verification unless a narrower scope is requested.
4
4
  ---
5
5
 
6
6
  # Update Dependencies
@@ -28,6 +28,54 @@ audit/plan results, deployment previews, and review findings directly visible
28
28
  when they drive a safety decision. Do not dump full suite logs into context,
29
29
  silence warnings at their source, or change check scope to reduce output.
30
30
 
31
+ ## Checkout setup
32
+
33
+ This shared workflow also handles natural-language requests to "update everything".
34
+ If the repository owns an `update-everything` wrapper, read and follow its extra
35
+ requirements; do not create an alias or overwrite that skill. The workflow works
36
+ from a standalone checkout with no workspace coordinator.
37
+
38
+ After a fetch, branch integration, or dependency pin change, check that the local
39
+ `agent-tool --version` matches the repository's declared pin before trusting its
40
+ commands or generated skills. A stale CLI can reject newer configuration keys.
41
+ After screening install scripts and hooks as below, run the documented frozen
42
+ install with the repository's pinned runtime. For Bun projects, an already working
43
+ CLI can run `agent-tool bun install`; if it is missing or cannot parse the new
44
+ config, run the documented package-manager install directly. A known working
45
+ executable may capture it with `agent-tool --repo <checkout> run --label setup --
46
+ <install-command> [args...]`. Do not fetch an unpinned CLI, alter global tools, or
47
+ assume a sibling workspace exists. Verify the installed version afterward; if it
48
+ still differs, report the mismatch rather than deleting arbitrary dependencies.
49
+
50
+ After upgrading agent-tool, run the repository's skill/permission sync commands
51
+ (or `agent-tool skills install --harness <existing-scope> --apply`), preserve its
52
+ harness scope, and commit ownership manifests with generated files. Hermes reads
53
+ the shared `.agents/skills` in a trusted project; the installer does not grant
54
+ trust. If discovery is unavailable, read those files directly rather than silently
55
+ using a stale global copy.
56
+
57
+ ## Nonblocking execution
58
+
59
+ Use the harness's managed background execution for long finite checks, reviews,
60
+ push hooks, CI waits, and release verification; request completion notification
61
+ when available and retain its process handle plus agent-tool's log/receipt path.
62
+ Run `run-group` and reviews directly so their reports remain visible. A started
63
+ process is not a passed check: read its final status and required evidence before
64
+ shipping, and do not repeatedly poll when completion events are available.
65
+ Without harness background support, `proc start --label <name> -- agent-tool run
66
+ --label <check> -- <command> [args...]` records the outer process and captures
67
+ its output; inspect `proc list` and `logs` plus the inner run receipt to establish
68
+ completion and exit status. An exited process record alone does not prove success.
69
+ Use `proc stop`/`teardown` for owned leftovers; never stop another session's work.
70
+ These commands need no matrix workspace or global configuration change.
71
+
72
+ Keep independent compatibility groups moving while a producer release or an
73
+ external gate is pending. Only final adoption of that exact published release
74
+ must wait. Bound CPU/process-heavy concurrency to the machine's available budget;
75
+ a failed full suite needs diagnosis and a full-scope rerun, not larger timeouts,
76
+ fewer assertions, or a focused retry presented as suite recovery. Background
77
+ execution never bypasses an approval, a deployment preview, or exact-head review.
78
+
31
79
  ## Safety before execution
32
80
 
33
81
  **Always dry-run before any infrastructure apply or deployment. Never destroy,
@@ -223,8 +271,24 @@ versions with `agent-tool bun update <name[@version]>` and drop packages with
223
271
  `agent-tool bun remove <name>`; both use the pinned Bun and refuse to add a
224
272
  package. A new dependency goes through `agent-tool bun add <name@version>`, which
225
273
  asks for approval; never introduce one by editing `package.json` and
226
- reinstalling. Refresh transitive dependencies through
227
- their owning direct dependency; do not hand-edit integrity hashes or lock entries.
274
+ reinstalling. For existing Bun catalog entries use
275
+ `agent-tool bun update --catalog default <name@version>...` (or the existing named
276
+ catalog). Name an explicit exact/ranged registry spec; keep `catalog:` references,
277
+ workspace membership, other entries, and peer compatibility contracts intact.
278
+ The helper refuses intake and ambiguous layouts and uses the owning manager to
279
+ refresh the lock; do not replace catalog references with direct versions.
280
+
281
+ For an already-resolved transitive registry package with an eligible owner range,
282
+ use `agent-tool bun update --resolved <name>...` with the owning Bun 1.4.4 or newer.
283
+ It selects every locked copy of those names, never a requested version or glob,
284
+ and accepts only a result with identical manifests and unchanged non-target lock
285
+ entries/metadata. It preserves release-age, overrides and patches. If an older
286
+ manager is pinned, or a required new child prevents a target-only result, this
287
+ path is unsupported: prefer an owning-dependency upgrade through the normal
288
+ helper, or obtain approval for an explicitly reviewed manager workflow. Do not
289
+ silently substitute a broad refresh or add a transitive package directly. Inspect
290
+ the receipt/logs and re-audit; a no-op or warning can leave an advisory unresolved.
291
+ Never hand-edit integrity hashes or lock entries.
228
292
  Review intentional patches, overrides, and pins before removal; retain them until
229
293
  upstream fixes and regression checks demonstrate they are unnecessary.
230
294
  Avoid unrelated global installations or environment changes. Use task-local or
@@ -1,13 +1,14 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { closeSync, existsSync, fstatSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { chmodSync, closeSync, existsSync, fstatSync, lstatSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { createLog } from "../commands/logFile";
5
5
  import { DEFAULT_LIMITS, printLogTail } from "../commands/output";
6
6
  import { assert, flag, option, output } from "../workflows/core";
7
7
  import { bunExecute } from "./execute";
8
8
  import { pinnedBun, type BunExecutable } from "./toolchain";
9
- import { lockedWorkspaceVersions, lostWorkspaceReferences } from "../version/lockfileVersions";
9
+ import { lockedWorkspaceVersions, lostWorkspaceReferences, unresolvedDependencies } from "../version/lockfileVersions";
10
10
  import { packageDirectories, workspacePackage } from "../system/workspace";
11
+ import { assertNoLinks, replaceFile } from "../system/files";
11
12
 
12
13
  const SECTIONS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"] as const;
13
14
  type Section = typeof SECTIONS[number];
@@ -20,6 +21,8 @@ const USAGE = `Usage:
20
21
  agent-tool bun install [--cwd <dir>]
21
22
  agent-tool bun add <name@version>... [--dev|--optional|--peer] [--exact] [--cwd <dir>]
22
23
  agent-tool bun update <name[@version]>... [--cwd <dir>]
24
+ agent-tool bun update --catalog <default|name> <name@version>... [--cwd <dir>]
25
+ agent-tool bun update --resolved <name>... [--cwd <dir>]
23
26
  agent-tool bun remove <name>... [--cwd <dir>]
24
27
  agent-tool bun test [--cwd <dir>] [--log] [-- args...]
25
28
  agent-tool bun x <executable> [--cwd <dir>] [--log] [-- args...]`;
@@ -97,16 +100,24 @@ function changes(before: Manifest, after: Manifest, names: Set<string>): Depende
97
100
  }
98
101
 
99
102
  /** Saved bytes for each file, with a way to put them back and to list which ones changed. */
100
- function snapshot(files: string[]) {
101
- const saved = files.map(file => ({ file, contents: existsSync(file) ? readFileSync(file) : null }));
103
+ function snapshot(files: string[], safeRoot?: string) {
104
+ if (safeRoot) for (const file of files) assertNoLinks(safeRoot, file);
105
+ const saved = files.map(file => ({ file, contents: existsSync(file) ? readFileSync(file) : null, mode: existsSync(file) ? lstatSync(file).mode & 0o777 : 0o600 }));
102
106
  return {
103
107
  /** Restore every file even if one fails, recreating directories a script removed. */
104
108
  restore() {
105
109
  const failures: string[] = [];
106
- for (const { file, contents } of saved) {
110
+ for (const { file, contents, mode } of saved) {
107
111
  try {
108
- if (contents) { mkdirSync(path.dirname(file), { recursive: true }); writeFileSync(file, contents); }
109
- else if (existsSync(file)) unlinkSync(file);
112
+ if (safeRoot) assertNoLinks(safeRoot, path.dirname(file));
113
+ if (contents) {
114
+ mkdirSync(path.dirname(file), { recursive: true });
115
+ if (safeRoot) { replaceFile(file, contents); chmodSync(file, mode); }
116
+ else writeFileSync(file, contents);
117
+ } else {
118
+ try { lstatSync(file); } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") continue; throw error; }
119
+ unlinkSync(file);
120
+ }
110
121
  } catch (error) { failures.push(`${file}: ${(error as Error).message}`); }
111
122
  }
112
123
  if (failures.length) throw new Error(`Could not restore ${failures.join("; ")}`);
@@ -130,6 +141,72 @@ function runBun(bun: BunExecutable, directory: string, args: string[]) {
130
141
  } finally { closeSync(fd); }
131
142
  }
132
143
 
144
+ function resolvedPlan(root: string, version: string, requested: Requested[], workspaceNames: Set<string>) {
145
+ assert(Bun.semver.satisfies(version, ">=1.4.4"), `Resolved-only updates require the owning Bun 1.4.4 or newer (found ${version}); older Bun can add a direct declaration. Upgrade the project's pin through its normal approval workflow first.`);
146
+ const readLock = () => {
147
+ const lock = Bun.JSONC.parse(readFileSync(path.join(root, "bun.lock"), "utf8")) as Record<string, unknown> & { packages: Record<string, unknown[]> };
148
+ assert(lock && (lock.lockfileVersion === 1 || lock.lockfileVersion === 2) && lock.packages && typeof lock.packages === "object" && !Array.isArray(lock.packages), "Resolved-only updates need a supported text bun.lock with packages.");
149
+ return lock;
150
+ };
151
+ const keyName = (key: string) => { const parts = key.split("/"); return parts.at(-2)?.startsWith("@") ? parts.slice(-2).join("/") : parts.at(-1)!; };
152
+ const registry = (tuple: unknown, name: string) => Array.isArray(tuple) && typeof tuple[0] === "string" && tuple[0].startsWith(`${name}@`) && EXACT.test(tuple[0].slice(name.length + 1));
153
+ const before = readLock();
154
+ const names = new Set(requested.map(item => item.name));
155
+ for (const { name, spec } of requested) {
156
+ assert(spec === undefined, `Resolved-only updates take names without versions: ${name}. Owner ranges must not be overridden.`);
157
+ assert(!workspaceNames.has(name), `${name} is a workspace package; it cannot be refreshed from the registry.`);
158
+ const entries = Object.entries(before.packages).filter(([key, tuple]) => keyName(key) === name || registry(tuple, name));
159
+ assert(entries.length, `${name} is not an already-resolved registry package; adding a package needs explicit approval.`);
160
+ assert(entries.every(([key, tuple]) => keyName(key) === name && registry(tuple, name)), `${name} has an alias, ambiguous identity, or non-registry source in bun.lock; update its owner instead.`);
161
+ for (const directory of packageDirectories(root)) assert(!declarations(readManifest(path.join(directory, "package.json")), name).length, `${name} is directly declared in ${path.relative(root, directory) || "."}; use the declared-dependency update path.`);
162
+ }
163
+ return {
164
+ violations() {
165
+ const after = readLock();
166
+ const { packages: oldPackages, ...oldMetadata } = before;
167
+ const { packages: newPackages, ...newMetadata } = after;
168
+ const violations = JSON.stringify(oldMetadata) === JSON.stringify(newMetadata) ? [] : ["bun.lock metadata changed outside resolved packages."];
169
+ for (const key of new Set([...Object.keys(oldPackages), ...Object.keys(newPackages)])) {
170
+ const name = keyName(key);
171
+ if (!names.has(name)) {
172
+ if (JSON.stringify(oldPackages[key]) !== JSON.stringify(newPackages[key])) violations.push(`bun.lock changed non-target resolution ${key}; this refresh cannot stay target-only. Update the owning dependency instead.`);
173
+ } else if (newPackages[key] !== undefined && !registry(newPackages[key], name)) violations.push(`${key} no longer resolves to its registry package.`);
174
+ }
175
+ return violations;
176
+ },
177
+ changes() {
178
+ const after = readLock();
179
+ return [...new Set([...Object.keys(before.packages), ...Object.keys(after.packages)])].flatMap(key => {
180
+ const from = before.packages[key]?.[0] ?? null, to = after.packages[key]?.[0] ?? null;
181
+ return from === to ? [] : [{ key, from, to }];
182
+ });
183
+ },
184
+ };
185
+ }
186
+
187
+ function catalogPlan(before: Manifest, catalog: string, requested: Requested[], workspaceNames: Set<string>) {
188
+ const manifest: Manifest = JSON.parse(JSON.stringify(before));
189
+ const object = (value: unknown): Record<string, unknown> | undefined => value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
190
+ const workspaces = object(manifest.workspaces);
191
+ const catalogEntries = (owner: Record<string, unknown> | undefined) => catalog === "default" ? owner?.catalog ?? object(owner?.catalogs)?.default : object(owner?.catalogs)?.[catalog];
192
+ assert(catalog.length > 0 && catalog.trim() === catalog && !/[\s:/\\]/.test(catalog), "Use a literal catalog name, or default; whitespace and path-like selectors are not supported.");
193
+ const locations = [catalogEntries(manifest), catalogEntries(workspaces)].filter(value => value !== undefined);
194
+ assert(locations.length <= 1, `Catalog ${catalog} has an ambiguous top-level/workspaces definition; choose one layout manually.`);
195
+ if (catalog === "default") for (const owner of [manifest, workspaces]) assert(!(owner?.catalog && object(owner?.catalogs)?.default), "Default catalog has an ambiguous catalog/catalogs.default definition; use one definition manually.");
196
+ const entries = locations[0] as Record<string, string> | undefined;
197
+ assert(entries && typeof entries === "object" && !Array.isArray(entries), `Catalog ${catalog} must already exist; creating catalogs needs explicit approval.`);
198
+ const catalogChanges = requested.map(({ name, spec }) => {
199
+ assert(Object.hasOwn(entries, name), `${name} is not declared in catalog ${catalog}; adding a package needs \`agent-tool bun add\` or an approved catalog edit.`);
200
+ const from = entries[name]!;
201
+ assert(typeof from === "string" && registrySpec(from), `${name} uses a non-registry catalog source; update it manually.`);
202
+ assert(!workspaceNames.has(name), `${name} is a workspace package; change references to it by hand.`);
203
+ assert(spec && registrySpec(spec), `Name an explicit registry version or range for catalog entry ${name}.`);
204
+ entries[name] = spec;
205
+ return { catalog, name, from, to: spec };
206
+ }).filter(change => change.from !== change.to);
207
+ return { manifest, catalogChanges };
208
+ }
209
+
133
210
  function parseArgs(action: string, args: string[]) {
134
211
  const sections = (["--dev", "--optional", "--peer"] as const).filter(name => flag(args, name));
135
212
  const exact = flag(args, "--exact");
@@ -168,6 +245,7 @@ function plan(action: "add" | "update" | "remove", manifest: Manifest, requested
168
245
  assert(!workspaceNames.has(name), `${name} is a workspace package; change references to it by hand.`);
169
246
  assert(installed.length === 1, `${name} is declared in ${installed.map(item => item.section).join(" and ")}; update it manually.`);
170
247
  const current = installed[0]!;
248
+ assert(!current.spec.startsWith("catalog:"), `${name} uses ${current.spec}; update its shared entry explicitly with \`agent-tool bun update --catalog ${current.spec.slice(8).trim() || "default"} ${name}@<version>\`.`);
171
249
  assert(registrySpec(current.spec), `${name} uses a non-registry source (${current.spec}); update it manually.`);
172
250
  assert(spec === undefined || registrySpec(spec), `${name} can only be updated to a registry version or tag.`);
173
251
  // An explicit version is written as given; moving to latest keeps an exact pin exact.
@@ -185,7 +263,11 @@ export function bunCommand(args: string[]): number | Promise<number> {
185
263
  const target = path.resolve(option(args, "--cwd") ?? ".");
186
264
  assert(existsSync(path.join(target, "package.json")), `No package.json in ${target}.`);
187
265
  const { root, directory } = workspacePackage(target);
188
- const file = path.join(directory, "package.json");
266
+ const catalog = option(args, "--catalog");
267
+ const resolved = flag(args, "--resolved");
268
+ assert((catalog === undefined && !resolved) || action === "update", "--catalog and --resolved apply only to bun update.");
269
+ assert(catalog === undefined || !resolved, "Choose either --catalog or --resolved, not both.");
270
+ const file = path.join(catalog === undefined && !resolved ? directory : root, "package.json");
189
271
  const { requested, exact, addSection } = parseArgs(action, args);
190
272
  const bun = pinnedBun(directory, root);
191
273
  const bunInfo = { path: bun.path, version: bun.version, source: bun.source, pinned: bun.pinned };
@@ -198,43 +280,79 @@ export function bunCommand(args: string[]): number | Promise<number> {
198
280
  // Before a change, the lockfile must already cover every declaration, or Bun would also resolve a hand-added package.
199
281
  const preflight = ["install", "--frozen-lockfile", "--lockfile-only"];
200
282
  const workspaceNames = new Set(packageDirectories(root).map(item => (JSON.parse(readFileSync(path.join(item, "package.json"), "utf8")) as { name?: string }).name).filter((name): name is string => typeof name === "string"));
201
- const calls = action === "install" ? [["install", "--frozen-lockfile"]] : [preflight, ...plan(action, before, requested, exact, addSection, workspaceNames)];
283
+ const catalogUpdate = catalog === undefined ? null : catalogPlan(before, catalog, requested, workspaceNames);
284
+ const finalFrozen = ["install", "--frozen-lockfile"];
285
+
202
286
  // The checks below read the text lockfile; a binary bun.lockb cannot be verified.
203
287
  if (action !== "install") assert(existsSync(path.join(root, "bun.lock")), "Changing dependencies needs a text bun.lock; convert bun.lockb with approval first.");
288
+ const resolvedUpdate = resolved ? resolvedPlan(root, bun.version, requested, workspaceNames) : null;
289
+ const calls = catalogUpdate ? [preflight, ["install"], finalFrozen]
290
+ : resolvedUpdate ? [preflight, ["update", ...requested.map(item => item.name)], finalFrozen]
291
+ : action === "install" ? [["install", "--frozen-lockfile"]] : [preflight, ...plan(action, before, requested, exact, addSection, workspaceNames)];
204
292
  if (action !== "install") {
205
293
  // A frozen check can accept stale workspace versions; an unfrozen call would then fetch such a reference.
206
294
  const lost = lostWorkspaceReferences(root, lockedWorkspaceVersions(root));
207
295
  assert(!lost.length, `${lost.join(", ")} no longer match their workspaces, so Bun would fetch them from the registry; fix those references before changing dependencies.`);
208
296
  }
297
+ if (catalogUpdate) {
298
+ for (const item of packageDirectories(root)) {
299
+ const peers = readManifest(path.join(item, "package.json")).peerDependencies ?? {};
300
+ for (const { name } of requested) {
301
+ const reference = peers[name];
302
+ assert(!reference?.startsWith("catalog:") || (reference.slice(8).trim() || "default") !== catalog,
303
+ `${name} in catalog ${catalog} supplies a peer compatibility range in ${path.relative(root, item) || "."}; update that contract manually.`);
304
+ }
305
+ }
306
+ }
307
+ const guarded = !!(catalogUpdate || resolvedUpdate);
308
+ if (guarded) {
309
+ const unresolved = unresolvedDependencies(root);
310
+ assert(!unresolved.length, `${unresolved.join(", ")} differ from bun.lock; resolve that difference with approval before changing dependencies.`);
311
+ }
209
312
  // Lifecycle scripts can touch any workspace manifest; every one but the target must stay as it was.
210
313
  const others = packageDirectories(root).map(item => path.join(item, "package.json")).filter(item => item !== file && existsSync(item));
211
314
  const othersBefore = new Map(others.map(item => [item, JSON.stringify(readManifest(item))]));
212
315
  const restored = [...new Set([file, ...others, ...lockfiles, ...nested])];
213
- const saved = snapshot(restored);
316
+ const saved = snapshot(restored, guarded ? root : undefined);
214
317
  const names = new Set(requested.map(item => item.name));
215
318
  const runs = [];
216
319
  let violations: string[] = [];
217
320
  let after = before;
321
+ let frozenSnapshot: ReturnType<typeof snapshot> | null = null;
218
322
  // Validate after every Bun call: a lifecycle script in one call must not feed the next.
219
323
  const known = new Set([file, ...others]);
220
324
  // A lifecycle script can create a workspace, or widen the globs to one, for a later call to resolve.
221
325
  const discover = (): string[] => packageDirectories(root).map(item => path.join(item, "package.json")).filter(item => !known.has(item) && existsSync(item));
222
326
  // Call after restoring the root manifest: under the original globs, every manifest that existed
223
327
  // before is known, so anything else was created during the change. Widened globs never delete.
224
- const removeCreated = () => { for (const item of discover()) unlinkSync(item); };
328
+ const removeCreated = () => { for (const item of discover()) { if (guarded) assertNoLinks(root, item); unlinkSync(item); } };
225
329
  const validate = (call: string[]): string[] => {
330
+ if (guarded) for (const item of restored) assertNoLinks(root, item);
226
331
  const appeared = discover();
227
332
  if (appeared.length) return appeared.map(item => `${path.relative(root, item)} appeared during the change.`);
228
- if (call === preflight || action === "install") return saved.changed().map(item => `${path.relative(directory, item) || "package.json"} changed during a frozen install.`);
333
+ if (call === finalFrozen) return frozenSnapshot!.changed().map(item => `${path.relative(root, item)} changed during the final frozen install.`);
334
+ const changed = saved.changed();
335
+ if (call === preflight || action === "install") return changed.map(item => `${path.relative(directory, item) || "package.json"} changed during a frozen install.`);
229
336
  after = readManifest(file);
230
- const found = manifestViolations(action, before, after, names, addSection);
231
- for (const item of others) if (JSON.stringify(readManifest(item)) !== othersBefore.get(item)) found.push(`${path.relative(root, item)} changed outside the target package.`);
232
- for (const item of saved.changed().filter(changed => nested.includes(changed))) found.push(`${path.relative(root, item)} changed beside a workspace package.`);
337
+ const found = catalogUpdate
338
+ ? (JSON.stringify(after) === JSON.stringify(catalogUpdate.manifest) ? [] : ["package.json changed outside the requested catalog entries."])
339
+ : resolvedUpdate ? (changed.includes(file) ? ["package.json changed during a resolved-only update."] : [])
340
+ : manifestViolations(action, before, after, names, addSection);
341
+ for (const item of others) if (guarded ? changed.includes(item) : JSON.stringify(readManifest(item)) !== othersBefore.get(item)) found.push(`${path.relative(root, item)} changed outside the target package.`);
342
+ for (const item of changed.filter(item => nested.includes(item))) found.push(`${path.relative(root, item)} changed beside a workspace package.`);
343
+ if (resolvedUpdate) found.push(...resolvedUpdate.violations());
233
344
  return found;
234
345
  };
235
346
  try {
236
347
  for (const call of calls) {
237
- const run = runBun(bun, directory, call);
348
+ if (catalogUpdate && call !== preflight && call !== finalFrozen) {
349
+ assertNoLinks(root, file);
350
+ const mode = lstatSync(file).mode & 0o777;
351
+ replaceFile(file, Buffer.from(`${JSON.stringify(catalogUpdate.manifest, null, 2)}\n`));
352
+ chmodSync(file, mode);
353
+ }
354
+ if (call === finalFrozen) frozenSnapshot = snapshot(restored, root);
355
+ const run = runBun(bun, catalogUpdate || resolvedUpdate ? root : directory, call);
238
356
  runs.push(run);
239
357
  if (run.exitCode !== 0) { violations = [call === preflight ? "package.json and the lockfile disagree; resolve that difference with approval before changing dependencies." : `bun ${call[0]} failed with exit ${run.exitCode}.`]; break; }
240
358
  violations = validate(call);
@@ -250,7 +368,7 @@ export function bunCommand(args: string[]): number | Promise<number> {
250
368
  saved.restore();
251
369
  removeCreated();
252
370
  }
253
- output({ ok: !violations.length, action, directory, bun: bunInfo, changes: violations.length ? [] : changes(before, after, names), ...(violations.length ? { violations, restored: restored.map(item => path.relative(directory, item) || "package.json") } : {}), runs });
371
+ output({ ok: !violations.length, action, directory, bun: bunInfo, changes: violations.length || catalogUpdate || resolvedUpdate ? [] : changes(before, after, names), ...(resolvedUpdate ? { scope: "resolved-targets", resolvedChanges: violations.length ? [] : resolvedUpdate.changes() } : {}), ...(catalogUpdate ? { catalogChanges: violations.length ? [] : catalogUpdate.catalogChanges } : {}), ...(violations.length ? { violations, restored: restored.map(item => path.relative(directory, item) || "package.json") } : {}), runs });
254
372
  if (runs.at(-1)?.exitCode) printLogTail(runs.at(-1)!.log, DEFAULT_LIMITS, 2);
255
373
  return violations.length ? 1 : 0;
256
374
  }
package/src/cli.ts CHANGED
@@ -66,11 +66,13 @@ Usage: agent-tool [--repo <directory>] [--config <file>] <command>
66
66
  dependencies check-terraform-plan <json-file> Read-only destructive-plan check
67
67
  dependencies diff --base <ref> Declared dependencies added, removed, or changed since base
68
68
  skills list
69
- skills install [--harness <all|claude|codex|opencode>] [--apply]
70
- skills check [--harness <all|claude|codex|opencode>]
69
+ skills install [--harness <all|claude|codex|opencode|hermes>] [--apply]
70
+ skills check [--harness <all|claude|codex|opencode|hermes>]
71
71
  bun install [--cwd <dir>] Frozen-lockfile install with the pinned Bun
72
72
  bun add <name@version>... [--dev|--optional|--peer] [--exact] [--cwd <dir>]
73
73
  bun update <name[@version]>... [--cwd <dir>] Change versions of declared dependencies only
74
+ bun update --catalog <default|name> <name@version>... [--cwd <dir>] Existing catalog entries only
75
+ bun update --resolved <name>... [--cwd <dir>] Existing transitive resolutions only (Bun 1.4.4+)
74
76
  bun remove <name>... [--cwd <dir>] Remove declared dependencies only
75
77
  bun test [--cwd <dir>] [--log] [-- args...]
76
78
  bun x <executable> [--cwd <dir>] [--log] [-- args...] Installed executables only, never fetched
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export const CLASSES = {
7
7
  read: { decision: "allow", description: "Reads local or GitHub state without changing it." },
8
- local: { decision: "allow", description: "Changes only this checkout: files, local branches, lockfile installs, and versions of declared dependencies." },
8
+ local: { decision: "allow", description: "Changes only this checkout: files, local branches, lockfile installs, and versions of declared or already-resolved dependencies." },
9
9
  share: { decision: "allow", description: "Publishes work for review without changing a shared branch: pushes a feature branch, opens a PR, answers review threads, or prunes a merged PR's feature branch." },
10
10
  release: { decision: "ask", description: "Merges into a shared branch, publishes a package, or deletes remote state." },
11
11
  intake: { decision: "ask", description: "Brings new third-party code into the repository." },
@@ -53,8 +53,9 @@ export const GUARDS: { command: string; class: PermissionClass }[] = [
53
53
  export const INVOCATIONS = ["bun run agent-tool", "node_modules/.bin/agent-tool", "agent-tool"];
54
54
 
55
55
  const rank = (value: PermissionClass) => CLASSES[value].decision === "ask" ? 1 : 0;
56
+ /** Select the stricter decision; on ties label the rule by its applied effect, not its preview. */
56
57
  export function strictest(entry: CatalogEntry): PermissionClass {
57
- return entry.applyClass && rank(entry.applyClass) > rank(entry.class) ? entry.applyClass : entry.class;
58
+ return entry.applyClass && rank(entry.applyClass) >= rank(entry.class) ? entry.applyClass : entry.class;
58
59
  }
59
60
 
60
61
  export interface ClaudePermissions { allow: string[]; ask: string[] }
@@ -88,7 +89,8 @@ export function codexRules(project: CatalogEntry[] = []): string {
88
89
  const lines = [
89
90
  "# Managed by agent-tool; regenerate with `agent-tool permissions install --apply`.",
90
91
  "# Codex applies the most restrictive matching rule. Commands with an --apply form",
91
- "# use their stricter class here because prefix rules cannot match trailing flags.",
92
+ "# use their stricter decision because prefix rules cannot match trailing flags.",
93
+ "# Equal decisions group under the applied effect, not the read-only preview.",
92
94
  ];
93
95
  const groups = new Map<string, { prefix: string[]; last: string[]; class: PermissionClass }>();
94
96
  const add = (tokens: string[], value: PermissionClass) => {
@@ -147,7 +147,7 @@ async function start(args: string[]): Promise<number> {
147
147
  // already name another group. A member is ours when it carries this record's id, or
148
148
  // started while the launcher ran (to the second `ps` reports, with a second's margin).
149
149
  const candidates = [...table.values()].filter(row => row.pgid === child.pid);
150
- const survivors = adoptable(candidates, carrying(candidates.map(row => row.pid), "AGENT_TOOL_PROCESS", id), spawnedAt, exitedAt ?? Date.now());
150
+ const survivors = adoptable(candidates, carrying(table, "AGENT_TOOL_PROCESS", id), spawnedAt, exitedAt ?? Date.now());
151
151
  if (survivors.length) {
152
152
  record ??= { schemaVersion: 1, id, label, pid: child.pid, started: "", command: [...args], cwd: process.cwd(), log, startedAt: new Date().toISOString(), keep };
153
153
  record.members = survivors.map(row => ({ pid: row.pid, started: row.started }));
@@ -96,8 +96,12 @@ export function membersOf(record: ProcessRecord, table: Map<number, ProcessRow>,
96
96
  // remembered members still count.
97
97
  if (ours) group = [...table.values()].filter(row => row.pgid === record.pid);
98
98
  else if (!leader) {
99
- const candidates = [...table.values()].filter(row => row.pgid === record.pid);
100
- const tagged = carrying(candidates.filter(row => !known(row)).map(row => row.pid), "AGENT_TOOL_PROCESS", record.id);
99
+ // A child may detach after start's snapshot, then outlive its parent.
100
+ // Its group/ancestry is no longer evidence, but the inherited ownership tag
101
+ // still is. Never adopt by cwd, command, or timestamp, and retain the
102
+ // replaced-leader guard above. Listing this evidence does not write records.
103
+ const candidates = [...table.values()].filter(row => !claimed.has(row.pid));
104
+ const tagged = carrying(table, "AGENT_TOOL_PROCESS", record.id);
101
105
  group = candidates.filter(row => known(row) || tagged.has(row.pid));
102
106
  }
103
107
  // Members seen before, wherever they are now: a descendant that left the group keeps its
@@ -4,28 +4,28 @@
4
4
  import { execFileSync } from "node:child_process";
5
5
  import { readFileSync, readlinkSync } from "node:fs";
6
6
 
7
- export interface ProcessRow { pid: number; ppid: number; pgid: number; started: string; command: string }
7
+ export interface ProcessRow { pid: number; ppid: number; pgid: number; uid: number; started: string; command: string }
8
8
 
9
9
  // One locale and time zone, so start times read the same in every shell that compares them.
10
10
  const C_LOCALE = { ...process.env, LC_ALL: "C", LANG: "C", TZ: "UTC" };
11
11
  // `stat` is one token; `lstart` prints a fixed-width date of five fields, such as `Fri Oct 30 21:43:01 2026`.
12
- const ROW = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+([A-Z][a-z]{2}\s+[A-Z][a-z]{2}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s(.*)$/;
12
+ const ROW = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+([A-Z][a-z]{2}\s+[A-Z][a-z]{2}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s(.*)$/;
13
13
 
14
14
  /**
15
- * Rows of `ps -o pid=,ppid=,pgid=,stat=,lstart=,command=`. Zombies are left out: they have
15
+ * Rows of `ps -ww -o pid=,ppid=,pgid=,uid=,stat=,lstart=,command=`. Zombies are left out: they have
16
16
  * exited, cannot be signalled, and may never be reaped where PID 1 does not reap orphans.
17
17
  */
18
18
  export function parseRows(text: string): ProcessRow[] {
19
19
  return text.split("\n").flatMap(line => {
20
20
  const match = ROW.exec(line);
21
- if (!match || match[4]!.startsWith("Z")) return [];
22
- return [{ pid: Number(match[1]), ppid: Number(match[2]), pgid: Number(match[3]), started: match[5]!.replace(/\s+/g, " "), command: match[6]!.trim() }];
21
+ if (!match || match[5]!.startsWith("Z")) return [];
22
+ return [{ pid: Number(match[1]), ppid: Number(match[2]), pgid: Number(match[3]), uid: Number(match[4]), started: match[6]!.replace(/\s+/g, " "), command: match[7]!.trim() }];
23
23
  });
24
24
  }
25
25
 
26
26
  /** Every live process, by pid. */
27
27
  export function processTable(): Map<number, ProcessRow> {
28
- const text = execFileSync("ps", ["-A", "-o", "pid=,ppid=,pgid=,stat=,lstart=,command="], { encoding: "utf8", env: C_LOCALE, maxBuffer: 64 * 1024 * 1024 });
28
+ const text = execFileSync("ps", ["-A", "-ww", "-o", "pid=,ppid=,pgid=,uid=,stat=,lstart=,command="], { encoding: "utf8", env: C_LOCALE, maxBuffer: 64 * 1024 * 1024 });
29
29
  return new Map(parseRows(text).map(row => [row.pid, row]));
30
30
  }
31
31
 
@@ -86,26 +86,47 @@ export function ancestors(table: Map<number, ProcessRow>, pid = process.pid): Se
86
86
  return found;
87
87
  }
88
88
 
89
- /**
90
- * The processes among `pids` whose environment carries `NAME=value`, as `proc start` sets for
91
- * everything it launches. macOS hides the environment of protected system binaries, which then
92
- * never match.
93
- */
94
- export function carrying(pids: number[], name: string, value: string): Set<number> {
95
- const found = new Set<number>(), entry = `${name}=${value}`;
96
- if (pids.length === 0) return found;
97
- if (process.platform === "linux") {
98
- for (const pid of pids) {
99
- try { if (readFileSync(`/proc/${pid}/environ`, "latin1").split("\0").includes(entry)) found.add(pid); } catch { /* Gone or not ours. */ }
89
+ // Reuse one name's ownership scan for every record in the same immutable table
90
+ // snapshot. A fresh processTable() gets a fresh scan, including during stop polls.
91
+ const environmentCache = new WeakMap<Map<number, ProcessRow>, Map<string, Map<number, string>>>();
92
+
93
+ /** Read tags only from current-user processes, never their argument strings. */
94
+ export function carrying(table: Map<number, ProcessRow>, name: string, value: string): Set<number> {
95
+ let cache = environmentCache.get(table);
96
+ if (!cache) { cache = new Map(); environmentCache.set(table, cache); }
97
+ let values = cache.get(name);
98
+ if (!values) {
99
+ values = new Map();
100
+ cache.set(name, values);
101
+ const uid = process.getuid?.();
102
+ const candidates = [...table.values()].filter(row => uid !== undefined && row.uid === uid);
103
+ const prefix = `${name}=`;
104
+ if (process.platform === "linux") {
105
+ for (const row of candidates) {
106
+ try {
107
+ const entry = readFileSync(`/proc/${row.pid}/environ`, "latin1").split("\0").find(item => item.startsWith(prefix));
108
+ if (entry) values.set(row.pid, entry.slice(prefix.length));
109
+ } catch { /* Gone or not ours. */ }
110
+ }
111
+ } else if (process.platform === "darwin" && candidates.length) {
112
+ try {
113
+ const fields = "pid=,ppid=,pgid=,uid=,stat=,lstart=,command=";
114
+ const selected = candidates.map(row => row.pid).join(",");
115
+ const options = { encoding: "utf8" as const, env: C_LOCALE, stdio: ["ignore", "pipe", "ignore"] as ["ignore", "pipe", "ignore"], maxBuffer: 64 * 1024 * 1024 };
116
+ const expanded = parseRows(execFileSync("ps", ["-E", "-ww", "-o", fields, "-p", selected], options));
117
+ // ps appends the environment after argv. Strip the exact plain argv,
118
+ // and refuse changed identities/argv instead of mistaking them for tags.
119
+ const plain = new Map(parseRows(execFileSync("ps", ["-ww", "-o", fields, "-p", selected], options)).map(row => [row.pid, row]));
120
+ for (const row of expanded) {
121
+ const before = table.get(row.pid), after = plain.get(row.pid);
122
+ if (!before || !after || row.uid !== uid || before.uid !== uid || after.uid !== uid || row.started !== before.started || after.started !== before.started || after.command !== before.command) continue;
123
+ if (!row.command.startsWith(`${before.command} `)) continue;
124
+ const environment = row.command.slice(before.command.length + 1);
125
+ const entry = environment.split(/\s+/).find(item => item.startsWith(prefix));
126
+ if (entry) values.set(row.pid, entry.slice(prefix.length));
127
+ }
128
+ } catch { /* No reliable environment evidence. */ }
100
129
  }
101
- return found;
102
130
  }
103
- try {
104
- const text = execFileSync("ps", ["-E", "-ww", "-o", "pid=,command=", "-p", pids.join(",")], { encoding: "utf8", env: C_LOCALE, stdio: ["ignore", "pipe", "ignore"], maxBuffer: 64 * 1024 * 1024 });
105
- for (const line of text.split("\n")) {
106
- const match = /^\s*(\d+)\s(.*)$/.exec(line);
107
- if (match && ` ${match[2]} `.includes(` ${entry} `)) found.add(Number(match[1]));
108
- }
109
- } catch { /* None readable. */ }
110
- return found;
131
+ return new Set([...values].filter(([, found]) => found === value).map(([pid]) => pid));
111
132
  }
@@ -1,9 +1,9 @@
1
1
  // Generated by scripts/build/bundle-skills.ts; edit skills/*/SKILL.md.
2
2
  export const BUNDLED_SKILLS: Readonly<Record<string, string>> = {
3
- "cross-agent-review": "---\nname: cross-agent-review\ndescription: Review committed branch changes with an independent Claude Code, Codex, or OpenCode reviewer, and repair actionable findings when requested.\n---\n\n# Cross-Agent Review\n\nUse the installed `agent-tool` executable. Read the repository's development and\nreview policy and `agent-tool.json` when present. Keep project validation commands\nand release rules in the repository; do not assume Bun, a workspace layout, or\nparticular CI job names.\n\nSelect a reviewer different from the coordinating agent when possible. Honor a\nrequested reviewer or effort; otherwise use an available independent reviewer.\nRun `agent-tool doctor` if CLI compatibility is uncertain.\n\nRun noisy validation or repair checks through\n`agent-tool run --label <check> -- <command> [args...]`. Read its bounded failure\ntail and use `agent-tool logs <log>` or a targeted search for more evidence.\nKeep `agent-tool review` direct: it retains reviewer stderr in a private local\nlog and displays the complete final review. Never hide, truncate, or substitute\na command success receipt for findings and the final verdict.\n\nReview committed changes with `agent-tool review claude`, `agent-tool review codex`,\nor `agent-tool review opencode`. Optional effort is `low`, `medium`, `high`,\n`xhigh`, or `max`. For a local or non-GitHub repository, add `--base <commit-or-ref>`.\nFor a coordinated GitHub review, pin the fetched base using\n`AGENT_TOOL_REVIEW_BASE_REF`, the base branch name such as `main` (not a\nremote-tracking ref like `origin/main`), and `AGENT_TOOL_REVIEW_BASE_OID`, its\nfetched commit. Fetch from the repository that owns the PR, which can differ\nfrom the checkout's origin.\n\nUse `agent-tool repo context --fetch` to resolve and fetch the PR's own base,\nthen record the exact base and HEAD before reviewing. The tool reviews raw committed\nfiles and excludes worktree edits. A zero exit means a complete review was\nreturned; it does not mean the findings are non-blocking. Read the final\n`VERDICT: BLOCKER|MAJOR|MINOR|SUGGESTION|CLEAN` and the findings. A reviewer\nthat labels findings `[P0]` through `[P3]` uses the same scale: `[P0]` and\n`[P1]` are BLOCKER and MAJOR, `[P2]` and `[P3]` are MINOR and SUGGESTION.\n\nIf a CLI is unavailable, out of credits, or returns an unusable review, try another\navailable reviewer and disclose the fallback. Do not silently convert a failed\nreview into CLEAN or call an in-session review independent.\n\nFor report-only requests, report findings without changing files or history.\nWhen repairs are authorized, address actionable BLOCKER and MAJOR findings,\nvalidate with the repository's relevant checks, commit the repairs, and review\nthe new HEAD. Continue while meaningful progress is possible; report an impasse\nwhen progress requires a material user decision. A repaired commit is unreviewed\nuntil the next review completes. Preserve unrelated work.\n\nBefore reporting success, confirm HEAD still matches the reviewed commit and\nthat an existing PR points to that same commit. If the base moved, refresh it and\nreview again before shipping. Report reviewer, exact base and head IDs, verdict,\nremaining findings, validation, and any repairs performed. This skill does not\nauthorize pushing, opening a PR, or merging unless the user requested that work.\n",
3
+ "cross-agent-review": "---\nname: cross-agent-review\ndescription: Review committed branch changes with an independent Claude Code, Codex, or OpenCode reviewer, and repair actionable findings when requested.\n---\n\n# Cross-Agent Review\n\nUse the installed `agent-tool` executable. Read the repository's development and\nreview policy and `agent-tool.json` when present. Keep project validation commands\nand release rules in the repository; do not assume Bun, a workspace layout, or\nparticular CI job names.\n\nSelect an independent reviewer using a different model/provider from the author\nwhen possible, not merely a different CLI: Hermes and OpenCode can use the same\nmodel as another harness. Hermes can coordinate this skill but is not a `review`\nadapter. Honor a requested reviewer or effort; otherwise use an available\nindependent reviewer.\nRun `agent-tool doctor` if CLI compatibility is uncertain.\n\nRun noisy validation or repair checks through\n`agent-tool run --label <check> -- <command> [args...]`. Read its bounded failure\ntail and use `agent-tool logs <log>` or a targeted search for more evidence.\nKeep `agent-tool review` direct: it retains reviewer stderr in a private local\nlog and displays the complete final review. Never hide, truncate, or substitute\na command success receipt for findings and the final verdict.\n\nReview committed changes with `agent-tool review claude`, `agent-tool review codex`,\nor `agent-tool review opencode`. Optional effort is `low`, `medium`, `high`,\n`xhigh`, or `max`. For a local or non-GitHub repository, add `--base <commit-or-ref>`.\nFor a coordinated GitHub review, pin the fetched base using\n`AGENT_TOOL_REVIEW_BASE_REF`, the base branch name such as `main` (not a\nremote-tracking ref like `origin/main`), and `AGENT_TOOL_REVIEW_BASE_OID`, its\nfetched commit. Fetch from the repository that owns the PR, which can differ\nfrom the checkout's origin.\n\nUse `agent-tool repo context --fetch` to resolve and fetch the PR's own base,\nthen record the exact base and HEAD before reviewing. The tool reviews raw committed\nfiles and excludes worktree edits. A zero exit means a complete review was\nreturned; it does not mean the findings are non-blocking. Read the final\n`VERDICT: BLOCKER|MAJOR|MINOR|SUGGESTION|CLEAN` and the findings. A reviewer\nthat labels findings `[P0]` through `[P3]` uses the same scale: `[P0]` and\n`[P1]` are BLOCKER and MAJOR, `[P2]` and `[P3]` are MINOR and SUGGESTION.\n\nIf a CLI is unavailable, out of credits, or returns an unusable review, try another\navailable reviewer and disclose the fallback. Do not silently convert a failed\nreview into CLEAN or call an in-session review independent.\n\nFor report-only requests, report findings without changing files or history.\nWhen repairs are authorized, address actionable BLOCKER and MAJOR findings,\nvalidate with the repository's relevant checks, commit the repairs, and review\nthe new HEAD. Continue while meaningful progress is possible; report an impasse\nwhen progress requires a material user decision. A repaired commit is unreviewed\nuntil the next review completes. Preserve unrelated work.\n\nBefore reporting success, confirm HEAD still matches the reviewed commit and\nthat an existing PR points to that same commit. If the base moved, refresh it and\nreview again before shipping. Report reviewer, exact base and head IDs, verdict,\nremaining findings, validation, and any repairs performed. This skill does not\nauthorize pushing, opening a PR, or merging unless the user requested that work.\n",
4
4
  "open-pr": "---\nname: open-pr\ndescription: Open a GitHub pull request for the current feature branch with a validated title and a reviewable description.\n---\n\n# Open PR\n\nUse the installed `agent-tool`. Follow repository policy for validation, commits,\nand branch naming. Resolve the repository and default branch with `agent-tool repo context`;\ndo not assume the branch is named main. Preserve unrelated work and finish\nrequested changes and appropriate checks before publishing them.\n\nEnsure the intended commit is on a feature branch and use\n`agent-tool branch push <reviewed-head> --remote <remote>` through normal hooks.\nIt names the destination explicitly and verifies local/remote HEAD afterward;\na changed result needs another review. Respect existing push authorization.\nIf the user only requested a draft description, prepare it without publishing.\n\nWrite a title that satisfies the data-only `agent-tool.json` subject policy.\nDescribe the concrete resulting behavior and relevant validation. When the\nbranch changes package manifests, run\n`agent-tool dependencies diff --base <base-oid>` and name every `newPackages`\nentry in the body with its manifest and version, so a reviewer sees new\nthird-party code however it was added. Put multiline body text in a temporary\nfile, then run:\n\n```sh\nagent-tool pr open 'feat: describe the change' --body-file /path/to/pr-body.txt\n```\n\nOmitting the title uses the latest commit subject. The tool refuses duplicate\nopen PRs and requires the branch to be pushed at its local HEAD. It does not\npush for you. Resolve a fork's head/base identities explicitly if the repository\nuses fork PRs; the initial open helper supports same-repository branches.\n\nReport the created PR link and any remaining validation or review work. Run\n`agent-tool teardown` first, stop what the work started, and report what remains.\n",
5
5
  "reset": "---\nname: reset\ndescription: Return a clean checkout to its updated default branch after confirmed shipping or an explicit checkout cleanup request.\n---\n\n# Reset\n\nThis is checkout cleanup, not a destructive Git reset. Read repository guidance\nfor setup and hooks. Determine the default branch through the repository's\nremote metadata rather than assuming main.\n\nFor post-merge cleanup, preview with\n`agent-tool repo cleanup <PR-number> <shipped-head>`, then run it with `--apply`\nwithin the authorized cleanup scope. It requires a confirmed MERGED PR, refuses\nthe default and `branch.exempt` branches, preserves local edits, checks exact\nlocal/remote feature commits, verifies the merge is on the default branch, and\nfast-forwards that branch. By default it prunes the\nshipped local feature branch and deletes the matching remote branch if present.\nLocal pruning also applies after a squash merge or when GitHub already deleted\nthe remote branch; a missing remote is not evidence that local cleanup is done.\nPass `--keep-branch` when the user asked to preserve both feature refs.\nRead the receipt's completed steps if a later operation fails; do not claim full\ncleanup from a partial result. Verify the final branch and clean worktree, and\ncheck that `git branch --list <feature-branch>` is empty unless keeping the branch.\nIf the feature branch is checked out in another worktree, shared cleanup reports\na partial failure after updating the default branch and leaves both feature refs\nin place; preserve that worktree and report the remaining cleanup.\n\nRemote branch deletion uses GitHub REST after checking the live ref to avoid\npre-push hooks. The API has no atomic expected-OID delete guard; the check and\ndeletion retain a small concurrent-update window. Fork PRs require manual remote\nhandling and are refused by the shared cleanup command.\nUse a repository's stronger cleanup procedure when required, including atomic\nremote deletion or additional local verification; do not weaken it to use the\nshared command. The same local pruning and keep-branch outcomes still apply.\n\nFor an explicit checkout cleanup without a merged PR, preserve all feature refs:\ncheck worktree/index, resolve the default branch with `repo context`, fetch it,\nswitch, and update by fast-forward only. Never discard or automatically stash\nedits, force a diverged default branch, or infer shipping from Git ancestry after\na squash merge.\n\nRun `agent-tool teardown` and stop what the finished work left running\n(`teardown --apply` for recorded processes); report anything that remains.\nRun the repository's documented dependency/hook setup if required. When copied\nhooks are configured, use `agent-tool hooks check` and `hooks install --apply`;\ninspect unmanaged hooks before an explicit `--adopt`, which backs them up.\nDo not assume a package manager. Report final branch, commit, whether the shipped\nlocal branch was pruned or preserved, and unfinished cleanup or setup.\n",
6
6
  "ship-pr": "---\nname: ship-pr\ndescription: Complete an authorized commit, independent review, repair, PR, CI, and squash-merge workflow for a GitHub repository.\n---\n\n# Ship PR\n\nUse the installed `agent-tool`. Read repository guidance and policy first;\nvalidation, versioning, package management, and hook bodies remain project\nconcerns. Use shared branch, hook, check, cleanup, and release commands where\nconfigured; keep application-specific commands in repository guidance. Shipping authorizes this workflow within the user's stated scope.\nPreserve unrelated edits and honor requested stopping points such as open-PR\nonly, report-only review, or keeping the feature branch.\n\nUse `agent-tool run --label <check> -- <command> [args...]` for noisy setup,\nvalidation, builds, and hook-running Git commands. It preserves exit status and\ncomplete private logs, prints one success receipt, and shows a bounded failure\ntail. Inspect more with `agent-tool logs <log> --max-bytes 8192 --tail-lines 80`\nor a targeted search; never dump an entire suite log or pipe checks into `tail`.\nThe receipt proves process completion, not absence of warnings: inspect required\nwarning, coverage, timing, or artifact evidence before accepting a check. Keep\nmachine-readable receipts and review findings visible by running `versions\nprepare`, `review`, and other commands whose output drives a decision directly.\nPrefer documented compact test reporters when they preserve required evidence;\ndo not reduce test scope, skip checks, silence warnings, or mask failures.\nStart servers, watchers, and other long-running processes with\n`agent-tool proc start --label <name> -- <command>`, never a backgrounded shell\ncommand, so they can be found and stopped.\n\n1. Finish the requested change on a feature branch. Use\n `agent-tool branch create <branch> --base <base-branch> --remote <remote>`\n from a clean checkout; it fetches and pins the base, checks naming policy and\n collisions, and creates without a local upstream. Use `branch check` with\n explicit destination names in CI or hooks.\n Validate it with appropriate project checks and commit only the intended\n files.\n2. Use `agent-tool repo context --fetch` to resolve the PR's base repository\n and branch, or the default branch when no PR exists, and fetch its exact base.\n Record the returned base/head identities. Integrate the fetched base\n using the repository's normal workflow, resolving conflicts without losing\n user work. If project policy versions packages, prepare versions against the\n pinned base after every integration or repair and before each review, using\n the project's commands or `agent-tool versions prepare <base-oid>`, which\n commits bumped manifests and the refreshed lockfile.\n `agent-tool versions resolve-conflicts` resolves conflicts confined to\n version fields. Review the resulting committed HEAD with an independent\n reviewer using the bundled cross-agent-review skill. Repair blocking findings\n and review every changed HEAD again. Review failure blocks shipping.\n3. Run `agent-tool branch push <reviewed-head> --remote <remote>` through\n normal hooks; it verifies local and remote identity afterward. If a hook\n changed content or HEAD, review that result.\n Open the PR using the open-pr skill, or update the existing PR as needed.\n4. Allow configured review bots time to respond, then use\n `agent-tool pr threads list` to fetch review threads and all comments. Follow repository policy for feedback: fix valid findings,\n reply with `pr threads reply <comment-id>` (body file or stdin), and use\n `pr threads resolve <thread-id>` only for fully addressed findings.\n Validate, commit, push, and independently review every feedback or CI repair.\n Wait with `agent-tool pr checks --wait --head <reviewed-head>`, whose limit\n defaults to `agent-tool.json` `merge.checksTimeoutSeconds`; when a job\n fails, read its log with `agent-tool pr logs --head <reviewed-head>`. When it\n stops with `behind: true`, the base moved: cancel the stale head's CI with\n `agent-tool pr cancel-runs --head <reviewed-head>`, integrate the reported\n base, validate, review, push the new head, and wait on it. Then run\n `agent-tool pr preflight <reviewed-head> <base-branch> <reviewed-base>` to\n recheck CI, live base, PR identity, local state, and explicit version policy.\n A changed base requires integration, validation, and another review. When\n versions are managed, `agent-tool versions check <base-oid>` must pass for the\n live base. Use the squash-merge skill to merge the exact reviewed HEAD.\n5. After confirmed MERGED, follow the reset skill: run\n `agent-tool repo cleanup <PR-number> <reviewed-head> --apply` to return to the\n updated default branch and prune the shipped feature branch, honoring\n stronger repository cleanup rules. Never prune with raw `git branch -D` or\n `git push --delete`: the cleanup command checks the merge and both refs\n first, and harness command guards prompt for raw force deletes. If the user requested keep-branch, preserve\n both feature refs. Check the cleanup receipt and verify the local feature ref\n is absent unless preserved; report incomplete cleanup explicitly. Apply\n documented dependency/hook setup.\n6. When the project publishes npm packages, use `agent-tool release verify\n <package> <version> <merge-oid> --workflow <publish-workflow>` to verify the\n exact workflow and tarball, allowing registry propagation time. When the\n project deploys the merged branch, verify that deployment and documented\n live checks. Report deployment failures\n separately from a successful merge; follow project policy for repairs.\n\nBefore the final report, run `agent-tool teardown` in each repository this work\ntouched. Stop what the work started (`teardown --apply`, `proc stop`), stop any\nuntracked process it started, and remove worktrees it added. Report what\nremains and why; the work is not done while teardown fails for anything it\nstarted.\n\nNever infer a clean review from process success alone or carry a review across\ncontent changes. Do not weaken configured CI policy to get a merge through.\nIf a step fails, preserve useful intermediate work and report the concrete\nremaining issue. Report PR link, merged commit, review verdict, validation, final\ncheckout state, the teardown result, and whether the shipped local branch was\npruned or preserved.\n",
7
7
  "squash-merge": "---\nname: squash-merge\ndescription: Squash-merge a reviewed GitHub pull request while binding the mutation to the reviewed commit and checking CI.\n---\n\n# Squash Merge\n\nUse the installed `agent-tool` and repository merge policy. Merge only within\nthe user's authorized scope. Have the exact reviewed HEAD, base commit, and base\nbranch available, and read the verdict and unresolved findings. BLOCKER or MAJOR\nfindings require repair and a new review before merging.\nFollow the repository's review-bot policy and require all actionable blocking\nreview feedback to be addressed before merging.\n\nWait with `agent-tool pr checks --wait --head <reviewed-head>`, which waits for\nchecks to appear and for every workflow/job/event entry to finish, and stops\nearly with `behind: true` once the live base moves past the head; then cancel\nthat head's runs with `agent-tool pr cancel-runs --head <reviewed-head>` and\nintegrate, validate, review, and push again. Run\n`agent-tool pr preflight <reviewed-head> <base-branch> <reviewed-base>` immediately\nbefore merging. It checks local and PR identity, live base, ancestry, explicit\nversion policy, merge readiness, configured strict rulesets, and passing checks.\nIf an identity changed, refresh, integrate, validate, and review again. Read\nunresolved feedback with `pr threads list` and follow project review policy.\n\nUse a title that satisfies `agent-tool.json` and pass both reviewed HEAD and\nbase branch:\n\n```sh\nagent-tool pr merge 'feat: describe the change' <reviewed-head-oid> <base-branch>\n```\n\nThe helper enforces HEAD atomically through GitHub's `expectedHeadOid`, checks\nCI, rejects queued or automatic merges, and creates a subject-only squash with\nthe PR reference. GitHub has no atomic expected-base argument in this mutation;\nrepository branch protection must enforce any required base freshness. With\n`merge.requireStrictBaseFreshness`, the helper refuses to merge unless an\nactive strict status ruleset that the actor cannot bypass protects the base. Do\nnot retarget the PR concurrently with merging.\n\nConfirm GitHub reports MERGED before any cleanup. A failed or uncertain mutation\nis not evidence of a merge. Preserve the branch on failure. After a confirmed\nmerge, perform checkout cleanup only when requested or as part of an authorized\nshipping workflow, with `agent-tool repo cleanup <PR-number> <reviewed-head>\n--apply` rather than raw `git branch -D`. Report the PR and merge result.\n",
8
- "update-dependencies": "---\nname: update-dependencies\ndescription: Update repository dependencies, runtimes, tools, and CI pins with compatibility checks and safe infrastructure previews, then ship through review, CI, merge, and release verification unless a narrower scope is requested.\n---\n\n# Update Dependencies\n\nUse the repository's package managers and installed `agent-tool`. Read `AGENTS.md`,\nREADME, existing dependency-maintenance notes, linked setup/release documents,\nand repository-owned upgrade skills before editing. Follow their hooks, version\npolicy, checks, and requested stopping point.\nUpdate applicable dependencies throughout the repository, including workspaces\nand examples. When asked to perform dependency updates, invoke the installed\n[`ship-pr`](../ship-pr/SKILL.md) to complete review, PR, CI, and merge, then verify\nthe repository's normal merge-triggered publication or deployment. An explicit\nnarrower scope, such as inventory-only, local changes, or open-PR-only, takes\nprecedence. This default does not authorize standalone infrastructure applies,\ndeployments, or global tool changes. Default dependency-update merge subject:\n`chore: update dependencies`, subject to repository title policy and any explicit\nuser subject.\n\nRun noisy installs, checks, tests, and builds with\n`agent-tool run --label <check> -- <command> [args...]` to retain full private\nlogs and return the original status with compact output. Inspect bounded excerpts\nusing `agent-tool logs <log>` and targeted searches for warnings and evidence;\na successful receipt does not establish a warning-free upgrade. Keep structured\naudit/plan results, deployment previews, and review findings directly visible\nwhen they drive a safety decision. Do not dump full suite logs into context,\nsilence warnings at their source, or change check scope to reduce output.\n\n## Safety before execution\n\n**Always dry-run before any infrastructure apply or deployment. Never destroy,\nreplace, or recreate a resource during an upgrade.** This includes Terraform,\nOpenTofu, Wrangler, Ansible, other infrastructure tools, and effects triggered by\ninstallation, tests, hooks, Git push, opening a PR, CI, or merging. Greenfield\nprojects and approval to ship do not waive this rule.\n\nBefore running project commands or pushing, trace their scripts, hooks, workflow\ntriggers, reusable workflows, and deployment commands. A dry-run flag alone is\ninsufficient: inspect tasks, provisioners, plugins, external programs, and actions\nthat can mutate despite preview mode. Avoid such a preview unless its effects\nare proven read-only. Do not run destroy/delete, taint/replace, state removal,\nbackend migration, forced database reset, or an automatic \"repair\" that bypasses\nthe rule. Do not hide destruction with targeting, disabled refresh, ignored\nchanges, new resource identities, or weakened policy.\n\nPreview with the proposed versions and complete configuration for each affected\nenvironment, using its real backend/workspace/account, variables, inventory, and\nresource identities. An empty local state, mocked credentials, validate-only run,\nor bundle build cannot establish safety for production. Check the current baseline\nas well when needed to distinguish existing drift from the upgrade. Keep state,\nbinary plans, JSON plans, and secret-bearing diffs out of Git and public reports.\n\nIf a preview proposes deletion or replacement, fails, is incomplete, omits\naffected resources, or cannot establish safety, skip that dependency and its\ncoupled migrations. Restore only this task's edits to the affected compatibility\ngroup, retain independent safe upgrades, and record the reason and affected\nresources. Never apply to find out. A preview is not deployment authorization.\n\nBefore an authorized apply/deploy, verify the exact validated commit, toolchain,\nconfiguration, environment, and current state still match the preview. Re-preview\nafter relevant changes or drift. CI must enforce the same order and stop on an\nunsafe preview before mutation; a successful local preview does not excuse an\nunguarded push- or merge-triggered deployment. Establish a narrowly scoped guard\nwhen authorized and reviewable, or hold the affected upgrade before triggering\nthat workflow. Do not disable protections to ship. A plan must be inspected before\nthe operation; inspecting deployment logs afterward is too late.\nAn unsafe workflow triggered on every branch push also blocks pushing otherwise\nindependent upgrades until that trigger has a proven safe guard.\n\n### Terraform and OpenTofu\n\nRead core, provider, and module upgrade guides, including intermediate major\nmigrations. Regenerate provider locks with the pinned CLI, preserving supported\nplatform hashes. Backend initialization/migration is also subject to the safety\ngate. Inspect external data programs and provisioners before planning.\n\nUse a full refreshed saved plan with locking and the real variables, for example\n`terraform plan -input=false -out=<private-plan>`; if using `-detailed-exitcode`,\n0 and 2 are success, 1 is failure. Inspect `terraform show -json <private-plan>`.\nRun `agent-tool dependencies check-terraform-plan <private-plan.json>` to check\nsupported JSON resource actions. It fails for every action containing `delete`,\nincluding both replacement orders, unknown actions, incomplete/deferred plans,\nfailed checks, or opaque provider action invocations. It prints actions and\ndiagnostics without resource attribute values; it does not prove that a plan is\nfresh, comprehensive, authentic, or that provider code has no hidden effects.\nOlder/unsupported plan formats that cannot establish completeness are skipped.\nOpenTofu currently omits `complete` in its JSON format and is rejected by this\nhelper; report that limitation rather than adding a synthetic completeness flag.\nAlso inspect resource identity, drift, warnings, provider semantics, and any\neffects outside resource actions. For an authorized apply, consume the exact\napproved saved plan, not an uninspected newly generated plan.\n\nSource: [Terraform JSON plan format](https://developer.hashicorp.com/terraform/internals/json-format).\n\n### Wrangler and other deployment tools\n\nUse the proposed pinned Wrangler for `wrangler deploy --dry-run` with the same\nbuild/configuration/environment used by deployment. This checks bundling, not a\ncomplete remote resource plan. Separately verify account, worker name, routes,\nbindings and resource IDs against the existing deployment and read migration\nsemantics. Review Durable Object deleted/renamed classes, D1 schema/data\nmigrations, storage resources, queues, containers, and deployment integrations.\nNever infer \"no destruction\" from a successful bundle. Resource removals,\nreplacement, or unprovable remote effects skip the affected group. Apply the\nequivalent preview and identity checks to other tools rather than assuming they\nshare Terraform's guarantees.\n\nDo not assume `wrangler d1 migrations list --remote` is read-only. The\nimplementation inspected for this skill initializes migration tracking with\n`CREATE TABLE IF NOT EXISTS`; verify the pinned Wrangler version before using\nthe command. Verify database identity and compare migration files with the\ndatabase's configured migration tracking table through audited read-only\nqueries. A missing table, pending migration, or unverifiable schema/data\neffects hold the affected upgrade/deployment group. Never initialize schema\nmerely to establish preview safety.\n\nWrangler runs a custom `build.command` again during deployment. Use supported\nconfiguration referencing the same validated artifacts for both preview and\ndeployment, preventing another unvalidated build before upload. Bind the\nartifacts, configuration, and pinned tool to the authorized commit, and recheck\ntheir identities before upload and after relevant changes. Hold the affected\ngroup if the pinned Wrangler cannot upload the validated output without\nrebuilding it, or if the upload cannot be proven to match the preview.\n\nSources: [Wrangler command documentation](https://developers.cloudflare.com/workers/wrangler/commands/),\n[D1 migration listing implementation](https://github.com/cloudflare/workers-sdk/blob/e3d186776fcc2e4b5cc4cfd7155591778a375957/packages/wrangler/src/d1/migrations/list.ts),\n[migration table initialization](https://github.com/cloudflare/workers-sdk/blob/e3d186776fcc2e4b5cc4cfd7155591778a375957/packages/wrangler/src/d1/migrations/helpers.ts),\nand [Wrangler custom builds](https://developers.cloudflare.com/workers/wrangler/custom-builds/).\n\n### Ansible\n\nCheck controller Python, ansible-core, collections/roles, and target Python support\ntogether; read porting guides. Inspect all tasks and invoked scripts before\n`ansible-playbook --check --diff` with the real inventory and variables.\n`check_mode: false` forces mutation even with `--check`; unsupported modules and\nconditions based on registered results can omit effects. Inspect deletion,\nrecreation, service/data reset, and package removal semantics beyond the diff.\nSkipped or opaque tasks are a safety gap, not a clean preview. Redact diffs.\n\nSource: [Ansible check mode limits](https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_checkmode.html).\n\n## Inventory and choose compatible targets\n\nRecord each dependency source, current resolved version, latest stable candidate,\nselected target, compatibility constraints, upstream migration links, and checks\nin the run ledger described under Report.\nQuery authoritative registries, official releases, support matrices, and migration\nguides at execution time; do not treat remembered versions or prereleases as\nlatest stable. Inspect runtime requirements, peer dependencies, ABI/API changes,\nplatform availability, maintained release lines, and changed defaults, including\nsecurity, privacy/telemetry, data retention, and postinstall/native behavior.\nCheck transitive dependencies' peer/runtime constraints as well as direct ones. Unsupported\nor unpublished targets stay pinned with an explanation.\n\nInventory manifests, locks, tool configuration, CI/reusable actions, containers,\nscripts, deployment files, and executable documentation. Search duplicate version\npins and aliases, not just filenames; exclude vendored/generated snapshots unless\nthe repository owns their update process. Examples of sources to inspect:\n\n| Ecosystem | Sources and compatibility group |\n| --- | --- |\n| JavaScript/TypeScript | All package.json dependency fields, overrides/resolutions, workspace catalogs, packageManager/engines, npm/Bun/pnpm/Yarn locks, Node/Bun pins, compiler and lint/build/test plugins |\n| Toolchains | mise.toml/.mise.toml, mise.lock, .tool-versions, runtime version files, tool URLs/checksums, CI pins and container images; include every tool declared by mise, not just mise itself |\n| Python/Ansible | pyproject/requirements, uv/Poetry/pip locks, Python pins, ansible-core, collection/role requirements, lint plugins and managed host constraints |\n| Ruby/iOS | .ruby-version, Gemfile/lock, Bundler, CocoaPods, Podfile/lock, podspecs, Swift packages and Xcode/iOS deployment requirements |\n| Infrastructure | Terraform/OpenTofu CLI constraints, provider locks/constraints, module versions/Git refs, Wrangler and Cloudflare integration dependencies |\n| CI/system/native | Actions/reusable workflow refs, Docker tags/digests, OS images/packages, Rust/Cargo toolchain and locks, Go modules, Gradle/AGP/Kotlin/JDK and other repository-specific manifests |\n\nBuild connected compatibility groups before editing. Select the newest supported\ncombination, which can be older than an individual package's latest. Upgrade the\nframework first or keep its compatible toolchain pinned; do not force peer\nresolution, drop compatibility checks, or add blanket overrides to make installs\npass. Preserve source pin policy (exact version, digest, full Git SHA) and resolve\nimmutable pins from verified upstream releases. Preserve intentional runtime\nminimums unless migration requires changing the support contract.\n\nFor React Native, use its version-specific upgrade guide/template diffs and\nrepository upgrade skills. Couple React and its renderer, native modules,\nMetro/Babel, CLI, codegen, Hermes/New Architecture, CocoaPods/Bundler/Ruby,\nXcode/iOS deployment targets, and Android Gradle/AGP/Kotlin/JDK/SDK/NDK. For Expo,\nuse the chosen SDK's supported React Native/dependency matrix and its dependency\nchecks. Do not independently update framework-managed pods or generated native\nfiles; use the framework's regeneration workflow and prove iOS/Android builds.\nPermissive peer ranges alone do not prove native renderer or toolchain compatibility;\ncheck the framework's actual supported versions and native implementation.\nAn unavailable native toolchain is a reported validation limit; do not claim the\ngroup verified. See [React Native upgrading](https://reactnative.dev/docs/upgrading).\n\nCheck developer tools' own dependencies too: a compiler release without its old\nprogrammatic API may break test/lint runners that import it even when `tsc` passes.\nAction runtime upgrades can require newer self-hosted runners; preserve workflow\npermissions, action inputs/outputs, caching, artifact semantics, and job names.\n\nCheck current advisories with read-only ecosystem audits against the baseline and\nproposed lockfiles, using the repository's configured registry/advisory sources.\nDo not send private dependency metadata to an unconfigured or unauthorized service;\nreport the resulting coverage limit. Cover direct and resolved transitive dependencies,\nincluding native/platform-specific binaries. Latest direct packages can still\npin vulnerable transitive versions. Trace each finding to its owning dependency,\nverify affected versions, runtime conditions, and remediation in the maintainer's\nadvisory and fixed release. When the owner's range permits a fixed version, use a\ntargeted refresh with the lockfile's owning manager and preserve unrelated\nresolutions; otherwise prefer a supported owner upgrade.\nIf the owner still pins a vulnerable version, a narrowly scoped security patch\noverride may be used only with API/ABI/runtime compatibility evidence and focused\nregression checks of the actual affected integration. Document its rationale and\nremoval condition; a passing install or bundle alone is insufficient. Do not run\nblind audit fixes or use blanket overrides to bypass compatibility constraints.\nReport unresolved advisories and unavailable audit coverage accurately; current\ndirect pins or a partial clean audit do not\nestablish that the whole dependency graph is safe.\n\n## Migrate and validate\n\nCapture relevant baseline checks and warnings first, after screening their side\neffects. Update one compatibility group at a time. Read the upstream instructions\nfor every breaking step; implement required source/config/schema migrations and\nremoved API replacements. Use the manager that owns each lockfile, on the selected\nruntime, and inspect the resulting diff. For Bun lockfiles, change declared\nversions with `agent-tool bun update <name[@version]>` and drop packages with\n`agent-tool bun remove <name>`; both use the pinned Bun and refuse to add a\npackage. A new dependency goes through `agent-tool bun add <name@version>`, which\nasks for approval; never introduce one by editing `package.json` and\nreinstalling. Refresh transitive dependencies through\ntheir owning direct dependency; do not hand-edit integrity hashes or lock entries.\nReview intentional patches, overrides, and pins before removal; retain them until\nupstream fixes and regression checks demonstrate they are unnecessary.\nAvoid unrelated global installations or environment changes. Use task-local or\nrepository-scoped tools for testing changed runtime pins.\nFor published packages, also validate tarballs and consumer installs with their\ndocumented supported package managers, including manifest/override syntax and\nruntime requirements. A successful owning-manager install does not prove that\npublished metadata works for those consumers.\n\nRun the repository's setup/hooks, frozen installs, lint, typechecks, tests, builds,\npackage/platform smoke checks, and relevant safe integration previews. Resolve\nupgrade-related deprecation warnings at their source. Do not suppress warnings,\nweaken tests, or disable checks; report remaining upstream/pre-existing warnings\nwith evidence and keep a target pinned when migration cannot be completed.\nAdd focused regression coverage when a migration changes behavior; avoid tests\nthat merely assert version strings. Re-run checks after repairs. Report checks\nblocked by missing credentials, platforms, or external services accurately.\nRe-audit the final resolved graph after migrations and repairs.\n\nConfirm every inventory row is upgraded, already current, intentionally constrained,\nor skipped with a concrete reason. Do not present an unresolved group as complete.\nWhen upgrading agent-tool itself, install its managed skills with the same harness\nscope and commit the ownership manifest; never edit generated managed skill copies.\n\nWhen changes depend on another repository's release, ship the producer first and\nconfirm publishing and registry availability before pinning the consumer; include\nits exact version/commit and API migration notes. Independent groups may proceed.\nComplete the installed `ship-pr` workflow within the requested scope, including\nindependent review of every changed HEAD, required CI and review feedback, merge,\nand verification of the merge's normal publication or deployment. Do not stop at\nan open PR unless the user requested that stopping point or a concrete blocker\nprevents continuing. Check deployment safety again before push/open/merge;\nshipping defaults do not waive the preview gates above.\n\n## Report\n\nKeep run-specific evidence in the upgrade PR body or a linked PR comment: the\ninventory and old/selected versions, completed migrations, validation and warnings,\nbaseline/final audits and coverage limits, preview environment/commit/tool versions\nand safe action summary, and skipped/constrained dependencies with reasons. Include\nsecurity overrides, their rationale and removal condition, and unresolved advisories.\nIf the user requested a scope that stops before a PR, provide this ledger in the\ntask report and honor that limit. For a sweep, keep one entry per repository,\nincluding unready and unchanged checkouts, and use an existing tracking issue only\nwhen explicitly requested. Never create an issue merely to store upgrade evidence.\n\nKeep durable repository guidance focused on what the next upgrade needs: current\ncompatibility constraints, intentional pins/overrides, unresolved advisories, and\nreusable repository-specific procedures. Prefer the existing maintenance document;\notherwise create `docs/dependency-upgrades.md` (or the repository's equivalent)\nonly when there is substantive guidance to preserve. For each constraint, record\nits rationale, upstream advisory/migration/issue link or originating PR, and the\ncondition and check for removing or revisiting it. Do not copy version inventories,\naudit output, validation/preview receipts, or dated run journals into that document.\n\nOn every upgrade, reconcile the existing maintenance document against the final\ndependency graph and checks: update constraints in place, remove resolved entries,\nand link the PR for historical evidence. Keep the root README focused on purpose,\nsetup, supported requirements, and normal usage, with a concise maintenance link\nwhen needed. Keep dependency-maintenance guidance in `AGENTS.md` to a short\nrouting rule and link; do not accumulate upgrade reports in either entrypoint\nor duplicate the maintenance document.\n\nFor shipping, report the branch, reviewed commit/verdict, PR, merge, publish/deploy\nresult, and final checkout state. Keep full logs private; never publish credentials,\nstate, or plan contents in any of these locations.\n"
8
+ "update-dependencies": "---\nname: update-dependencies\ndescription: Update dependencies or update everything in a repository, including runtimes, tools, and CI pins, with compatibility checks and safe previews; ship through review, CI, merge, and release verification unless a narrower scope is requested.\n---\n\n# Update Dependencies\n\nUse the repository's package managers and installed `agent-tool`. Read `AGENTS.md`,\nREADME, existing dependency-maintenance notes, linked setup/release documents,\nand repository-owned upgrade skills before editing. Follow their hooks, version\npolicy, checks, and requested stopping point.\nUpdate applicable dependencies throughout the repository, including workspaces\nand examples. When asked to perform dependency updates, invoke the installed\n[`ship-pr`](../ship-pr/SKILL.md) to complete review, PR, CI, and merge, then verify\nthe repository's normal merge-triggered publication or deployment. An explicit\nnarrower scope, such as inventory-only, local changes, or open-PR-only, takes\nprecedence. This default does not authorize standalone infrastructure applies,\ndeployments, or global tool changes. Default dependency-update merge subject:\n`chore: update dependencies`, subject to repository title policy and any explicit\nuser subject.\n\nRun noisy installs, checks, tests, and builds with\n`agent-tool run --label <check> -- <command> [args...]` to retain full private\nlogs and return the original status with compact output. Inspect bounded excerpts\nusing `agent-tool logs <log>` and targeted searches for warnings and evidence;\na successful receipt does not establish a warning-free upgrade. Keep structured\naudit/plan results, deployment previews, and review findings directly visible\nwhen they drive a safety decision. Do not dump full suite logs into context,\nsilence warnings at their source, or change check scope to reduce output.\n\n## Checkout setup\n\nThis shared workflow also handles natural-language requests to \"update everything\".\nIf the repository owns an `update-everything` wrapper, read and follow its extra\nrequirements; do not create an alias or overwrite that skill. The workflow works\nfrom a standalone checkout with no workspace coordinator.\n\nAfter a fetch, branch integration, or dependency pin change, check that the local\n`agent-tool --version` matches the repository's declared pin before trusting its\ncommands or generated skills. A stale CLI can reject newer configuration keys.\nAfter screening install scripts and hooks as below, run the documented frozen\ninstall with the repository's pinned runtime. For Bun projects, an already working\nCLI can run `agent-tool bun install`; if it is missing or cannot parse the new\nconfig, run the documented package-manager install directly. A known working\nexecutable may capture it with `agent-tool --repo <checkout> run --label setup --\n<install-command> [args...]`. Do not fetch an unpinned CLI, alter global tools, or\nassume a sibling workspace exists. Verify the installed version afterward; if it\nstill differs, report the mismatch rather than deleting arbitrary dependencies.\n\nAfter upgrading agent-tool, run the repository's skill/permission sync commands\n(or `agent-tool skills install --harness <existing-scope> --apply`), preserve its\nharness scope, and commit ownership manifests with generated files. Hermes reads\nthe shared `.agents/skills` in a trusted project; the installer does not grant\ntrust. If discovery is unavailable, read those files directly rather than silently\nusing a stale global copy.\n\n## Nonblocking execution\n\nUse the harness's managed background execution for long finite checks, reviews,\npush hooks, CI waits, and release verification; request completion notification\nwhen available and retain its process handle plus agent-tool's log/receipt path.\nRun `run-group` and reviews directly so their reports remain visible. A started\nprocess is not a passed check: read its final status and required evidence before\nshipping, and do not repeatedly poll when completion events are available.\nWithout harness background support, `proc start --label <name> -- agent-tool run\n--label <check> -- <command> [args...]` records the outer process and captures\nits output; inspect `proc list` and `logs` plus the inner run receipt to establish\ncompletion and exit status. An exited process record alone does not prove success.\nUse `proc stop`/`teardown` for owned leftovers; never stop another session's work.\nThese commands need no matrix workspace or global configuration change.\n\nKeep independent compatibility groups moving while a producer release or an\nexternal gate is pending. Only final adoption of that exact published release\nmust wait. Bound CPU/process-heavy concurrency to the machine's available budget;\na failed full suite needs diagnosis and a full-scope rerun, not larger timeouts,\nfewer assertions, or a focused retry presented as suite recovery. Background\nexecution never bypasses an approval, a deployment preview, or exact-head review.\n\n## Safety before execution\n\n**Always dry-run before any infrastructure apply or deployment. Never destroy,\nreplace, or recreate a resource during an upgrade.** This includes Terraform,\nOpenTofu, Wrangler, Ansible, other infrastructure tools, and effects triggered by\ninstallation, tests, hooks, Git push, opening a PR, CI, or merging. Greenfield\nprojects and approval to ship do not waive this rule.\n\nBefore running project commands or pushing, trace their scripts, hooks, workflow\ntriggers, reusable workflows, and deployment commands. A dry-run flag alone is\ninsufficient: inspect tasks, provisioners, plugins, external programs, and actions\nthat can mutate despite preview mode. Avoid such a preview unless its effects\nare proven read-only. Do not run destroy/delete, taint/replace, state removal,\nbackend migration, forced database reset, or an automatic \"repair\" that bypasses\nthe rule. Do not hide destruction with targeting, disabled refresh, ignored\nchanges, new resource identities, or weakened policy.\n\nPreview with the proposed versions and complete configuration for each affected\nenvironment, using its real backend/workspace/account, variables, inventory, and\nresource identities. An empty local state, mocked credentials, validate-only run,\nor bundle build cannot establish safety for production. Check the current baseline\nas well when needed to distinguish existing drift from the upgrade. Keep state,\nbinary plans, JSON plans, and secret-bearing diffs out of Git and public reports.\n\nIf a preview proposes deletion or replacement, fails, is incomplete, omits\naffected resources, or cannot establish safety, skip that dependency and its\ncoupled migrations. Restore only this task's edits to the affected compatibility\ngroup, retain independent safe upgrades, and record the reason and affected\nresources. Never apply to find out. A preview is not deployment authorization.\n\nBefore an authorized apply/deploy, verify the exact validated commit, toolchain,\nconfiguration, environment, and current state still match the preview. Re-preview\nafter relevant changes or drift. CI must enforce the same order and stop on an\nunsafe preview before mutation; a successful local preview does not excuse an\nunguarded push- or merge-triggered deployment. Establish a narrowly scoped guard\nwhen authorized and reviewable, or hold the affected upgrade before triggering\nthat workflow. Do not disable protections to ship. A plan must be inspected before\nthe operation; inspecting deployment logs afterward is too late.\nAn unsafe workflow triggered on every branch push also blocks pushing otherwise\nindependent upgrades until that trigger has a proven safe guard.\n\n### Terraform and OpenTofu\n\nRead core, provider, and module upgrade guides, including intermediate major\nmigrations. Regenerate provider locks with the pinned CLI, preserving supported\nplatform hashes. Backend initialization/migration is also subject to the safety\ngate. Inspect external data programs and provisioners before planning.\n\nUse a full refreshed saved plan with locking and the real variables, for example\n`terraform plan -input=false -out=<private-plan>`; if using `-detailed-exitcode`,\n0 and 2 are success, 1 is failure. Inspect `terraform show -json <private-plan>`.\nRun `agent-tool dependencies check-terraform-plan <private-plan.json>` to check\nsupported JSON resource actions. It fails for every action containing `delete`,\nincluding both replacement orders, unknown actions, incomplete/deferred plans,\nfailed checks, or opaque provider action invocations. It prints actions and\ndiagnostics without resource attribute values; it does not prove that a plan is\nfresh, comprehensive, authentic, or that provider code has no hidden effects.\nOlder/unsupported plan formats that cannot establish completeness are skipped.\nOpenTofu currently omits `complete` in its JSON format and is rejected by this\nhelper; report that limitation rather than adding a synthetic completeness flag.\nAlso inspect resource identity, drift, warnings, provider semantics, and any\neffects outside resource actions. For an authorized apply, consume the exact\napproved saved plan, not an uninspected newly generated plan.\n\nSource: [Terraform JSON plan format](https://developer.hashicorp.com/terraform/internals/json-format).\n\n### Wrangler and other deployment tools\n\nUse the proposed pinned Wrangler for `wrangler deploy --dry-run` with the same\nbuild/configuration/environment used by deployment. This checks bundling, not a\ncomplete remote resource plan. Separately verify account, worker name, routes,\nbindings and resource IDs against the existing deployment and read migration\nsemantics. Review Durable Object deleted/renamed classes, D1 schema/data\nmigrations, storage resources, queues, containers, and deployment integrations.\nNever infer \"no destruction\" from a successful bundle. Resource removals,\nreplacement, or unprovable remote effects skip the affected group. Apply the\nequivalent preview and identity checks to other tools rather than assuming they\nshare Terraform's guarantees.\n\nDo not assume `wrangler d1 migrations list --remote` is read-only. The\nimplementation inspected for this skill initializes migration tracking with\n`CREATE TABLE IF NOT EXISTS`; verify the pinned Wrangler version before using\nthe command. Verify database identity and compare migration files with the\ndatabase's configured migration tracking table through audited read-only\nqueries. A missing table, pending migration, or unverifiable schema/data\neffects hold the affected upgrade/deployment group. Never initialize schema\nmerely to establish preview safety.\n\nWrangler runs a custom `build.command` again during deployment. Use supported\nconfiguration referencing the same validated artifacts for both preview and\ndeployment, preventing another unvalidated build before upload. Bind the\nartifacts, configuration, and pinned tool to the authorized commit, and recheck\ntheir identities before upload and after relevant changes. Hold the affected\ngroup if the pinned Wrangler cannot upload the validated output without\nrebuilding it, or if the upload cannot be proven to match the preview.\n\nSources: [Wrangler command documentation](https://developers.cloudflare.com/workers/wrangler/commands/),\n[D1 migration listing implementation](https://github.com/cloudflare/workers-sdk/blob/e3d186776fcc2e4b5cc4cfd7155591778a375957/packages/wrangler/src/d1/migrations/list.ts),\n[migration table initialization](https://github.com/cloudflare/workers-sdk/blob/e3d186776fcc2e4b5cc4cfd7155591778a375957/packages/wrangler/src/d1/migrations/helpers.ts),\nand [Wrangler custom builds](https://developers.cloudflare.com/workers/wrangler/custom-builds/).\n\n### Ansible\n\nCheck controller Python, ansible-core, collections/roles, and target Python support\ntogether; read porting guides. Inspect all tasks and invoked scripts before\n`ansible-playbook --check --diff` with the real inventory and variables.\n`check_mode: false` forces mutation even with `--check`; unsupported modules and\nconditions based on registered results can omit effects. Inspect deletion,\nrecreation, service/data reset, and package removal semantics beyond the diff.\nSkipped or opaque tasks are a safety gap, not a clean preview. Redact diffs.\n\nSource: [Ansible check mode limits](https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_checkmode.html).\n\n## Inventory and choose compatible targets\n\nRecord each dependency source, current resolved version, latest stable candidate,\nselected target, compatibility constraints, upstream migration links, and checks\nin the run ledger described under Report.\nQuery authoritative registries, official releases, support matrices, and migration\nguides at execution time; do not treat remembered versions or prereleases as\nlatest stable. Inspect runtime requirements, peer dependencies, ABI/API changes,\nplatform availability, maintained release lines, and changed defaults, including\nsecurity, privacy/telemetry, data retention, and postinstall/native behavior.\nCheck transitive dependencies' peer/runtime constraints as well as direct ones. Unsupported\nor unpublished targets stay pinned with an explanation.\n\nInventory manifests, locks, tool configuration, CI/reusable actions, containers,\nscripts, deployment files, and executable documentation. Search duplicate version\npins and aliases, not just filenames; exclude vendored/generated snapshots unless\nthe repository owns their update process. Examples of sources to inspect:\n\n| Ecosystem | Sources and compatibility group |\n| --- | --- |\n| JavaScript/TypeScript | All package.json dependency fields, overrides/resolutions, workspace catalogs, packageManager/engines, npm/Bun/pnpm/Yarn locks, Node/Bun pins, compiler and lint/build/test plugins |\n| Toolchains | mise.toml/.mise.toml, mise.lock, .tool-versions, runtime version files, tool URLs/checksums, CI pins and container images; include every tool declared by mise, not just mise itself |\n| Python/Ansible | pyproject/requirements, uv/Poetry/pip locks, Python pins, ansible-core, collection/role requirements, lint plugins and managed host constraints |\n| Ruby/iOS | .ruby-version, Gemfile/lock, Bundler, CocoaPods, Podfile/lock, podspecs, Swift packages and Xcode/iOS deployment requirements |\n| Infrastructure | Terraform/OpenTofu CLI constraints, provider locks/constraints, module versions/Git refs, Wrangler and Cloudflare integration dependencies |\n| CI/system/native | Actions/reusable workflow refs, Docker tags/digests, OS images/packages, Rust/Cargo toolchain and locks, Go modules, Gradle/AGP/Kotlin/JDK and other repository-specific manifests |\n\nBuild connected compatibility groups before editing. Select the newest supported\ncombination, which can be older than an individual package's latest. Upgrade the\nframework first or keep its compatible toolchain pinned; do not force peer\nresolution, drop compatibility checks, or add blanket overrides to make installs\npass. Preserve source pin policy (exact version, digest, full Git SHA) and resolve\nimmutable pins from verified upstream releases. Preserve intentional runtime\nminimums unless migration requires changing the support contract.\n\nFor React Native, use its version-specific upgrade guide/template diffs and\nrepository upgrade skills. Couple React and its renderer, native modules,\nMetro/Babel, CLI, codegen, Hermes/New Architecture, CocoaPods/Bundler/Ruby,\nXcode/iOS deployment targets, and Android Gradle/AGP/Kotlin/JDK/SDK/NDK. For Expo,\nuse the chosen SDK's supported React Native/dependency matrix and its dependency\nchecks. Do not independently update framework-managed pods or generated native\nfiles; use the framework's regeneration workflow and prove iOS/Android builds.\nPermissive peer ranges alone do not prove native renderer or toolchain compatibility;\ncheck the framework's actual supported versions and native implementation.\nAn unavailable native toolchain is a reported validation limit; do not claim the\ngroup verified. See [React Native upgrading](https://reactnative.dev/docs/upgrading).\n\nCheck developer tools' own dependencies too: a compiler release without its old\nprogrammatic API may break test/lint runners that import it even when `tsc` passes.\nAction runtime upgrades can require newer self-hosted runners; preserve workflow\npermissions, action inputs/outputs, caching, artifact semantics, and job names.\n\nCheck current advisories with read-only ecosystem audits against the baseline and\nproposed lockfiles, using the repository's configured registry/advisory sources.\nDo not send private dependency metadata to an unconfigured or unauthorized service;\nreport the resulting coverage limit. Cover direct and resolved transitive dependencies,\nincluding native/platform-specific binaries. Latest direct packages can still\npin vulnerable transitive versions. Trace each finding to its owning dependency,\nverify affected versions, runtime conditions, and remediation in the maintainer's\nadvisory and fixed release. When the owner's range permits a fixed version, use a\ntargeted refresh with the lockfile's owning manager and preserve unrelated\nresolutions; otherwise prefer a supported owner upgrade.\nIf the owner still pins a vulnerable version, a narrowly scoped security patch\noverride may be used only with API/ABI/runtime compatibility evidence and focused\nregression checks of the actual affected integration. Document its rationale and\nremoval condition; a passing install or bundle alone is insufficient. Do not run\nblind audit fixes or use blanket overrides to bypass compatibility constraints.\nReport unresolved advisories and unavailable audit coverage accurately; current\ndirect pins or a partial clean audit do not\nestablish that the whole dependency graph is safe.\n\n## Migrate and validate\n\nCapture relevant baseline checks and warnings first, after screening their side\neffects. Update one compatibility group at a time. Read the upstream instructions\nfor every breaking step; implement required source/config/schema migrations and\nremoved API replacements. Use the manager that owns each lockfile, on the selected\nruntime, and inspect the resulting diff. For Bun lockfiles, change declared\nversions with `agent-tool bun update <name[@version]>` and drop packages with\n`agent-tool bun remove <name>`; both use the pinned Bun and refuse to add a\npackage. A new dependency goes through `agent-tool bun add <name@version>`, which\nasks for approval; never introduce one by editing `package.json` and\nreinstalling. For existing Bun catalog entries use\n`agent-tool bun update --catalog default <name@version>...` (or the existing named\ncatalog). Name an explicit exact/ranged registry spec; keep `catalog:` references,\nworkspace membership, other entries, and peer compatibility contracts intact.\nThe helper refuses intake and ambiguous layouts and uses the owning manager to\nrefresh the lock; do not replace catalog references with direct versions.\n\nFor an already-resolved transitive registry package with an eligible owner range,\nuse `agent-tool bun update --resolved <name>...` with the owning Bun 1.4.4 or newer.\nIt selects every locked copy of those names, never a requested version or glob,\nand accepts only a result with identical manifests and unchanged non-target lock\nentries/metadata. It preserves release-age, overrides and patches. If an older\nmanager is pinned, or a required new child prevents a target-only result, this\npath is unsupported: prefer an owning-dependency upgrade through the normal\nhelper, or obtain approval for an explicitly reviewed manager workflow. Do not\nsilently substitute a broad refresh or add a transitive package directly. Inspect\nthe receipt/logs and re-audit; a no-op or warning can leave an advisory unresolved.\nNever hand-edit integrity hashes or lock entries.\nReview intentional patches, overrides, and pins before removal; retain them until\nupstream fixes and regression checks demonstrate they are unnecessary.\nAvoid unrelated global installations or environment changes. Use task-local or\nrepository-scoped tools for testing changed runtime pins.\nFor published packages, also validate tarballs and consumer installs with their\ndocumented supported package managers, including manifest/override syntax and\nruntime requirements. A successful owning-manager install does not prove that\npublished metadata works for those consumers.\n\nRun the repository's setup/hooks, frozen installs, lint, typechecks, tests, builds,\npackage/platform smoke checks, and relevant safe integration previews. Resolve\nupgrade-related deprecation warnings at their source. Do not suppress warnings,\nweaken tests, or disable checks; report remaining upstream/pre-existing warnings\nwith evidence and keep a target pinned when migration cannot be completed.\nAdd focused regression coverage when a migration changes behavior; avoid tests\nthat merely assert version strings. Re-run checks after repairs. Report checks\nblocked by missing credentials, platforms, or external services accurately.\nRe-audit the final resolved graph after migrations and repairs.\n\nConfirm every inventory row is upgraded, already current, intentionally constrained,\nor skipped with a concrete reason. Do not present an unresolved group as complete.\nWhen upgrading agent-tool itself, install its managed skills with the same harness\nscope and commit the ownership manifest; never edit generated managed skill copies.\n\nWhen changes depend on another repository's release, ship the producer first and\nconfirm publishing and registry availability before pinning the consumer; include\nits exact version/commit and API migration notes. Independent groups may proceed.\nComplete the installed `ship-pr` workflow within the requested scope, including\nindependent review of every changed HEAD, required CI and review feedback, merge,\nand verification of the merge's normal publication or deployment. Do not stop at\nan open PR unless the user requested that stopping point or a concrete blocker\nprevents continuing. Check deployment safety again before push/open/merge;\nshipping defaults do not waive the preview gates above.\n\n## Report\n\nKeep run-specific evidence in the upgrade PR body or a linked PR comment: the\ninventory and old/selected versions, completed migrations, validation and warnings,\nbaseline/final audits and coverage limits, preview environment/commit/tool versions\nand safe action summary, and skipped/constrained dependencies with reasons. Include\nsecurity overrides, their rationale and removal condition, and unresolved advisories.\nIf the user requested a scope that stops before a PR, provide this ledger in the\ntask report and honor that limit. For a sweep, keep one entry per repository,\nincluding unready and unchanged checkouts, and use an existing tracking issue only\nwhen explicitly requested. Never create an issue merely to store upgrade evidence.\n\nKeep durable repository guidance focused on what the next upgrade needs: current\ncompatibility constraints, intentional pins/overrides, unresolved advisories, and\nreusable repository-specific procedures. Prefer the existing maintenance document;\notherwise create `docs/dependency-upgrades.md` (or the repository's equivalent)\nonly when there is substantive guidance to preserve. For each constraint, record\nits rationale, upstream advisory/migration/issue link or originating PR, and the\ncondition and check for removing or revisiting it. Do not copy version inventories,\naudit output, validation/preview receipts, or dated run journals into that document.\n\nOn every upgrade, reconcile the existing maintenance document against the final\ndependency graph and checks: update constraints in place, remove resolved entries,\nand link the PR for historical evidence. Keep the root README focused on purpose,\nsetup, supported requirements, and normal usage, with a concise maintenance link\nwhen needed. Keep dependency-maintenance guidance in `AGENTS.md` to a short\nrouting rule and link; do not accumulate upgrade reports in either entrypoint\nor duplicate the maintenance document.\n\nFor shipping, report the branch, reviewed commit/verdict, PR, merge, publish/deploy\nresult, and final checkout state. Keep full logs private; never publish credentials,\nstate, or plan contents in any of these locations.\n"
9
9
  };
@@ -45,8 +45,10 @@ function readManifest(root: string): Record<string, string> {
45
45
 
46
46
  export function installSkills(rootDir: string, target: string, apply = false): SkillChange[] {
47
47
  const root = realpathSync(rootDir);
48
- // OpenCode discovers both of these directories; avoid a third duplicate.
49
- const directories = target === "all" ? [".agents/skills", ".claude/skills"] : [harnessNamed(target).skillDirectory];
48
+ // Hermes consumes shared skills, not the review adapter registry. OpenCode
49
+ // discovers both default directories too; avoid additional duplicate bundles.
50
+ const directories = target === "all" ? [".agents/skills", ".claude/skills"]
51
+ : [target === "hermes" ? ".agents/skills" : harnessNamed(target).skillDirectory];
50
52
  const manifestFile = safeDestination(root, MANIFEST);
51
53
  const previous = readManifest(root);
52
54
  const pending: { file: string; relative: string; source: string; change: SkillChange }[] = [];