lanekeeper 0.1.3 → 0.1.4
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 +13 -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
|
@@ -266,6 +266,19 @@ Things a sharp reader should already know before they ask:
|
|
|
266
266
|
refuses to touch any worktree with a live process's cwd inside it right
|
|
267
267
|
now. That check needs `lsof` on PATH; if it's missing, pruning fails
|
|
268
268
|
closed (treats liveness as unknown, never removes) rather than guessing.
|
|
269
|
+
- **The `WorktreeCreate` hook needs the host project's own real install.**
|
|
270
|
+
It runs via `npx lanekeeper hook worktree-create` (a raw hook command has
|
|
271
|
+
no `node_modules/.bin` on its PATH, unlike an `npm run` script) — but npx
|
|
272
|
+
silently falls back to fetching an ephemeral, unpinned copy when it can't
|
|
273
|
+
resolve the package locally, which is exactly what happens if the host
|
|
274
|
+
project's own `node_modules` install of lanekeeper is missing or
|
|
275
|
+
mid-upgrade. That's a real failure mode, not hypothetical: it happened in
|
|
276
|
+
production and the fallback ran silently long enough to mask a broken
|
|
277
|
+
install for two lane-landings. The hook now refuses to run at all if it
|
|
278
|
+
detects it's executing from npx's ephemeral cache rather than the
|
|
279
|
+
project's own installed copy, so a broken install fails loud immediately
|
|
280
|
+
(`npm install` and retry) instead of quietly limping along on a
|
|
281
|
+
mismatched stand-in version.
|
|
269
282
|
|
|
270
283
|
## 🧬 Where this came from
|
|
271
284
|
|
|
@@ -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.4",
|
|
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",
|