agent-merge-broker 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +352 -0
  3. package/dist/broker.d.ts +110 -0
  4. package/dist/broker.d.ts.map +1 -0
  5. package/dist/broker.js +1023 -0
  6. package/dist/broker.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +558 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +19 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +312 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/errors.d.ts +16 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +36 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/git.d.ts +46 -0
  20. package/dist/git.d.ts.map +1 -0
  21. package/dist/git.js +195 -0
  22. package/dist/git.js.map +1 -0
  23. package/dist/hooks.d.ts +22 -0
  24. package/dist/hooks.d.ts.map +1 -0
  25. package/dist/hooks.js +110 -0
  26. package/dist/hooks.js.map +1 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +10 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/patterns.d.ts +9 -0
  32. package/dist/patterns.d.ts.map +1 -0
  33. package/dist/patterns.js +66 -0
  34. package/dist/patterns.js.map +1 -0
  35. package/dist/process.d.ts +30 -0
  36. package/dist/process.d.ts.map +1 -0
  37. package/dist/process.js +91 -0
  38. package/dist/process.js.map +1 -0
  39. package/dist/provenance.d.ts +10 -0
  40. package/dist/provenance.d.ts.map +1 -0
  41. package/dist/provenance.js +36 -0
  42. package/dist/provenance.js.map +1 -0
  43. package/dist/publisher.d.ts +37 -0
  44. package/dist/publisher.d.ts.map +1 -0
  45. package/dist/publisher.js +144 -0
  46. package/dist/publisher.js.map +1 -0
  47. package/dist/scheduler.d.ts +6 -0
  48. package/dist/scheduler.d.ts.map +1 -0
  49. package/dist/scheduler.js +96 -0
  50. package/dist/scheduler.js.map +1 -0
  51. package/dist/store.d.ts +73 -0
  52. package/dist/store.d.ts.map +1 -0
  53. package/dist/store.js +323 -0
  54. package/dist/store.js.map +1 -0
  55. package/dist/types.d.ts +219 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +3 -0
  58. package/dist/types.js.map +1 -0
  59. package/dist/validation.d.ts +13 -0
  60. package/dist/validation.d.ts.map +1 -0
  61. package/dist/validation.js +75 -0
  62. package/dist/validation.js.map +1 -0
  63. package/dist/verify.d.ts +40 -0
  64. package/dist/verify.d.ts.map +1 -0
  65. package/dist/verify.js +177 -0
  66. package/dist/verify.js.map +1 -0
  67. package/docs/ARCHITECTURE.md +144 -0
  68. package/docs/PROTOCOL.md +127 -0
  69. package/docs/RELEASING.md +23 -0
  70. package/docs/SECURITY.md +57 -0
  71. package/package.json +68 -0
  72. package/schemas/config.schema.json +149 -0
  73. package/schemas/provenance.schema.json +56 -0
  74. package/schemas/receipt.schema.json +39 -0
  75. package/templates/AGENTS.snippet.md +5 -0
@@ -0,0 +1,144 @@
1
+ import path from "node:path";
2
+ import { BrokerError } from "./errors.js";
3
+ import { runCommand } from "./process.js";
4
+ function renderTitle(template, batch) {
5
+ return template.replaceAll("{batchId}", batch.id).replaceAll("{taskCount}", String(batch.taskIds.length));
6
+ }
7
+ function pullRequestBody(batch, tasks) {
8
+ const rows = tasks.map((task) => `- \`${task.id}\`${task.title ? ` — ${task.title}` : ""}`);
9
+ return [
10
+ "## Merge Broker batch",
11
+ "",
12
+ `Batch: \`${batch.id}\``,
13
+ `Base: \`${batch.baseBranch}\` at \`${batch.baseSha}\``,
14
+ "",
15
+ "### Tasks",
16
+ "",
17
+ ...rows,
18
+ "",
19
+ "### Validation",
20
+ "",
21
+ ...batch.validations.map((result) => `- ${result.exitCode === 0 ? "✅" : "❌"} ${result.name}`),
22
+ ...(batch.provenancePath
23
+ ? ["", "### Provenance", "", `- Batch manifest: \`${batch.provenancePath}\``]
24
+ : []),
25
+ "",
26
+ "Generated by Agent Merge Broker. Commit receipts and validation output remain in local broker state.",
27
+ ].join("\n");
28
+ }
29
+ export async function publishBatch(options) {
30
+ const { repo, config, batch, tasks } = options;
31
+ if (!batch.branchName)
32
+ throw new BrokerError("NO_BRANCH", `Batch ${batch.id} has no integration branch.`);
33
+ if (config.publish.mode === "none") {
34
+ throw new BrokerError("PUBLISH_DISABLED", "Publishing is disabled in .merge-broker/config.json.");
35
+ }
36
+ await repo.push(config.remote, batch.branchName);
37
+ if (config.publish.mode === "branch") {
38
+ return { mode: "branch", branchName: batch.branchName };
39
+ }
40
+ const title = renderTitle(config.publish.titleTemplate, batch);
41
+ const args = [
42
+ "pr",
43
+ "create",
44
+ "--base",
45
+ config.baseBranch,
46
+ "--head",
47
+ batch.branchName,
48
+ "--title",
49
+ title,
50
+ "--body-file",
51
+ "-",
52
+ ];
53
+ if (config.publish.draft)
54
+ args.push("--draft");
55
+ for (const label of config.publish.labels)
56
+ args.push("--label", label);
57
+ const result = await runCommand("gh", args, {
58
+ cwd: repo.root,
59
+ input: pullRequestBody(batch, tasks),
60
+ });
61
+ const url = result.stdout
62
+ .split("\n")
63
+ .map((line) => line.trim())
64
+ .find((line) => /^https?:\/\//u.test(line));
65
+ if (!url) {
66
+ throw new BrokerError("PUBLISH_FAILED", "GitHub CLI did not return a pull request URL.", {
67
+ stdout: result.stdout,
68
+ stderr: result.stderr,
69
+ });
70
+ }
71
+ const autoMergeEnabled = config.publish.autoMerge ? await enableAutoMerge(repo.root, url, config) : false;
72
+ return { mode: "pull-request", branchName: batch.branchName, pullRequestUrl: url, autoMergeEnabled };
73
+ }
74
+ /**
75
+ * GitHub's own view of whether a pull request can merge. Returns undefined when the state cannot be
76
+ * read at all, which is treated as "do not merge" rather than as permission to proceed.
77
+ */
78
+ export async function readMergeState(repoRoot, pullRequestUrl) {
79
+ const result = await runCommand("gh", ["pr", "view", pullRequestUrl, "--json", "state,mergeStateStatus,mergeable"], { cwd: repoRoot, allowFailure: true });
80
+ if (result.exitCode !== 0)
81
+ return undefined;
82
+ try {
83
+ const value = JSON.parse(result.stdout);
84
+ return {
85
+ state: value.state ?? "UNKNOWN",
86
+ mergeStateStatus: value.mergeStateStatus ?? "UNKNOWN",
87
+ mergeable: value.mergeable ?? "UNKNOWN",
88
+ };
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ }
94
+ /**
95
+ * Hands the merge decision to GitHub: it lands the pull request once required checks pass, and
96
+ * updates the branch itself when the base branch requires it. The broker never pushes to the base
97
+ * branch, so branch protection stays authoritative.
98
+ */
99
+ export async function enableAutoMerge(repoRoot, pullRequestUrl, config) {
100
+ const result = await runCommand("gh", ["pr", "merge", pullRequestUrl, "--auto", `--${config.publish.mergeMethod}`], { cwd: repoRoot, allowFailure: true });
101
+ if (result.exitCode === 0)
102
+ return true;
103
+ // GitHub refuses to queue auto-merge on a pull request that is already mergeable, which happens
104
+ // whenever the required checks finish before publication returns. Ask GitHub what the state is
105
+ // instead of reading the CLI's prose, which differs across gh versions and locales.
106
+ const mergeState = await readMergeState(repoRoot, pullRequestUrl);
107
+ if (mergeState?.state === "MERGED")
108
+ return true;
109
+ if (mergeState?.mergeStateStatus === "CLEAN") {
110
+ // "CLEAN" means mergeable with every required check already passed, so merging now is exactly
111
+ // what the auto-merge queue would have done.
112
+ const direct = await runCommand("gh", ["pr", "merge", pullRequestUrl, `--${config.publish.mergeMethod}`], {
113
+ cwd: repoRoot,
114
+ allowFailure: true,
115
+ });
116
+ if (direct.exitCode === 0)
117
+ return true;
118
+ throw new BrokerError("AUTO_MERGE_FAILED", "The pull request reported a clean merge state but did not merge.", {
119
+ pullRequestUrl,
120
+ mergeState,
121
+ stdout: direct.stdout,
122
+ stderr: direct.stderr,
123
+ });
124
+ }
125
+ throw new BrokerError("AUTO_MERGE_FAILED", "Could not enable auto-merge on the published pull request. Enable auto-merge in the repository settings, or set publish.autoMerge to false.", {
126
+ pullRequestUrl,
127
+ ...(mergeState ? { mergeState } : {}),
128
+ stdout: result.stdout,
129
+ stderr: result.stderr,
130
+ });
131
+ }
132
+ export async function inspectPullRequest(repoRoot, pullRequestUrl) {
133
+ const result = await runCommand("gh", ["pr", "view", pullRequestUrl, "--json", "state,mergedAt,mergeCommit"], { cwd: repoRoot });
134
+ const value = JSON.parse(result.stdout);
135
+ return {
136
+ state: value.state,
137
+ ...(value.mergedAt ? { mergedAt: value.mergedAt } : {}),
138
+ ...(value.mergeCommit?.oid ? { mergeCommitSha: value.mergeCommit.oid } : {}),
139
+ };
140
+ }
141
+ export function defaultProjectTitle(repoRoot) {
142
+ return path.basename(repoRoot);
143
+ }
144
+ //# sourceMappingURL=publisher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publisher.js","sourceRoot":"","sources":["../src/publisher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { BrokerConfig, BrokerState, SchedulePlan } from "./types.js";
2
+ export declare function scheduleTasks(state: BrokerState, config: BrokerConfig, options?: {
3
+ taskIds?: string[];
4
+ maxTasks?: number;
5
+ }): SchedulePlan;
6
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAc,MAAM,YAAY,CAAC;AA+BtF,wBAAgB,aAAa,CAC3B,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,YAAY,CA0Ed"}
@@ -0,0 +1,96 @@
1
+ import { matchedSerializedPatterns, pathsOverlap } from "./patterns.js";
2
+ function timestamp(value) {
3
+ return value ? Date.parse(value) : Number.MAX_SAFE_INTEGER;
4
+ }
5
+ function taskFromState(state, taskId) {
6
+ return Object.hasOwn(state.tasks, taskId) ? state.tasks[taskId] : undefined;
7
+ }
8
+ function dependencyReady(task, selected, state, requireDependencies) {
9
+ if (!requireDependencies)
10
+ return true;
11
+ return task.dependsOn.every((dependency) => {
12
+ const dependencyTask = taskFromState(state, dependency);
13
+ return dependencyTask?.status === "merged" || selected.has(dependency);
14
+ });
15
+ }
16
+ function compareTasks(left, right) {
17
+ return (right.priority - left.priority ||
18
+ timestamp(left.submittedAt) - timestamp(right.submittedAt) ||
19
+ left.id.localeCompare(right.id));
20
+ }
21
+ export function scheduleTasks(state, config, options = {}) {
22
+ const requested = options.taskIds ? new Set(options.taskIds) : undefined;
23
+ const candidates = Object.values(state.tasks)
24
+ .filter((task) => task.status === "submitted" && (!requested || requested.has(task.id)))
25
+ .sort(compareTasks);
26
+ const selected = [];
27
+ const selectedIds = new Set();
28
+ const rejected = [];
29
+ const rejectedIds = new Set();
30
+ const maxTasks = Math.min(options.maxTasks ?? config.scheduling.maxTasks, config.scheduling.maxTasks);
31
+ let totalCommits = 0;
32
+ let madeProgress = true;
33
+ while (madeProgress && selected.length < maxTasks) {
34
+ madeProgress = false;
35
+ for (const task of candidates) {
36
+ if (selectedIds.has(task.id) || rejectedIds.has(task.id))
37
+ continue;
38
+ if (!dependencyReady(task, selectedIds, state, config.policies.requireDependencies))
39
+ continue;
40
+ if (totalCommits + task.commits.length > config.scheduling.maxCommits) {
41
+ // A task larger than the whole limit can never be scheduled, however many times it is
42
+ // re-planned. Say so, instead of reporting the same transient-looking reason forever.
43
+ rejected.push({
44
+ taskId: task.id,
45
+ reason: task.commits.length > config.scheduling.maxCommits
46
+ ? `task has ${task.commits.length} commits and can never fit scheduling.maxCommits (${config.scheduling.maxCommits}); raise the limit or split the task`
47
+ : "batch commit limit",
48
+ });
49
+ rejectedIds.add(task.id);
50
+ continue;
51
+ }
52
+ const collision = selected.find((chosen) => {
53
+ if (!config.scheduling.allowPathOverlap && pathsOverlap(task.actualPaths, chosen.actualPaths))
54
+ return true;
55
+ const taskResources = matchedSerializedPatterns(task.actualPaths, config.leases.serializedPatterns);
56
+ const chosenResources = matchedSerializedPatterns(chosen.actualPaths, config.leases.serializedPatterns);
57
+ return taskResources.some((resource) => chosenResources.includes(resource));
58
+ });
59
+ if (collision) {
60
+ rejected.push({ taskId: task.id, reason: "conflicting change set", conflictsWith: collision.id });
61
+ rejectedIds.add(task.id);
62
+ continue;
63
+ }
64
+ selected.push(task);
65
+ selectedIds.add(task.id);
66
+ totalCommits += task.commits.length;
67
+ madeProgress = true;
68
+ if (selected.length >= maxTasks)
69
+ break;
70
+ }
71
+ }
72
+ for (const task of candidates) {
73
+ if (selectedIds.has(task.id) || rejectedIds.has(task.id))
74
+ continue;
75
+ const missing = config.policies.requireDependencies ? task.dependsOn.filter((dependency) => {
76
+ const dependencyTask = taskFromState(state, dependency);
77
+ return dependencyTask?.status !== "merged" && !selectedIds.has(dependency);
78
+ }) : [];
79
+ rejected.push({
80
+ taskId: task.id,
81
+ reason: missing.length > 0 ? `dependencies not integrated: ${missing.join(", ")}` : "batch task limit",
82
+ });
83
+ }
84
+ if (requested) {
85
+ for (const taskId of requested) {
86
+ const task = taskFromState(state, taskId);
87
+ if (!task)
88
+ rejected.push({ taskId, reason: "unknown task" });
89
+ else if (task.status !== "submitted") {
90
+ rejected.push({ taskId, reason: `task status is ${task.status}` });
91
+ }
92
+ }
93
+ }
94
+ return { selected, rejected, totalCommits };
95
+ }
96
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { type AuditEvent, type BrokerState, type CommitReceipt } from "./types.js";
2
+ export interface LockStatus {
3
+ name: string;
4
+ held: boolean;
5
+ path: string;
6
+ owner?: {
7
+ pid?: number;
8
+ host?: string;
9
+ createdAt?: string;
10
+ };
11
+ ageMs?: number;
12
+ /** True only when the holder is provably gone: this machine, and the process no longer exists. */
13
+ abandoned?: boolean;
14
+ }
15
+ export type AuditRecorder = (event: string, fields?: Omit<AuditEvent, "sequence" | "at" | "event">) => void;
16
+ export declare class StateStore {
17
+ readonly directory: string;
18
+ readonly worktreesDirectory: string;
19
+ readonly archiveDirectory: string;
20
+ readonly tokensDirectory: string;
21
+ private readonly stateFile;
22
+ private readonly auditFile;
23
+ private readonly receiptsDirectory;
24
+ private readonly batchesDirectory;
25
+ private readonly lockTimeoutMs;
26
+ constructor(commonGitDir: string, stateDirectory: string, lockTimeoutSeconds: number);
27
+ initialize(): Promise<void>;
28
+ read(): Promise<BrokerState>;
29
+ transaction<T>(mutator: (state: BrokerState, audit: AuditRecorder) => Promise<T> | T): Promise<T>;
30
+ withIntegrationLock<T>(operation: () => Promise<T>): Promise<T>;
31
+ writeReceipt(receipt: CommitReceipt): Promise<string>;
32
+ tokenPath(taskId: string): string;
33
+ /**
34
+ * Holds a worker's lease token for it. A token shown once and never stored forces every adopter to
35
+ * build a token store, and the obvious implementation puts a live credential in the working tree
36
+ * where `git add` and validator commands can reach it. Here it stays owner-readable, beside the
37
+ * state it authorizes, under a directory whose contents are already trusted.
38
+ */
39
+ writeToken(taskId: string, token: string, target?: string): Promise<string>;
40
+ readToken(taskId: string): Promise<string | undefined>;
41
+ deleteToken(taskId: string): Promise<void>;
42
+ writeBatchManifest(batchId: string, manifest: unknown): Promise<string>;
43
+ /**
44
+ * Reads the tail of the active audit stream. Deliberately tolerant: a tail read can begin
45
+ * mid-line, and a crash between writing and flushing can leave a truncated final line. Neither is
46
+ * a reason to make the whole audit trail unreadable, which is exactly when it is needed most.
47
+ */
48
+ readAudit(limit?: number): Promise<AuditEvent[]>;
49
+ /**
50
+ * Moves the active audit file aside once it grows large enough to make reads expensive. Segments
51
+ * are never deleted: an audit stream that silently forgets is worse than a large one.
52
+ */
53
+ private rotateAuditIfLarge;
54
+ /** Writes a retired slice of state to the archive directory and returns its path. */
55
+ archive(name: string, value: unknown): Promise<string>;
56
+ inspectLock(name: string): Promise<LockStatus>;
57
+ /**
58
+ * Releases a held lock. Without `force` this refuses unless the holder is provably gone, because
59
+ * removing a live lock allows two integrations to run against the same repository at once.
60
+ */
61
+ releaseLock(name: string, options?: {
62
+ force?: boolean;
63
+ }): Promise<LockStatus>;
64
+ private withLock;
65
+ /**
66
+ * True only when the lock is provably abandoned: written by this machine, by a process that is
67
+ * gone. An unreadable owner file is not proof -- it is also what a lock looks like in the instant
68
+ * between creating the directory and recording its owner.
69
+ */
70
+ private lockOwnerCrashed;
71
+ private atomicWrite;
72
+ }
73
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}