lanekeeper 0.1.7 → 0.1.8

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.
@@ -34,10 +34,15 @@ async function init() {
34
34
  }
35
35
  const { writeFileSync, readFileSync: read, existsSync, appendFileSync } = await import("node:fs");
36
36
  const { join } = await import("node:path");
37
+ // What actually got newly written or modified this run — only these need
38
+ // committing. If everything below was already wired from a previous run,
39
+ // there's nothing new to tell you to commit.
40
+ const writtenFiles = [];
37
41
  if (hasConfig(root)) {
38
42
  console.log("lanekeeper init: lanekeeper.config.mjs already exists — leaving it alone.");
39
43
  }
40
44
  else {
45
+ writtenFiles.push("lanekeeper.config.mjs");
41
46
  const detectedBranch = detectCurrentBranch(root);
42
47
  const detectedCheck = detectCheckCommand(root);
43
48
  const generated = {
@@ -80,10 +85,12 @@ export default ${JSON.stringify(generated, null, 2)};
80
85
  if (!existsSync(claudeMdPath)) {
81
86
  writeFileSync(claudeMdPath, `# Project instructions for Claude Code\n\n${snippet}`);
82
87
  console.log(`lanekeeper init: wrote ${claudeMdPath}`);
88
+ writtenFiles.push("CLAUDE.md");
83
89
  }
84
90
  else if (!read(claudeMdPath, "utf8").includes(MARKER)) {
85
91
  appendFileSync(claudeMdPath, `\n${snippet}`);
86
92
  console.log(`lanekeeper init: appended the LaneKeeper workflow section to ${claudeMdPath}`);
93
+ writtenFiles.push("CLAUDE.md");
87
94
  }
88
95
  else {
89
96
  console.log(`lanekeeper init: ${claudeMdPath} already has the LaneKeeper workflow section — leaving it alone.`);
@@ -92,9 +99,11 @@ export default ${JSON.stringify(generated, null, 2)};
92
99
  switch (claudeSettingsResult) {
93
100
  case "created":
94
101
  console.log(`lanekeeper init: wrote ${join(root, ".claude", "settings.json")} with the WorktreeCreate hook.`);
102
+ writtenFiles.push(".claude/settings.json");
95
103
  break;
96
104
  case "merged":
97
105
  console.log(`lanekeeper init: added the WorktreeCreate hook to your existing ${join(root, ".claude", "settings.json")}.`);
106
+ writtenFiles.push(".claude/settings.json");
98
107
  break;
99
108
  case "already-wired":
100
109
  console.log("lanekeeper init: .claude/settings.json already has the WorktreeCreate hook — leaving it alone.");
@@ -108,9 +117,11 @@ export default ${JSON.stringify(generated, null, 2)};
108
117
  switch (prePushResult) {
109
118
  case "created":
110
119
  console.log("lanekeeper init: wrote .husky/pre-push.");
120
+ writtenFiles.push(".husky/pre-push");
111
121
  break;
112
122
  case "merged":
113
123
  console.log("lanekeeper init: appended LaneKeeper's checks to your existing .husky/pre-push.");
124
+ writtenFiles.push(".husky/pre-push");
114
125
  break;
115
126
  case "already-wired":
116
127
  console.log("lanekeeper init: .husky/pre-push already wired — leaving it alone.");
@@ -143,6 +154,7 @@ export default ${JSON.stringify(generated, null, 2)};
143
154
  switch (scriptsResult.result) {
144
155
  case "added":
145
156
  console.log(`lanekeeper init: added "${scriptsResult.added.join('", "')}" to package.json scripts.`);
157
+ writtenFiles.push("package.json");
146
158
  break;
147
159
  case "already-wired":
148
160
  console.log("lanekeeper init: package.json already has all five scripts — leaving them alone.");
@@ -157,9 +169,14 @@ export default ${JSON.stringify(generated, null, 2)};
157
169
  }
158
170
  console.log("");
159
171
  console.log("Next steps:");
160
- console.log(" 1. Commit everything it wrote — lanekeeper.config.mjs, CLAUDE.md, .claude/settings.json,");
161
- console.log(" .husky/pre-push, and package.json.");
162
- console.log(" 2. claude --worktree <name> — the agent takes it from there.");
172
+ if (writtenFiles.length > 0) {
173
+ console.log(` 1. Commit what it wrote — ${writtenFiles.join(", ")}.`);
174
+ console.log(" 2. claude --worktree <name> — the agent takes it from there.");
175
+ }
176
+ else {
177
+ console.log(" 1. claude --worktree <name> — the agent takes it from there.");
178
+ console.log(" (everything was already wired — nothing new to commit)");
179
+ }
163
180
  }
164
181
  async function main() {
165
182
  switch (command) {
@@ -11,4 +11,5 @@ export declare function createLane(mainTop: string, cfg: LaneKeeperConfig): {
11
11
  lane: number;
12
12
  };
13
13
  export declare function isEphemeralNpxCopy(selfPath: string): boolean;
14
+ export declare function expectsLocalInstall(mainTop: string): boolean;
14
15
  export declare function runWorktreeCreateHook(): Promise<void>;
@@ -24,7 +24,7 @@
24
24
  * automatically — nothing to release by hand.
25
25
  */
26
26
  import { execFileSync } from "node:child_process";
27
- import { existsSync, mkdirSync, symlinkSync } from "node:fs";
27
+ import { existsSync, mkdirSync, readFileSync, symlinkSync } from "node:fs";
28
28
  import { dirname, join, basename, sep } from "node:path";
29
29
  import { fileURLToPath } from "node:url";
30
30
  import { loadConfig, hasConfig, DEFAULTS } from "../lib/config.js";
@@ -140,6 +140,25 @@ export function createLane(mainTop, cfg) {
140
140
  export function isEphemeralNpxCopy(selfPath) {
141
141
  return selfPath.includes(`${sep}_npx${sep}`);
142
142
  }
143
+ // The guard above only makes sense for a host project that's an npm project
144
+ // with lanekeeper as a real dependency — hola, say. A non-Node host repo
145
+ // (a Haskell/Lua/Rust/whatever project with no package.json at all) has
146
+ // nowhere to install lanekeeper INTO; npx's ephemeral cache is the only way
147
+ // it can ever run lanekeeper commands, not a fallback masking a broken
148
+ // local install. Only expect a local install — and therefore only treat
149
+ // ephemeral execution as suspicious — when the host's own package.json
150
+ // actually lists lanekeeper as a dependency. No package.json, or one that
151
+ // doesn't mention lanekeeper: ephemeral execution is completely normal.
152
+ export function expectsLocalInstall(mainTop) {
153
+ let pkg;
154
+ try {
155
+ pkg = JSON.parse(readFileSync(join(mainTop, "package.json"), "utf8"));
156
+ }
157
+ catch {
158
+ return false; // no package.json, or unreadable/invalid — nothing "expected" to be there
159
+ }
160
+ return Boolean(pkg.dependencies?.lanekeeper || pkg.devDependencies?.lanekeeper);
161
+ }
143
162
  async function readStdin() {
144
163
  const chunks = [];
145
164
  for await (const chunk of process.stdin)
@@ -156,11 +175,11 @@ export async function runWorktreeCreateHook() {
156
175
  }
157
176
  const fromCwd = input.cwd ?? process.cwd();
158
177
  try {
159
- if (isEphemeralNpxCopy(fileURLToPath(import.meta.url))) {
178
+ const mainTop = resolveMainCheckout(fromCwd);
179
+ if (expectsLocalInstall(mainTop) && isEphemeralNpxCopy(fileURLToPath(import.meta.url))) {
160
180
  throw new Error("running from npx's ephemeral cache, not this project's own installed dependency — " +
161
181
  "node_modules is missing or broken. Run `npm install` in the main checkout and try again.");
162
182
  }
163
- const mainTop = resolveMainCheckout(fromCwd);
164
183
  const cfg = hasConfig(mainTop) ? await loadConfig(mainTop) : { ...DEFAULTS };
165
184
  const { wt } = createLane(mainTop, cfg);
166
185
  process.stdout.write(wt + "\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lanekeeper",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
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",