claude-code-merge-queue 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +260 -0
- package/dist/bin/claude-code-local-merge.d.ts +2 -0
- package/dist/bin/claude-code-local-merge.js +316 -0
- package/dist/bin/lanekeeper.d.ts +2 -0
- package/dist/bin/lanekeeper.js +307 -0
- package/dist/bin/localmerge.d.ts +2 -0
- package/dist/bin/localmerge.js +316 -0
- package/dist/bin/mergequeue.d.ts +2 -0
- package/dist/bin/mergequeue.js +307 -0
- package/dist/build-lock.d.ts +1 -0
- package/dist/build-lock.js +70 -0
- package/dist/hooks/worktree-create.d.ts +15 -0
- package/dist/hooks/worktree-create.js +192 -0
- package/dist/land.d.ts +1 -0
- package/dist/land.js +144 -0
- package/dist/lib/check-command.d.ts +29 -0
- package/dist/lib/check-command.js +83 -0
- package/dist/lib/check-push.d.ts +35 -0
- package/dist/lib/check-push.js +46 -0
- package/dist/lib/claude-md-snippet.d.ts +16 -0
- package/dist/lib/claude-md-snippet.js +18 -0
- package/dist/lib/config.d.ts +92 -0
- package/dist/lib/config.js +137 -0
- package/dist/lib/ephemeral.d.ts +40 -0
- package/dist/lib/ephemeral.js +100 -0
- package/dist/lib/lane-port.d.ts +3 -0
- package/dist/lib/lane-port.js +25 -0
- package/dist/lib/main-checkout.d.ts +1 -0
- package/dist/lib/main-checkout.js +19 -0
- package/dist/lib/prune-lanes.d.ts +36 -0
- package/dist/lib/prune-lanes.js +196 -0
- package/dist/lib/queue-lock.d.ts +26 -0
- package/dist/lib/queue-lock.js +212 -0
- package/dist/lib/tty-confirm.d.ts +1 -0
- package/dist/lib/tty-confirm.js +44 -0
- package/dist/lib/wire-hooks.d.ts +62 -0
- package/dist/lib/wire-hooks.js +230 -0
- package/dist/preview.d.ts +1 -0
- package/dist/preview.js +119 -0
- package/dist/promote.d.ts +1 -0
- package/dist/promote.js +77 -0
- package/dist/sync.d.ts +16 -0
- package/dist/sync.js +161 -0
- package/examples/claude-code-local-merge.config.mjs +67 -0
- package/examples/ephemeral-tmp-dir.example.ts +66 -0
- package/hooks/claude-settings.example.json +14 -0
- package/hooks/pre-push +23 -0
- package/package.json +46 -0
package/dist/sync.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sync.ts — fast-forward the MAIN checkout to its upstream branch.
|
|
3
|
+
*
|
|
4
|
+
* Your dev server (or whatever's watching the filesystem) runs on the MAIN
|
|
5
|
+
* checkout, which tracks the integration branch. Lanes land onto that branch
|
|
6
|
+
* via a push, but the MAIN checkout's working tree only advances on a pull —
|
|
7
|
+
* so it serves stale files until something fast-forwards it. `land` runs
|
|
8
|
+
* this immediately after every successful push, so the dev server picks up
|
|
9
|
+
* landed work with zero manual `git pull`.
|
|
10
|
+
*
|
|
11
|
+
* Safe by construction:
|
|
12
|
+
* - Fast-forward ONLY. If the checkout has diverged from its upstream, it
|
|
13
|
+
* warns and leaves it untouched — never a force, never a merge commit.
|
|
14
|
+
* - Retries transient index.lock contention (two lanes landing near-simultaneously).
|
|
15
|
+
* - If a fast-forward is blocked only by a locally-modified *regenerable*
|
|
16
|
+
* file (configured in claude-code-local-merge.config), it discards that file and
|
|
17
|
+
* retries. Any other dirty file → warn and skip.
|
|
18
|
+
*/
|
|
19
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
20
|
+
import { loadConfig } from "./lib/config.js";
|
|
21
|
+
import { resolveMainCheckout } from "./lib/main-checkout.js";
|
|
22
|
+
import { detectPackageManager } from "./lib/check-command.js";
|
|
23
|
+
const LOCK_RETRIES = 3;
|
|
24
|
+
// Keyed by detectPackageManager's return value. bun writes either lockfile
|
|
25
|
+
// name depending on version, so both are checked.
|
|
26
|
+
const LOCKFILES = {
|
|
27
|
+
npm: ["package-lock.json"],
|
|
28
|
+
pnpm: ["pnpm-lock.yaml"],
|
|
29
|
+
yarn: ["yarn.lock"],
|
|
30
|
+
bun: ["bun.lockb", "bun.lock"],
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The main checkout's node_modules is the one every lane symlinks from
|
|
34
|
+
* (see claude-code-local-merge.config's `symlinks`). Fast-forwarding its git state does
|
|
35
|
+
* nothing to that directory — if the range we just pulled in changed the
|
|
36
|
+
* lockfile, every lane is now silently running on stale dependencies until
|
|
37
|
+
* someone happens to run `npm install` here by hand. Do it automatically,
|
|
38
|
+
* the same moment the git state lands, so the gap never opens.
|
|
39
|
+
*/
|
|
40
|
+
function refreshDependenciesIfChanged(root, before, after) {
|
|
41
|
+
const pm = detectPackageManager(root);
|
|
42
|
+
const lockfiles = LOCKFILES[pm] ?? [];
|
|
43
|
+
const changed = git(root, ["diff", "--name-only", before, after], { allowFail: true }).out.split("\n");
|
|
44
|
+
if (!lockfiles.some((f) => changed.includes(f)))
|
|
45
|
+
return;
|
|
46
|
+
console.log(`claude-code-local-merge sync: lockfile changed — running "${pm} install" so the shared node_modules (symlinked into every lane) stays in sync…`);
|
|
47
|
+
const result = spawnSync(pm, ["install"], { cwd: root, stdio: "inherit" });
|
|
48
|
+
if (result.status !== 0) {
|
|
49
|
+
console.error(`claude-code-local-merge sync: "${pm} install" failed (exit ${result.status ?? 1}) — shared node_modules may be stale. Run it manually in ${root}.`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log("claude-code-local-merge sync: dependencies refreshed.");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function git(cwd, args, { allowFail = false } = {}) {
|
|
56
|
+
try {
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
out: execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
if (!allowFail)
|
|
64
|
+
throw e;
|
|
65
|
+
const err = e;
|
|
66
|
+
return { ok: false, out: `${err.stdout ?? ""}${err.stderr ?? ""}` };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function sleep(ms) {
|
|
70
|
+
const end = Date.now() + ms;
|
|
71
|
+
while (Date.now() < end) {
|
|
72
|
+
/* tiny synchronous backoff for index.lock */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fast-forwards the MAIN checkout. Returns a process exit code; never throws.
|
|
77
|
+
*
|
|
78
|
+
* Accepts an already-loaded config, for `land` calling this immediately
|
|
79
|
+
* after a push that ITSELF introduced or changed claude-code-local-merge.config.mjs: the
|
|
80
|
+
* MAIN checkout hasn't been fast-forwarded yet at that exact moment (that's
|
|
81
|
+
* this function's whole job), so loading fresh from MAIN would silently
|
|
82
|
+
* fall back to DEFAULTS and could reject a perfectly good sync — the same
|
|
83
|
+
* bootstrap gap createLane had to be fixed for. The lane's own config,
|
|
84
|
+
* which just successfully rebased onto and pushed to the real
|
|
85
|
+
* integrationBranch, is the more trustworthy answer at that moment. A bare
|
|
86
|
+
* `claude-code-local-merge sync` (no caller-provided config) still loads fresh from
|
|
87
|
+
* MAIN, same as before.
|
|
88
|
+
*/
|
|
89
|
+
export async function sync(providedCfg) {
|
|
90
|
+
let MAIN;
|
|
91
|
+
try {
|
|
92
|
+
MAIN = resolveMainCheckout(process.cwd());
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
console.error("claude-code-local-merge sync: not inside a git repo — nothing to do.");
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
const cfg = providedCfg ?? (await loadConfig(MAIN));
|
|
99
|
+
const regenerable = new Set(cfg.regenerableFiles);
|
|
100
|
+
const branchRes = git(MAIN, ["rev-parse", "--abbrev-ref", "HEAD"], { allowFail: true });
|
|
101
|
+
const branch = branchRes.out.trim();
|
|
102
|
+
if (!branch || branch === "HEAD") {
|
|
103
|
+
console.error("claude-code-local-merge sync: the checkout is detached or unresolved — left untouched.");
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
// The main checkout is meant to stay parked on integrationBranch permanently
|
|
107
|
+
// (that's what makes "fast-forward it" a safe, unattended operation). If
|
|
108
|
+
// it's on something else — someone switched branches in it by hand, or ran
|
|
109
|
+
// `land` from a single non-worktree checkout instead of a lane worktree —
|
|
110
|
+
// fast-forwarding "whatever HEAD happens to be" silently does the wrong
|
|
111
|
+
// thing. Say so plainly instead of surfacing a raw git error later.
|
|
112
|
+
if (branch !== cfg.integrationBranch) {
|
|
113
|
+
console.error(`claude-code-local-merge sync: this checkout is on '${branch}', not the configured integrationBranch ` +
|
|
114
|
+
`('${cfg.integrationBranch}'). sync only fast-forwards the main checkout — run it from ` +
|
|
115
|
+
`there, or check out '${cfg.integrationBranch}' here first. Left untouched.`);
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
const upstream = `origin/${branch}`;
|
|
119
|
+
const before = git(MAIN, ["rev-parse", "--short", "HEAD"], { allowFail: true }).out.trim();
|
|
120
|
+
git(MAIN, ["fetch", "origin", "--quiet"], { allowFail: true });
|
|
121
|
+
const tryFastForward = () => git(MAIN, ["merge", "--ff-only", upstream], { allowFail: true });
|
|
122
|
+
let res = tryFastForward();
|
|
123
|
+
// Retry transient lock contention (another lane landing at the same instant).
|
|
124
|
+
for (let i = 0; i < LOCK_RETRIES && !res.ok && /index\.lock|Unable to create|another git process/i.test(res.out); i++) {
|
|
125
|
+
sleep(400);
|
|
126
|
+
res = tryFastForward();
|
|
127
|
+
}
|
|
128
|
+
// Blocked by a locally-modified regenerable file? Discard it and retry once.
|
|
129
|
+
if (!res.ok && /would be overwritten by merge/i.test(res.out)) {
|
|
130
|
+
const files = res.out
|
|
131
|
+
.split("\n")
|
|
132
|
+
.map((l) => l.trim())
|
|
133
|
+
.filter((l) => l && !/would be overwritten|please commit|aborting|^error:/i.test(l));
|
|
134
|
+
const blocking = files.filter((f) => !regenerable.has(f));
|
|
135
|
+
if (blocking.length === 0 && files.length > 0) {
|
|
136
|
+
git(MAIN, ["checkout", "--", ...files], { allowFail: true });
|
|
137
|
+
res = tryFastForward();
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.error(`claude-code-local-merge sync: ${branch} has local changes blocking fast-forward (${blocking.join(", ")}). Left untouched — resolve in the checkout.`);
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (res.ok) {
|
|
145
|
+
const after = git(MAIN, ["rev-parse", "--short", "HEAD"], { allowFail: true }).out.trim();
|
|
146
|
+
if (before === after) {
|
|
147
|
+
console.log(`claude-code-local-merge sync: ${branch} already current at ${after}.`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
console.log(`claude-code-local-merge sync: fast-forwarded ${branch} ${before} → ${after} — the dev server will pick it up.`);
|
|
151
|
+
refreshDependenciesIfChanged(MAIN, before, after);
|
|
152
|
+
}
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
if (/Not possible to fast-forward|diverging|non-fast-forward/i.test(res.out)) {
|
|
156
|
+
console.error(`claude-code-local-merge sync: local ${branch} has DIVERGED from ${upstream} (something was committed directly on the checkout). Left untouched — reconcile it manually.`);
|
|
157
|
+
return 0;
|
|
158
|
+
}
|
|
159
|
+
console.error(`claude-code-local-merge sync: could not fast-forward ${branch} — left untouched.\n${res.out.trim()}`);
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// claude-code-local-merge.config.mjs — lives at your repo root. `claude-code-local-merge init` writes a
|
|
2
|
+
// copy of this for you; edit the values below for your project.
|
|
3
|
+
//
|
|
4
|
+
// Worktree isolation is Claude Code's job (native `--worktree` /
|
|
5
|
+
// `isolation: "worktree"`) — this file is what the WorktreeCreate hook
|
|
6
|
+
// (see hooks/claude-settings.example.json) reads to name and shape the lane
|
|
7
|
+
// it creates, and what everything downstream (build queue, landing queue,
|
|
8
|
+
// preview) reads too.
|
|
9
|
+
|
|
10
|
+
/** @type {import("claude-code-local-merge").ClaudeCodeLocalMergeConfig} */
|
|
11
|
+
export default {
|
|
12
|
+
// Lane branches: lane/1, lane/2, ...
|
|
13
|
+
branchPrefix: "lane/",
|
|
14
|
+
|
|
15
|
+
// Sibling worktree dirs: ../<your-repo>-lane-1, -lane-2, ...
|
|
16
|
+
worktreeSuffix: "-lane-",
|
|
17
|
+
|
|
18
|
+
// Lane 1 gets this port, lane 2 gets portBase + 2, and so on — handy if
|
|
19
|
+
// each lane also runs its own throwaway dev server.
|
|
20
|
+
portBase: 3000,
|
|
21
|
+
|
|
22
|
+
// The branch `claude-code-local-merge land` rebases onto and pushes to. Agents land
|
|
23
|
+
// here continuously and autonomously — see the CLAUDE.md workflow section
|
|
24
|
+
// `claude-code-local-merge init` writes.
|
|
25
|
+
integrationBranch: "main",
|
|
26
|
+
|
|
27
|
+
// Set this if you run a two-stage model: agents land on integrationBranch,
|
|
28
|
+
// a human ships to productionBranch on their own schedule with
|
|
29
|
+
// `claude-code-local-merge promote`. null (the default) means integrationBranch IS
|
|
30
|
+
// production — no separate promotion step. Example: integrationBranch
|
|
31
|
+
// "dev", productionBranch "main". Automatically protected by the pre-push
|
|
32
|
+
// hook when set — you don't need to also list it below.
|
|
33
|
+
productionBranch: null,
|
|
34
|
+
|
|
35
|
+
// Extra branches the pre-push hook refuses a *direct* push to, beyond
|
|
36
|
+
// integrationBranch and productionBranch. Most repos need nothing here.
|
|
37
|
+
protectedBranches: [],
|
|
38
|
+
|
|
39
|
+
// Files your build tool rewrites on its own that should never block a
|
|
40
|
+
// rebase or a fast-forward. Next.js projects typically want
|
|
41
|
+
// ["next-env.d.ts"] here at minimum — add to this list the first time a
|
|
42
|
+
// regenerated file blocks a landing, and never think about it again.
|
|
43
|
+
regenerableFiles: [],
|
|
44
|
+
|
|
45
|
+
// Git-ignored paths symlinked into every new lane so it needs no fresh
|
|
46
|
+
// install and no copy of your secrets.
|
|
47
|
+
symlinks: [".env", ".env.local", "node_modules"],
|
|
48
|
+
|
|
49
|
+
// Build-output dirs `claude-code-local-merge preview` never copies onto your dev
|
|
50
|
+
// checkout. preview is framework-agnostic (it's an rsync, not a build
|
|
51
|
+
// step) — this is the one place your framework's name shows up. Add
|
|
52
|
+
// ".output" for Nuxt, ".svelte-kit" for SvelteKit, etc.
|
|
53
|
+
buildOutputDirs: ["dist", "build", ".next"],
|
|
54
|
+
|
|
55
|
+
// The command that actually gates a landing — your lint/typecheck/test/
|
|
56
|
+
// build. `claude-code-local-merge init` tries to detect this from package.json
|
|
57
|
+
// (check:push, check, ci, or test, in that order) and fills it in for
|
|
58
|
+
// you. null means nothing runs, which is only allowed if checksRequired
|
|
59
|
+
// is also false — see below.
|
|
60
|
+
checkCommand: "npm run check",
|
|
61
|
+
|
|
62
|
+
// true (the default): a null checkCommand FAILS every push rather than
|
|
63
|
+
// landing unverified code. Set to false yourself to deliberately run
|
|
64
|
+
// with no checks — a real state for a repo with nothing to test yet, but
|
|
65
|
+
// one that should be a visible, committed choice, not a silent default.
|
|
66
|
+
checksRequired: true,
|
|
67
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A complete, runnable EphemeralResourceProvider — a scratch directory per
|
|
3
|
+
* test run instead of a database branch, so this example works with zero
|
|
4
|
+
* external services and zero cost. Swap `create`/`destroy`/`destroyOrphan`
|
|
5
|
+
* for calls to your actual provider (a Neon branch-create/delete API, a
|
|
6
|
+
* `CREATE DATABASE ... TEMPLATE ...`, a Docker container) and everything
|
|
7
|
+
* else — the claim registry, the orphan pruning, the finally-block release —
|
|
8
|
+
* carries over unchanged.
|
|
9
|
+
*
|
|
10
|
+
* Run it: node --import tsx examples/ephemeral-tmp-dir.example.ts
|
|
11
|
+
* Simulate a crash: node --import tsx examples/ephemeral-tmp-dir.example.ts --crash
|
|
12
|
+
* then run it again without --crash and watch it prune the orphan first.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { ClaimRegistry, withEphemeralResource, type EphemeralResourceProvider } from "../src/lib/ephemeral.js";
|
|
18
|
+
|
|
19
|
+
const REGISTRY_DIR = join(tmpdir(), "claude-code-local-merge-example-ephemeral-registry");
|
|
20
|
+
const RESOURCE_ROOT = join(tmpdir(), "claude-code-local-merge-example-ephemeral-resources");
|
|
21
|
+
mkdirSync(RESOURCE_ROOT, { recursive: true });
|
|
22
|
+
|
|
23
|
+
const tmpDirProvider: EphemeralResourceProvider<string> = {
|
|
24
|
+
async create() {
|
|
25
|
+
const dir = mkdtempSync(join(RESOURCE_ROOT, "run-"));
|
|
26
|
+
console.log(` created scratch dir: ${dir}`);
|
|
27
|
+
return dir;
|
|
28
|
+
},
|
|
29
|
+
async destroy(dir) {
|
|
30
|
+
rmSync(dir, { recursive: true, force: true });
|
|
31
|
+
console.log(` destroyed scratch dir: ${dir}`);
|
|
32
|
+
},
|
|
33
|
+
async destroyOrphan(claim) {
|
|
34
|
+
// The claim only tells us WHO made it and WHEN, not which directory it
|
|
35
|
+
// owned — in a real provider you'd store that mapping yourself (e.g. the
|
|
36
|
+
// resource's ID inside the claim record). For this example, orphaned
|
|
37
|
+
// "run-*" directories are swept by age instead.
|
|
38
|
+
console.log(` pruning orphan left by dead pid ${claim.pid} (claimed at ${new Date(claim.createdAt).toISOString()})`);
|
|
39
|
+
for (const name of readdirSync(RESOURCE_ROOT)) {
|
|
40
|
+
const path = join(RESOURCE_ROOT, name);
|
|
41
|
+
if (existsSync(join(path, ".orphan-sweep-safe"))) {
|
|
42
|
+
rmSync(path, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const registry = new ClaimRegistry(REGISTRY_DIR);
|
|
49
|
+
|
|
50
|
+
if (process.argv.includes("--crash")) {
|
|
51
|
+
// Simulate a run that claims a resource and then dies before its finally
|
|
52
|
+
// block runs — no rmSync, no registry.release(). The NEXT run should find
|
|
53
|
+
// and prune this.
|
|
54
|
+
const dir = mkdtempSync(join(RESOURCE_ROOT, "run-"));
|
|
55
|
+
writeFileSync(join(dir, ".orphan-sweep-safe"), "");
|
|
56
|
+
registry.record({ id: `${Date.now()}-${process.pid}`, pid: process.pid, createdAt: Date.now() });
|
|
57
|
+
console.log(`simulated crash: claimed ${dir} and exiting without cleanup (pid ${process.pid})`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await withEphemeralResource(tmpDirProvider, registry, async (dir) => {
|
|
62
|
+
writeFileSync(join(dir, "example.txt"), "this file only exists for the run's lifetime\n");
|
|
63
|
+
console.log(` running your tests against ${dir}…`);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log("done — resource was created, used, and torn down; any prior orphan was pruned first.");
|
package/hooks/pre-push
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Claude Code Local Merge — landing-queue enforcement + your checks, gating every push.
|
|
3
|
+
#
|
|
4
|
+
# Copy this file to .husky/pre-push (recommended — Husky wires it in on
|
|
5
|
+
# your package manager's install step, for every clone and every lane
|
|
6
|
+
# worktree) or straight to
|
|
7
|
+
# .git/hooks/pre-push (works, but isn't versioned or shared with the team).
|
|
8
|
+
# If you already have a pre-push hook, append this block instead of
|
|
9
|
+
# replacing the file. `claude-code-local-merge init` does all of this for you.
|
|
10
|
+
#
|
|
11
|
+
# `claude-code-local-merge check-push` does two things, in order:
|
|
12
|
+
# 1. Blocks a direct push to your integration/protected/production
|
|
13
|
+
# branches that didn't go through `claude-code-local-merge land` / `promote`.
|
|
14
|
+
# 2. Runs `checkCommand` from claude-code-local-merge.config.mjs (your lint/typecheck/
|
|
15
|
+
# test/build) and fails the push if it fails — or if checkCommand
|
|
16
|
+
# isn't set and checksRequired wasn't deliberately turned off.
|
|
17
|
+
#
|
|
18
|
+
# See src/land.ts and src/lib/check-push.ts for why a convention alone
|
|
19
|
+
# isn't enough to guarantee either of those.
|
|
20
|
+
|
|
21
|
+
if ! npx claude-code-local-merge check-push; then
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-code-merge-queue",
|
|
3
|
+
"version": "0.1.14",
|
|
4
|
+
"description": "The local, zero-cost merge queue for parallel Claude Code agents. Plugs into Claude Code's native worktree isolation; one build at a time, one landing at a time, zero races.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Jesse Heaslip",
|
|
8
|
+
"homepage": "https://github.com/funador/claude-code-local-merge",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/funador/claude-code-local-merge.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"claude-code",
|
|
15
|
+
"ai-agents",
|
|
16
|
+
"git-worktree",
|
|
17
|
+
"parallel-agents",
|
|
18
|
+
"coding-agent",
|
|
19
|
+
"merge-queue",
|
|
20
|
+
"monorepo-tooling"
|
|
21
|
+
],
|
|
22
|
+
"bin": {
|
|
23
|
+
"claude-code-local-merge": "dist/bin/claude-code-local-merge.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"hooks",
|
|
28
|
+
"examples",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/bin/claude-code-local-merge.js",
|
|
37
|
+
"prepare": "npm run build",
|
|
38
|
+
"pretest": "npm run build",
|
|
39
|
+
"test": "node --import tsx --test test/*.test.ts"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.10.0",
|
|
43
|
+
"tsx": "^4.19.0",
|
|
44
|
+
"typescript": "^5.7.0"
|
|
45
|
+
}
|
|
46
|
+
}
|