@a2f0/agent-tool 0.1.48 → 0.1.50

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a2f0/agent-tool",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
4
4
  "packageManager": "bun@1.4.4",
5
5
  "type": "module",
6
6
  "description": "Portable cross-agent review, PR workflows, and shared skills",
package/src/cli.ts CHANGED
@@ -16,7 +16,6 @@ import { bumpVersions, checkVersions, planVersions } from "./version/bumpVersion
16
16
  import { prepareVersions } from "./version/prepareVersions";
17
17
  import { resolveVersionConflicts } from "./version/resolveVersionConflicts";
18
18
  import { openPr } from "./pr/openPr";
19
- import { squashMerge } from "./pr/squashMerge";
20
19
  import metadata from "../package.json";
21
20
  import { runCommand } from "./commands/run";
22
21
  import { inspectLog } from "./commands/output";
@@ -25,7 +24,7 @@ import { branchCommand } from "./workflows/branch";
25
24
  import { commitsCommand, binariesCommand, gateCommand } from "./workflows/checks";
26
25
  import { hooksCommand } from "./workflows/hooks";
27
26
  import { repoCommand, policyCommand, toolchainCommand } from "./workflows/repository";
28
- import { prWorkflowCommand } from "./workflows/pr";
27
+ import { mergeReviewedHead, prWorkflowCommand } from "./workflows/pr";
29
28
  import { releaseCommand } from "./release/commands";
30
29
  import { checkoutRoot } from "./workflows/core";
31
30
  import { procCommand, teardownCommand } from "./processes/commands";
@@ -89,7 +88,7 @@ Usage: agent-tool [--repo <directory>] [--config <file>] <command>
89
88
  --help | --version
90
89
 
91
90
  Legacy actions remain available: solicitClaudeCodeReview, solicitCodexReview,
92
- solicitOpencodeReview, openPr, squashMerge, bumpVersions, checkVersions,
91
+ solicitOpencodeReview, openPr, bumpVersions, checkVersions,
93
92
  resolveVersionConflicts. A successful review exit means a usable review was
94
93
  returned; inspect VERDICT before shipping. Skill install previews unless --apply.
95
94
  `;
@@ -251,7 +250,7 @@ export function main(argv = process.argv.slice(2)): number | Promise<number> {
251
250
  }
252
251
  usage(action === "merge" && args.length === 3, "Usage: agent-tool pr merge <subject-or-empty> <reviewed-head-oid> <base-branch>");
253
252
  usage(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(args[1]!), "Merge requires a full reviewed HEAD OID.");
254
- return squashMerge(root, args[0], args[1], args[2]);
253
+ return mergeReviewedHead(root, args[0], args[1], args[2]);
255
254
  }
256
255
  if (command === "versions") {
257
256
  const action = args.shift();
@@ -37,7 +37,6 @@ export const CATALOG: CatalogEntry[] = [
37
37
  ...["checkVersions"].map(command => ({ command, class: "read" as const })),
38
38
  ...["bumpVersions", "resolveVersionConflicts", "solicitClaudeCodeReview", "solicitCodexReview", "solicitOpencodeReview"].map(command => ({ command, class: "local" as const })),
39
39
  { command: "openPr", class: "share" },
40
- { command: "squashMerge", class: "release" },
41
40
  ];
42
41
  // `run`, `run-group` and `proc start` execute arbitrary commands, so they stay unclassified and keep the harness default.
43
42
  // `bun x` runs only executables that `permissions.executables` classifies; project.ts adds those.
@@ -97,7 +97,7 @@ export function parsePullRequestMergeTarget(
97
97
  recordField(pullRequest, "autoMergeRequest") !== null
98
98
  ) {
99
99
  throw new Error(
100
- "Pull request already has a queued or automatic merge; cancel it before running squashMerge.",
100
+ "Pull request already has a queued or automatic merge; cancel it before running pr merge.",
101
101
  );
102
102
  }
103
103
  if (expectedBaseRef !== undefined && baseRefName !== expectedBaseRef) {
@@ -1,5 +1,4 @@
1
1
  import { openPr } from "./pr/openPr";
2
- import { squashMerge } from "./pr/squashMerge";
3
2
  import { solicitClaudeCodeReview } from "./review/solicitClaudeCodeReview";
4
3
  import { solicitCodexReview } from "./review/solicitCodexReview";
5
4
  import { solicitOpencodeReview } from "./review/solicitOpencodeReview";
@@ -7,7 +6,7 @@ import { bumpVersions, checkVersions } from "./version/bumpVersions";
7
6
  import { resolveVersionConflicts } from "./version/resolveVersionConflicts";
8
7
 
9
8
  const AGENT_TOOL_USAGE =
10
- "Usage: agent-tool <solicitClaudeCodeReview|solicitCodexReview|solicitOpencodeReview|openPr|squashMerge|bumpVersions|checkVersions|resolveVersionConflicts> [args]\n";
9
+ "Usage: agent-tool <solicitClaudeCodeReview|solicitCodexReview|solicitOpencodeReview|openPr|bumpVersions|checkVersions|resolveVersionConflicts> [args]\n";
11
10
 
12
11
  export interface AgentToolActions {
13
12
  readonly bumpVersions: (rootDir: string, baseOid?: string) => number;
@@ -20,12 +19,6 @@ export interface AgentToolActions {
20
19
  ) => number | Promise<number>;
21
20
  readonly solicitCodexReview: (rootDir: string, effort?: string) => number | Promise<number>;
22
21
  readonly solicitOpencodeReview: (rootDir: string, effort?: string) => number | Promise<number>;
23
- readonly squashMerge: (
24
- rootDir: string,
25
- subject?: string,
26
- expectedHeadSha?: string,
27
- expectedBaseRef?: string,
28
- ) => number;
29
22
  }
30
23
 
31
24
  const defaultActions: AgentToolActions = {
@@ -36,7 +29,6 @@ const defaultActions: AgentToolActions = {
36
29
  solicitClaudeCodeReview,
37
30
  solicitCodexReview,
38
31
  solicitOpencodeReview,
39
- squashMerge,
40
32
  };
41
33
 
42
34
  function assertMaximumPositionals(
@@ -58,7 +50,7 @@ export function runAgentToolAction(
58
50
  actions: AgentToolActions = defaultActions,
59
51
  ): number | Promise<number> {
60
52
  const [action, ...positionals] = args;
61
- const [first, second, third] = positionals;
53
+ const [first] = positionals;
62
54
  switch (action) {
63
55
  case "bumpVersions": {
64
56
  assertMaximumPositionals(action, positionals, 1);
@@ -88,10 +80,12 @@ export function runAgentToolAction(
88
80
  assertMaximumPositionals(action, positionals, 1);
89
81
  return actions.openPr(rootDir, first);
90
82
  }
91
- case "squashMerge": {
92
- assertMaximumPositionals(action, positionals, 3);
93
- return actions.squashMerge(rootDir, first, second, third);
94
- }
83
+ case "squashMerge":
84
+ // Removed: its optional reviewed head let a merge skip preflight.
85
+ process.stderr.write(
86
+ "squashMerge was removed; use agent-tool pr merge <subject-or-empty> <reviewed-head-oid> <base-branch>.\n",
87
+ );
88
+ return 1;
95
89
  default:
96
90
  process.stderr.write(`Unknown action: ${action ?? "(none)"}\n`);
97
91
  process.stderr.write(AGENT_TOOL_USAGE);
@@ -4,11 +4,11 @@ import { closeSync, readFileSync } from "node:fs";
4
4
  import { createLog } from "../commands/logFile";
5
5
  import { DEFAULT_LIMITS, logTail, outputLimit } from "../commands/output";
6
6
  import { loadConfig } from "../config";
7
- import { remoteBranchHead, resolvePr, resolveRepositoryGitUrl } from "../git/prContext";
7
+ import { remoteBranchHead, resolveFreshBaseRef, resolvePr, resolveRepositoryGitUrl } from "../git/prContext";
8
8
  import { assertMergeChecks } from "../pr/mergeChecks";
9
9
  import { assertStrictBaseFreshness } from "../pr/baseFreshness";
10
10
  import { baseFreshness, behindReason, cancelRuns } from "./prFreshness";
11
- import { resolvePullRequestMergeTarget } from "../pr/squashMerge";
11
+ import { resolvePullRequestMergeTarget, squashMerge } from "../pr/squashMerge";
12
12
  import { planVersions } from "../version/bumpVersions";
13
13
  import { assert, clean, currentBranch, git, head, json, oid, option, flag, output, poll, timeoutOptions } from "./core";
14
14
 
@@ -86,6 +86,22 @@ export function preflight(expectedHead: string, baseBranch: string, baseOid: str
86
86
  return { ok: true, repo: identity.repo, pr: pr.number, url: pr.url, headOid: expectedHead, baseBranch, baseOid, checks: checks.checks };
87
87
  }
88
88
 
89
+ /**
90
+ * Merge a reviewed HEAD only after every preflight gate passes against the
91
+ * freshly fetched live base: a skipped, failed, or stale preflight run never
92
+ * leads to a merge. The reviewed HEAD and base branch are required, so no
93
+ * merge path skips preflight.
94
+ */
95
+ export function mergeReviewedHead(rootDir: string, subject: string | undefined, expectedHead: string, baseBranch: string): number {
96
+ assert(!!expectedHead && !!baseBranch, "Merge requires a reviewed HEAD and a base branch.");
97
+ const identity = resolvePr();
98
+ const baseOid = resolveFreshBaseRef(identity.repo, baseBranch);
99
+ try { preflight(expectedHead, baseBranch, baseOid); }
100
+ catch (error) { throw new Error(`Refusing to merge: preflight failed against live ${baseBranch} ${baseOid}: ${(error as Error).message}`); }
101
+ // Bind the merge to the branch that passed preflight, so a retarget in between is refused.
102
+ return squashMerge(rootDir, subject, expectedHead, baseBranch);
103
+ }
104
+
89
105
  interface Comment { id: string; databaseId: number; body: string; url: string; author: { login: string } | null }
90
106
  interface Page<T> { nodes: T[]; pageInfo: { hasNextPage: boolean; endCursor: string | null } }
91
107
  interface Thread { id: string; isResolved: boolean; isOutdated: boolean; path: string; line: number | null; comments: Page<Comment> }