lanekeeper 0.1.5 → 0.1.7

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 CHANGED
@@ -49,30 +49,6 @@ 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
-
76
52
  ## 🧰 What's in the box
77
53
 
78
54
  | Command | What it does |
@@ -126,17 +102,11 @@ This does the whole setup, not just the config file:
126
102
  - **`.husky/pre-push`** — created or appended to, *if* you already have
127
103
  Husky. If you don't, `init` tells you so instead of silently writing to
128
104
  the untracked, not-shared-with-your-team `.git/hooks/pre-push`.
105
+ - **`package.json` scripts** — `land`, `sync`, `promote`, `preview`, and
106
+ `preview:restore` added, skipping any you've already defined yourself.
129
107
 
130
- **Commit everything it wrote.** Then add to `package.json`:
131
- ```json
132
- "scripts": {
133
- "land": "lanekeeper land",
134
- "sync": "lanekeeper sync",
135
- "promote": "lanekeeper promote",
136
- "preview": "lanekeeper preview",
137
- "preview:restore": "lanekeeper preview --restore"
138
- }
139
- ```
108
+ **Commit everything it wrote**, then you're running. Two steps, not a setup
109
+ guide.
140
110
 
141
111
  If `init` couldn't detect a `checkCommand` (no matching script in
142
112
  package.json), every push is **blocked** until you set one — see 🧰 What's
@@ -16,7 +16,7 @@ import { runWorktreeCreateHook } from "../hooks/worktree-create.js";
16
16
  import { lanePort } from "../lib/lane-port.js";
17
17
  import { claudeMdSnippet, MARKER } from "../lib/claude-md-snippet.js";
18
18
  import { detectCheckCommand, runCheckCommand } from "../lib/check-command.js";
19
- import { wireClaudeSettings, wireHuskyPrePush, ensureHooksPath } from "../lib/wire-hooks.js";
19
+ import { wireClaudeSettings, wireHuskyPrePush, ensureHooksPath, wirePackageJsonScripts } from "../lib/wire-hooks.js";
20
20
  import { resolveMainCheckout } from "../lib/main-checkout.js";
21
21
  import { pruneLandedLanes } from "../lib/prune-lanes.js";
22
22
  const [, , command, ...rest] = process.argv;
@@ -139,13 +139,27 @@ export default ${JSON.stringify(generated, null, 2)};
139
139
  break;
140
140
  }
141
141
  }
142
+ const scriptsResult = wirePackageJsonScripts(root);
143
+ switch (scriptsResult.result) {
144
+ case "added":
145
+ console.log(`lanekeeper init: added "${scriptsResult.added.join('", "')}" to package.json scripts.`);
146
+ break;
147
+ case "already-wired":
148
+ console.log("lanekeeper init: package.json already has all five scripts — leaving them alone.");
149
+ break;
150
+ case "no-package-json":
151
+ console.log("lanekeeper init: no package.json found — scripts NOT wired automatically.");
152
+ console.log(' Add "land"/"sync"/"promote"/"preview"/"preview:restore" -> "lanekeeper <name>" yourself.');
153
+ break;
154
+ case "unparseable":
155
+ console.log("lanekeeper init: package.json exists but isn't valid JSON — left untouched. Wire the scripts manually.");
156
+ break;
157
+ }
142
158
  console.log("");
143
159
  console.log("Next steps:");
144
- console.log(" 1. Check lanekeeper.config.mjs integrationBranch/checkCommand were auto-detected;");
145
- console.log(" set productionBranch too if you run a two-stage model.");
146
- console.log(' 2. Add to package.json "scripts": land, sync, promote, preview -> "lanekeeper <name>".');
147
- console.log(" 3. Commit lanekeeper.config.mjs, CLAUDE.md, .claude/settings.json, and .husky/pre-push.");
148
- console.log(" 4. claude --worktree <name> — the agent takes it from there.");
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.");
149
163
  }
150
164
  async function main() {
151
165
  switch (command) {
@@ -24,3 +24,16 @@ export type HooksPathResult = "set" | "already-set" | "custom-path";
24
24
  * husky's own next real install corrects it to `.husky/_`.
25
25
  */
26
26
  export declare function ensureHooksPath(root: string): HooksPathResult;
27
+ export type ScriptsWireResult = "added" | "already-wired" | "unparseable" | "no-package-json";
28
+ /**
29
+ * The last "copy this yourself" step `init` used to leave on the table:
30
+ * Quickstart told you to hand-add five scripts to package.json instead of
31
+ * just adding them. Same additive/idempotent contract as the rest of this
32
+ * file — only ever fills in scripts that don't exist yet, never overwrites
33
+ * one you've customized (e.g. if `land` already runs something of yours
34
+ * first), and does nothing if all five are already there.
35
+ */
36
+ export declare function wirePackageJsonScripts(root: string): {
37
+ result: ScriptsWireResult;
38
+ added: string[];
39
+ };
@@ -15,6 +15,13 @@ import { dirname, join } from "node:path";
15
15
  import { fileURLToPath } from "node:url";
16
16
  const HOOK_COMMAND = "npx lanekeeper hook worktree-create";
17
17
  const PRE_PUSH_MARKER = "lanekeeper check-push";
18
+ const PACKAGE_SCRIPTS = {
19
+ land: "lanekeeper land",
20
+ sync: "lanekeeper sync",
21
+ promote: "lanekeeper promote",
22
+ preview: "lanekeeper preview",
23
+ "preview:restore": "lanekeeper preview --restore",
24
+ };
18
25
  export function wireClaudeSettings(root) {
19
26
  const dir = join(root, ".claude");
20
27
  const path = join(dir, "settings.json");
@@ -121,3 +128,35 @@ export function ensureHooksPath(root) {
121
128
  execFileSync("git", ["config", "core.hooksPath", ".husky"], { cwd: root });
122
129
  return "set";
123
130
  }
131
+ /**
132
+ * The last "copy this yourself" step `init` used to leave on the table:
133
+ * Quickstart told you to hand-add five scripts to package.json instead of
134
+ * just adding them. Same additive/idempotent contract as the rest of this
135
+ * file — only ever fills in scripts that don't exist yet, never overwrites
136
+ * one you've customized (e.g. if `land` already runs something of yours
137
+ * first), and does nothing if all five are already there.
138
+ */
139
+ export function wirePackageJsonScripts(root) {
140
+ const path = join(root, "package.json");
141
+ if (!existsSync(path))
142
+ return { result: "no-package-json", added: [] };
143
+ let pkg;
144
+ try {
145
+ pkg = JSON.parse(readFileSync(path, "utf8"));
146
+ }
147
+ catch {
148
+ return { result: "unparseable", added: [] };
149
+ }
150
+ pkg.scripts ??= {};
151
+ const added = [];
152
+ for (const [name, command] of Object.entries(PACKAGE_SCRIPTS)) {
153
+ if (!(name in pkg.scripts)) {
154
+ pkg.scripts[name] = command;
155
+ added.push(name);
156
+ }
157
+ }
158
+ if (added.length === 0)
159
+ return { result: "already-wired", added: [] };
160
+ writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n");
161
+ return { result: "added", added };
162
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lanekeeper",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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",