lanekeeper 0.1.3 → 0.1.5
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 +37 -0
- package/dist/hooks/worktree-create.d.ts +1 -0
- package/dist/hooks/worktree-create.js +24 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,6 +49,30 @@ GitHub already ships a merge queue. Two things it costs you that this doesn't:
|
|
|
49
49
|
Same idea — serialize landings, test before merge, keep history clean — run
|
|
50
50
|
locally instead of in someone else's billed cloud. 💸
|
|
51
51
|
|
|
52
|
+
## 🧭 Prior art (so you don't have to wonder)
|
|
53
|
+
|
|
54
|
+
Nothing here was invented in isolation — worth naming what already existed
|
|
55
|
+
before you find it yourself:
|
|
56
|
+
|
|
57
|
+
- **[block/agent-task-queue](https://github.com/block/agent-task-queue)**
|
|
58
|
+
already solves "concurrent agents thrash your machine running simultaneous
|
|
59
|
+
builds," standalone, with no git or worktree awareness at all.
|
|
60
|
+
`build-lock` here does the same narrow job, just wired into `land` and
|
|
61
|
+
lanes instead of running on its own.
|
|
62
|
+
- **[Overstory](https://github.com/jayminwest/overstory)** (now archived)
|
|
63
|
+
built a FIFO merge queue plus a liveness-based watchdog for a fleet of
|
|
64
|
+
coding agents — the closest prior match to the landing queue and `prune`
|
|
65
|
+
here. It's a bigger, multi-runtime orchestration framework with its own
|
|
66
|
+
agent/worktree layer, not something that plugs into Claude Code's *native*
|
|
67
|
+
`--worktree`/hook system the way this does; its stated successor moved to
|
|
68
|
+
a hosted, cloud control-plane model.
|
|
69
|
+
|
|
70
|
+
What I couldn't find shipped anywhere else, as of this writing: this
|
|
71
|
+
specific combination — sitting on top of Claude Code's own worktree hook
|
|
72
|
+
instead of reimplementing it, a landing queue enforced at the git pre-push
|
|
73
|
+
hook layer, and liveness-aware auto-pruning — as one small, zero-cost, local
|
|
74
|
+
package. If you know of one, open an issue.
|
|
75
|
+
|
|
52
76
|
## 🧰 What's in the box
|
|
53
77
|
|
|
54
78
|
| Command | What it does |
|
|
@@ -266,6 +290,19 @@ Things a sharp reader should already know before they ask:
|
|
|
266
290
|
refuses to touch any worktree with a live process's cwd inside it right
|
|
267
291
|
now. That check needs `lsof` on PATH; if it's missing, pruning fails
|
|
268
292
|
closed (treats liveness as unknown, never removes) rather than guessing.
|
|
293
|
+
- **The `WorktreeCreate` hook needs the host project's own real install.**
|
|
294
|
+
It runs via `npx lanekeeper hook worktree-create` (a raw hook command has
|
|
295
|
+
no `node_modules/.bin` on its PATH, unlike an `npm run` script) — but npx
|
|
296
|
+
silently falls back to fetching an ephemeral, unpinned copy when it can't
|
|
297
|
+
resolve the package locally, which is exactly what happens if the host
|
|
298
|
+
project's own `node_modules` install of lanekeeper is missing or
|
|
299
|
+
mid-upgrade. That's a real failure mode, not hypothetical: it happened in
|
|
300
|
+
production and the fallback ran silently long enough to mask a broken
|
|
301
|
+
install for two lane-landings. The hook now refuses to run at all if it
|
|
302
|
+
detects it's executing from npx's ephemeral cache rather than the
|
|
303
|
+
project's own installed copy, so a broken install fails loud immediately
|
|
304
|
+
(`npm install` and retry) instead of quietly limping along on a
|
|
305
|
+
mismatched stand-in version.
|
|
269
306
|
|
|
270
307
|
## 🧬 Where this came from
|
|
271
308
|
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { execFileSync } from "node:child_process";
|
|
27
27
|
import { existsSync, mkdirSync, symlinkSync } from "node:fs";
|
|
28
|
-
import { dirname, join, basename } from "node:path";
|
|
28
|
+
import { dirname, join, basename, sep } from "node:path";
|
|
29
|
+
import { fileURLToPath } from "node:url";
|
|
29
30
|
import { loadConfig, hasConfig, DEFAULTS } from "../lib/config.js";
|
|
30
31
|
import { resolveMainCheckout } from "../lib/main-checkout.js";
|
|
31
32
|
// A lane-claim loop with no upper bound is exactly one path-resolution bug
|
|
@@ -121,6 +122,24 @@ export function createLane(mainTop, cfg) {
|
|
|
121
122
|
return { wt, branch, lane };
|
|
122
123
|
}
|
|
123
124
|
}
|
|
125
|
+
// `.claude/settings.json` invokes this hook via `npx lanekeeper hook
|
|
126
|
+
// worktree-create` rather than a project script, precisely because a raw
|
|
127
|
+
// hook command has no `node_modules/.bin` on its PATH the way `npm run`
|
|
128
|
+
// does — npx's own directory-walking local resolution is what makes that
|
|
129
|
+
// work at all. The problem: npx treats a package it can't resolve locally as
|
|
130
|
+
// license to silently fetch an ephemeral, unpinned copy from the registry
|
|
131
|
+
// and run *that* instead of failing — which is exactly what happens when the
|
|
132
|
+
// host project's own install of lanekeeper is missing or mid-upgrade (npm
|
|
133
|
+
// removes the old version's files before extracting the new one; anything
|
|
134
|
+
// that interrupts that leaves precisely this state). That fallback ran
|
|
135
|
+
// silently for long enough in production to block two lanes from landing
|
|
136
|
+
// before anyone noticed node_modules was broken. Refuse to proceed if this
|
|
137
|
+
// module is executing from npx's ephemeral cache instead of the project's
|
|
138
|
+
// own installed copy, so a broken install fails loud immediately instead of
|
|
139
|
+
// limping along on a stand-in version nobody asked for.
|
|
140
|
+
export function isEphemeralNpxCopy(selfPath) {
|
|
141
|
+
return selfPath.includes(`${sep}_npx${sep}`);
|
|
142
|
+
}
|
|
124
143
|
async function readStdin() {
|
|
125
144
|
const chunks = [];
|
|
126
145
|
for await (const chunk of process.stdin)
|
|
@@ -137,6 +156,10 @@ export async function runWorktreeCreateHook() {
|
|
|
137
156
|
}
|
|
138
157
|
const fromCwd = input.cwd ?? process.cwd();
|
|
139
158
|
try {
|
|
159
|
+
if (isEphemeralNpxCopy(fileURLToPath(import.meta.url))) {
|
|
160
|
+
throw new Error("running from npx's ephemeral cache, not this project's own installed dependency — " +
|
|
161
|
+
"node_modules is missing or broken. Run `npm install` in the main checkout and try again.");
|
|
162
|
+
}
|
|
140
163
|
const mainTop = resolveMainCheckout(fromCwd);
|
|
141
164
|
const cfg = hasConfig(mainTop) ? await loadConfig(mainTop) : { ...DEFAULTS };
|
|
142
165
|
const { wt } = createLane(mainTop, cfg);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lanekeeper",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|