claude-overnight 1.16.2 → 1.16.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 +55 -33
- package/dist/cli.js +4 -2
- package/dist/merge.d.ts +9 -0
- package/dist/merge.js +133 -19
- package/dist/planner-query.js +13 -0
- package/dist/state.js +9 -2
- package/package.json +22 -7
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# claude-overnight
|
|
2
2
|
|
|
3
|
-
Run 10, 100, or 1000 Claude agents overnight.
|
|
3
|
+
**Run 10, 100, or 1000 Claude agents overnight.** A local multi-session orchestrator for the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) — parallel Claude agent sessions in isolated git worktrees, spend caps, rate-limit handling, and crash-safe resume across days. Press Run and go to sleep.
|
|
4
4
|
|
|
5
|
-
Local-first, git-native, budget-first. Describe what to build, set a spend cap, press Run. The tool plans
|
|
5
|
+
Local-first, git-native, budget-first. Describe what to build, set a spend cap, press Run. The tool plans with a thinking wave of architect sessions, breaks the objective into concrete tasks, launches parallel agent sessions in isolated git worktrees, iterates toward quality with a planner/executor/reflection loop, handles rate limits automatically, and resumes cleanly across crashes, rate-limit windows, and laptop sleeps. You wake up to merged commits.
|
|
6
6
|
|
|
7
|
-
Different shape from hosted single-session
|
|
7
|
+
Different shape from hosted single-session agent harnesses like [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview): instead of one agent in one cloud container, you get many parallel agent sessions running on your own machine, in your real repo, coordinated by multi-wave steering. Works with Claude Opus, Sonnet, and Haiku — or pair an Anthropic planner with a cheaper executor on Qwen, OpenRouter, or any Anthropic-compatible endpoint via the `Other…` picker.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -12,16 +12,7 @@ Different shape from hosted single-session runtimes like [Claude Managed Agents]
|
|
|
12
12
|
npm install -g claude-overnight
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Requires Node.js
|
|
16
|
-
|
|
17
|
-
### Claude Code plugin
|
|
18
|
-
|
|
19
|
-
This repo also ships a Claude Code plugin so any Claude instance (inside this repo or any other) knows how to use, inspect, and resume `claude-overnight` runs:
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
/plugin marketplace add Fornace/claude-overnight
|
|
23
|
-
/plugin install claude-overnight
|
|
24
|
-
```
|
|
15
|
+
Requires Node.js ≥ 20 and Claude authentication (`claude auth login` or `ANTHROPIC_API_KEY`).
|
|
25
16
|
|
|
26
17
|
## Quick start
|
|
27
18
|
|
|
@@ -73,31 +64,55 @@ claude-overnight
|
|
|
73
64
|
|
|
74
65
|
You interact once (objective, budget, model, review themes), then everything runs autonomously — thinking, planning, executing, reflecting, steering. Rate-limited? It waits and retries. Crash? Resume where you left off. Capped at usage limit? Pick up next time with full context preserved.
|
|
75
66
|
|
|
67
|
+
## How is this different?
|
|
68
|
+
|
|
69
|
+
Claude already has several ways to run agents. `claude-overnight` fills a specific niche:
|
|
70
|
+
|
|
71
|
+
- **Claude Code** — interactive pair programming in your terminal. One agent, one conversation, you drive. `claude-overnight` is the inverse: many agents, no driver, you walk away and come back to merged commits.
|
|
72
|
+
- **[Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview)** — a hosted single-session agent harness. One agent, one cloud container, stateful conversation. `claude-overnight` is a local multi-session *orchestrator* built on the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk): many parallel sessions on your machine, in your real repo, with spend caps and multi-day crash-safe resume.
|
|
73
|
+
- **[Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk)** — primitives for building your own agent. `claude-overnight` is one specific thing built on top of it: an overnight swarm orchestrator you didn't have to write.
|
|
74
|
+
- **IDE copilots (Cursor, Copilot, Cline, etc.)** — synchronous assistants that complete while you're at the keyboard. `claude-overnight` is asynchronous: you hand off an objective and a budget, close the laptop lid, and review a branch in the morning.
|
|
75
|
+
|
|
76
|
+
If you want to hand an objective and a spend cap to Claude and wake up to shipped work on your real repo, this is the shape.
|
|
77
|
+
|
|
78
|
+
## Use cases
|
|
79
|
+
|
|
80
|
+
- **Overnight refactors** — "Modernize the auth system" at budget 200.
|
|
81
|
+
- **Batch feature implementation** — dozens of features from a task file, parallelized.
|
|
82
|
+
- **Codebase-wide cleanups** — deduplicate, simplify, rename, normalize.
|
|
83
|
+
- **Test generation at scale** — integration tests for every route or module.
|
|
84
|
+
- **Documentation sprints** — API docs, READMEs, inline comments, changelogs.
|
|
85
|
+
- **Framework migrations** — version upgrades, type annotations, config format swaps.
|
|
86
|
+
- **Quality audits** — reflection waves surface architectural issues and code smells.
|
|
87
|
+
- **Long research runs** — architect sessions explore a large codebase before any code lands.
|
|
88
|
+
|
|
89
|
+
Typical shape: one objective + a $20–$200 spend cap + sleep.
|
|
90
|
+
|
|
76
91
|
## How it works
|
|
77
92
|
|
|
78
|
-
### 1. Thinking wave
|
|
93
|
+
### 1. Thinking wave — parallel architect sessions
|
|
79
94
|
|
|
80
95
|
For budgets > 15, the tool launches **architect agents** that explore your codebase before any code is written. Each one gets a different research angle (architecture, data models, APIs, testing, etc.) and writes a structured design document. The number scales with budget: 5 for budget=50, 10 for budget=2000.
|
|
81
96
|
|
|
82
|
-
### 2.
|
|
97
|
+
### 2. Task orchestration
|
|
83
98
|
|
|
84
|
-
An orchestrator
|
|
99
|
+
An orchestrator session reads all design documents and synthesizes concrete execution tasks — grounded in real files and patterns the architects found. No guesswork. The task plan is also written to a file for resilience — if orchestration is interrupted, partial results survive.
|
|
85
100
|
|
|
86
|
-
### 3.
|
|
101
|
+
### 3. Parallel execution waves
|
|
87
102
|
|
|
88
|
-
Tasks run in parallel (each
|
|
103
|
+
Tasks run in parallel agent sessions (each in its own git worktree). After completing its task, each session automatically runs a **simplify pass** — reviewing its own `git diff` for code reuse opportunities, quality issues, and inefficiencies, then fixing them before the framework commits.
|
|
89
104
|
|
|
90
105
|
After each wave, steering assesses: "how good is this?" — not "what's missing?" It can:
|
|
91
106
|
|
|
92
107
|
- **Execute** more tasks to build features, fix bugs, polish UX
|
|
93
|
-
- **Reflect** by spinning up 1-2 review
|
|
108
|
+
- **Reflect** by spinning up 1-2 review sessions for deep quality/architecture audits
|
|
94
109
|
- **Declare done** when the vision is met at high quality
|
|
95
110
|
|
|
96
|
-
### 4. Goal refinement
|
|
111
|
+
### 4. Goal refinement and steering
|
|
97
112
|
|
|
98
113
|
The tool starts with your broad objective but evolves its definition of "amazing" as it learns your codebase. Steering refines the goal after each wave. Late waves are informed by early discoveries.
|
|
99
114
|
|
|
100
|
-
### 5. Three-layer context
|
|
115
|
+
### 5. Three-layer context memory
|
|
101
116
|
|
|
102
117
|
Long runs stay sharp because steering maintains three layers of memory:
|
|
103
118
|
|
|
@@ -105,7 +120,7 @@ Long runs stay sharp because steering maintains three layers of memory:
|
|
|
105
120
|
- **Milestones** — strategic snapshots archived every ~5 waves. Long-term memory.
|
|
106
121
|
- **Goal** — the evolving north star. What "amazing" means for this codebase.
|
|
107
122
|
|
|
108
|
-
## Run history and
|
|
123
|
+
## Run history, resume, and knowledge carryforward
|
|
109
124
|
|
|
110
125
|
Every run gets its own folder in `.claude-overnight/runs/`. Nothing is ever overwritten.
|
|
111
126
|
|
|
@@ -133,7 +148,7 @@ Any run that stops before the steering system declares the objective complete
|
|
|
133
148
|
|
|
134
149
|
On resume: unmerged branches auto-merge, the wave loop continues, all context is preserved. Designs and reflections stay on disk until the objective is truly complete.
|
|
135
150
|
|
|
136
|
-
If the thinking phase succeeds but orchestration crashes, the next run detects the orphaned design docs and reuses them — no re-running $9 worth of architect
|
|
151
|
+
If the thinking phase succeeds but orchestration crashes, the next run detects the orphaned design docs and reuses them — no re-running $9 worth of architect sessions:
|
|
137
152
|
|
|
138
153
|
```
|
|
139
154
|
✓ Reusing 5 design docs (from prior attempt)
|
|
@@ -143,15 +158,13 @@ If the thinking phase succeeds but orchestration crashes, the next run detects t
|
|
|
143
158
|
...
|
|
144
159
|
```
|
|
145
160
|
|
|
146
|
-
**Knowledge carries forward** — new runs inherit knowledge from completed previous runs. Thinking
|
|
161
|
+
**Knowledge carries forward** — new runs inherit knowledge from completed previous runs. Thinking sessions and steering see what past runs built. Run 2 knows run 1 already built the auth system.
|
|
147
162
|
|
|
148
163
|
Add `.claude-overnight/` to your `.gitignore` (with the trailing slash — see below).
|
|
149
164
|
|
|
150
165
|
A separate, tiny `claude-overnight.log.md` is also written at the repo root on every run. It's human-readable, append-only, one block per run (objective, start/finish, cost, outcome, branch), and is designed to be **committed** — so even after `.claude-overnight/` is cleaned up you can still recover which prompt produced which commits. Use `.claude-overnight/` (with trailing slash) in your gitignore so this file isn't matched by accident.
|
|
151
166
|
|
|
152
|
-
##
|
|
153
|
-
|
|
154
|
-
### Task file
|
|
167
|
+
## Task file and inline modes
|
|
155
168
|
|
|
156
169
|
```bash
|
|
157
170
|
claude-overnight tasks.json
|
|
@@ -181,7 +194,7 @@ For multi-wave runs, add `objective` and `flexiblePlan`:
|
|
|
181
194
|
}
|
|
182
195
|
```
|
|
183
196
|
|
|
184
|
-
|
|
197
|
+
Inline:
|
|
185
198
|
|
|
186
199
|
```bash
|
|
187
200
|
claude-overnight "fix auth bug in src/auth.ts" "add tests for user model"
|
|
@@ -215,7 +228,7 @@ claude-overnight "fix auth bug in src/auth.ts" "add tests for user model"
|
|
|
215
228
|
| `mergeStrategy` | `"yolo" \| "branch"` | `"yolo"` | Merge into HEAD or new branch |
|
|
216
229
|
| `usageCap` | `number (0-100)` | unlimited | Stop at N% utilization |
|
|
217
230
|
|
|
218
|
-
## Custom providers (Qwen, OpenRouter,
|
|
231
|
+
## Custom providers (Qwen, OpenRouter, any Anthropic-compatible endpoint)
|
|
219
232
|
|
|
220
233
|
Planner and executor are picked separately — pair Opus-on-Anthropic for the planner/thinker with a cheaper model on another provider for the bulk of execution.
|
|
221
234
|
|
|
@@ -245,7 +258,7 @@ Saved providers live user-level at `~/.claude/claude-overnight/providers.json` (
|
|
|
245
258
|
|
|
246
259
|
**Non-interactive / CI.** `claude-overnight --model=qwen3-coder-plus` auto-resolves the model id to a saved provider — no separate `--provider` flag.
|
|
247
260
|
|
|
248
|
-
##
|
|
261
|
+
## Spend caps and usage controls
|
|
249
262
|
|
|
250
263
|
### Extra usage protection
|
|
251
264
|
|
|
@@ -272,7 +285,7 @@ The usage bar cycles through all rate limit windows (5h, 7d, etc.) every 3 secon
|
|
|
272
285
|
|
|
273
286
|
When using extra usage with a budget, a dedicated progress bar shows spend vs limit with color-coded fill (magenta → yellow → red).
|
|
274
287
|
|
|
275
|
-
## Rate
|
|
288
|
+
## Rate-limit handling and crash-safe recovery
|
|
276
289
|
|
|
277
290
|
Built for unattended runs lasting hours or days.
|
|
278
291
|
|
|
@@ -286,15 +299,24 @@ Built for unattended runs lasting hours or days.
|
|
|
286
299
|
- **Usage cap**: set a ceiling, active agents finish, no new ones start — run is resumable
|
|
287
300
|
- **Planner retries**: steering and orchestration retry on rate limits (30s/60s/120s backoff) with full context
|
|
288
301
|
|
|
289
|
-
##
|
|
302
|
+
## Git worktrees and branch merging
|
|
290
303
|
|
|
291
|
-
Each agent gets an isolated git worktree (`swarm/task-N` branch). Changes auto-commit. After all
|
|
304
|
+
Each agent session gets an isolated git worktree (`swarm/task-N` branch). Changes auto-commit. After all sessions complete, branches merge back.
|
|
292
305
|
|
|
293
306
|
- `"yolo"` (default): merges into your current branch
|
|
294
307
|
- `"branch"`: creates a new `swarm/run-{timestamp}` branch
|
|
295
308
|
|
|
296
309
|
Conflicts retry with `-X theirs`. Unresolved branches are preserved for manual merge.
|
|
297
310
|
|
|
311
|
+
## Claude Code plugin
|
|
312
|
+
|
|
313
|
+
This repo also ships a Claude Code plugin so any Claude instance (inside this repo or any other) knows how to use, inspect, and resume `claude-overnight` runs:
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
/plugin marketplace add Fornace/claude-overnight
|
|
317
|
+
/plugin install claude-overnight
|
|
318
|
+
```
|
|
319
|
+
|
|
298
320
|
## Exit codes
|
|
299
321
|
|
|
300
322
|
| Code | Meaning |
|
package/dist/cli.js
CHANGED
|
@@ -158,10 +158,12 @@ export function ask(question) {
|
|
|
158
158
|
}
|
|
159
159
|
return new Promise((resolve) => {
|
|
160
160
|
const segs = [];
|
|
161
|
-
|
|
161
|
+
// DEC save/restore cursor + clear-to-end-of-screen so redraws don't pile
|
|
162
|
+
// up when the input wraps past the terminal width onto additional rows.
|
|
162
163
|
const redraw = () => {
|
|
163
|
-
stdout.write("\
|
|
164
|
+
stdout.write("\x1B8\x1B[J" + question + renderSegments(segs));
|
|
164
165
|
};
|
|
166
|
+
stdout.write("\x1B7");
|
|
165
167
|
stdout.write(question);
|
|
166
168
|
stdout.write("\x1B[?2004h");
|
|
167
169
|
try {
|
package/dist/merge.d.ts
CHANGED
|
@@ -17,6 +17,15 @@ export declare function mergeAllBranches(agents: {
|
|
|
17
17
|
branch: string;
|
|
18
18
|
filesChanged: number;
|
|
19
19
|
}[], cwd: string, strategy: MergeStrategy, log: (id: number, msg: string) => void): MergeAllResult;
|
|
20
|
+
/**
|
|
21
|
+
* Last-resort merge: overlay the branch's file state onto HEAD without a real
|
|
22
|
+
* 3-way merge. Walks `git diff --name-status base..branch` and for each entry
|
|
23
|
+
* either checks out the branch's version (add/modify/rename) or removes the
|
|
24
|
+
* file (delete). Always succeeds unless the branch itself is broken. Trades
|
|
25
|
+
* merge-graph fidelity for "your changes actually land" — the right call for
|
|
26
|
+
* an autonomous swarm.
|
|
27
|
+
*/
|
|
28
|
+
export declare function forceMergeOverlay(branch: string, cwd: string): boolean;
|
|
20
29
|
export declare function warnDirtyTree(cwd: string, log: (id: number, msg: string) => void): void;
|
|
21
30
|
export declare function cleanStaleWorktrees(cwd: string, log: (id: number, msg: string) => void): void;
|
|
22
31
|
export declare function writeSwarmLog(opts: {
|
package/dist/merge.js
CHANGED
|
@@ -5,20 +5,45 @@ import { tmpdir } from "os";
|
|
|
5
5
|
export function gitExec(cmd, cwd) {
|
|
6
6
|
return execSync(cmd, { cwd, encoding: "utf-8", stdio: "pipe" });
|
|
7
7
|
}
|
|
8
|
+
/** Total files the agent touched vs base — tracked changes + untracked. */
|
|
9
|
+
function measureWork(worktreeCwd, baseRef) {
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
try {
|
|
12
|
+
// Tracked: committed + staged + unstaged, all vs the worktree's base.
|
|
13
|
+
const diff = gitExec(`git diff --name-only ${baseRef} --`, worktreeCwd);
|
|
14
|
+
for (const p of diff.split("\n"))
|
|
15
|
+
if (p)
|
|
16
|
+
seen.add(p);
|
|
17
|
+
}
|
|
18
|
+
catch { }
|
|
19
|
+
try {
|
|
20
|
+
// Untracked files don't show in `git diff`; count them separately.
|
|
21
|
+
const untracked = gitExec("git ls-files --others --exclude-standard", worktreeCwd);
|
|
22
|
+
for (const p of untracked.split("\n"))
|
|
23
|
+
if (p)
|
|
24
|
+
seen.add(p);
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
return seen.size;
|
|
28
|
+
}
|
|
8
29
|
export function autoCommit(agentId, taskPrompt, worktreeCwd, baseRef, log) {
|
|
9
30
|
if (!existsSync(worktreeCwd)) {
|
|
10
31
|
log(agentId, "Worktree directory gone, skipping commit");
|
|
11
32
|
return 0;
|
|
12
33
|
}
|
|
13
|
-
|
|
14
|
-
|
|
34
|
+
if (!baseRef)
|
|
35
|
+
return 0;
|
|
36
|
+
// Measure actual work BEFORE committing. This captures reality even if a
|
|
37
|
+
// pre-commit hook rejects the commit: we used to silently return 0 in that
|
|
38
|
+
// case and the worktree cleanup would destroy the changes.
|
|
39
|
+
const preCount = measureWork(worktreeCwd, baseRef);
|
|
15
40
|
let status;
|
|
16
41
|
try {
|
|
17
42
|
status = gitExec("git status --porcelain", worktreeCwd);
|
|
18
43
|
}
|
|
19
44
|
catch (err) {
|
|
20
45
|
log(agentId, `git status failed: ${String(err.message || err).slice(0, 120)}`);
|
|
21
|
-
return
|
|
46
|
+
return preCount;
|
|
22
47
|
}
|
|
23
48
|
if (status.trim()) {
|
|
24
49
|
try {
|
|
@@ -27,33 +52,49 @@ export function autoCommit(agentId, taskPrompt, worktreeCwd, baseRef, log) {
|
|
|
27
52
|
catch (err) {
|
|
28
53
|
log(agentId, `git add failed: ${String(err.message || err).slice(0, 120)}`);
|
|
29
54
|
}
|
|
55
|
+
const msg = taskPrompt.slice(0, 72).replace(/'/g, "'\\''");
|
|
30
56
|
try {
|
|
31
|
-
const msg = taskPrompt.slice(0, 72).replace(/'/g, "'\\''");
|
|
32
57
|
gitExec(`git commit -m 'swarm: ${msg}'`, worktreeCwd);
|
|
33
58
|
}
|
|
34
59
|
catch (err) {
|
|
35
60
|
const m = String(err.message || err);
|
|
36
|
-
if (!m.includes("nothing to commit"))
|
|
37
|
-
|
|
61
|
+
if (!m.includes("nothing to commit")) {
|
|
62
|
+
// Hook-gated project: the user's pre-commit hooks rejected a
|
|
63
|
+
// potentially work-in-progress commit (lint errors, type errors,
|
|
64
|
+
// whatever). Retry bypassing hooks — this is swarm scaffolding,
|
|
65
|
+
// not a user-facing commit. Without this the branch stays empty,
|
|
66
|
+
// the merge gate drops it, and the work is destroyed when the
|
|
67
|
+
// worktree is cleaned up.
|
|
68
|
+
try {
|
|
69
|
+
gitExec(`git commit --no-verify -m 'swarm: ${msg}'`, worktreeCwd);
|
|
70
|
+
log(agentId, `Commit hooks bypassed (rejected swarm WIP commit)`);
|
|
71
|
+
}
|
|
72
|
+
catch (err2) {
|
|
73
|
+
log(agentId, `git commit failed even with --no-verify: ${String(err2.message || err2).slice(0, 120)}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
38
76
|
}
|
|
39
77
|
}
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
// its own work → filesChanged=0 → branch skipped by the merge gate → orphaned.
|
|
44
|
-
if (!baseRef)
|
|
45
|
-
return 0;
|
|
78
|
+
// Authoritative post-commit count: this is what `mergeAllBranches` will
|
|
79
|
+
// actually see on the branch.
|
|
80
|
+
let landed = 0;
|
|
46
81
|
try {
|
|
47
82
|
const diff = gitExec(`git diff --name-only ${baseRef}..HEAD`, worktreeCwd);
|
|
48
|
-
|
|
49
|
-
if (count > 0)
|
|
50
|
-
log(agentId, `${count} file(s) changed`);
|
|
51
|
-
return count;
|
|
83
|
+
landed = diff.trim().split("\n").filter(Boolean).length;
|
|
52
84
|
}
|
|
53
85
|
catch (err) {
|
|
54
86
|
log(agentId, `diff vs base failed: ${String(err.message || err).slice(0, 120)}`);
|
|
55
|
-
return 0;
|
|
56
87
|
}
|
|
88
|
+
// Red-flag: work existed before the commit attempt but didn't land on the
|
|
89
|
+
// branch. Surfaces silent data loss (stuck hooks, writes outside the
|
|
90
|
+
// worktree, gitignored targets) that used to look like "agent did 0 work".
|
|
91
|
+
if (landed === 0 && preCount > 0) {
|
|
92
|
+
log(agentId, `${preCount} file(s) touched but did NOT land on branch — check hooks / gitignore / absolute paths`);
|
|
93
|
+
return preCount;
|
|
94
|
+
}
|
|
95
|
+
if (landed > 0)
|
|
96
|
+
log(agentId, `${landed} file(s) changed`);
|
|
97
|
+
return landed;
|
|
57
98
|
}
|
|
58
99
|
export function mergeAllBranches(agents, cwd, strategy, log) {
|
|
59
100
|
const mergeResults = [];
|
|
@@ -121,8 +162,17 @@ export function mergeAllBranches(agents, cwd, strategy, log) {
|
|
|
121
162
|
gitExec("git merge --abort", cwd);
|
|
122
163
|
}
|
|
123
164
|
catch { }
|
|
124
|
-
|
|
125
|
-
|
|
165
|
+
// 3rd tier: brute-force overlay. Handles rename/rename, rename/delete
|
|
166
|
+
// and other tree-level conflicts that `-X theirs` can't resolve.
|
|
167
|
+
if (forceMergeOverlay(agent.branch, cwd)) {
|
|
168
|
+
result.ok = true;
|
|
169
|
+
result.autoResolved = true;
|
|
170
|
+
log(agent.id, `Force-merged ${agent.branch} (overlay)`);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
result.error = e.message?.slice(0, 80);
|
|
174
|
+
log(agent.id, `Merge conflict: ${agent.branch}`);
|
|
175
|
+
}
|
|
126
176
|
}
|
|
127
177
|
}
|
|
128
178
|
mergeResults.push(result);
|
|
@@ -165,6 +215,70 @@ export function mergeAllBranches(agents, cwd, strategy, log) {
|
|
|
165
215
|
}
|
|
166
216
|
return { mergeResults, mergeBranch };
|
|
167
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Last-resort merge: overlay the branch's file state onto HEAD without a real
|
|
220
|
+
* 3-way merge. Walks `git diff --name-status base..branch` and for each entry
|
|
221
|
+
* either checks out the branch's version (add/modify/rename) or removes the
|
|
222
|
+
* file (delete). Always succeeds unless the branch itself is broken. Trades
|
|
223
|
+
* merge-graph fidelity for "your changes actually land" — the right call for
|
|
224
|
+
* an autonomous swarm.
|
|
225
|
+
*/
|
|
226
|
+
export function forceMergeOverlay(branch, cwd) {
|
|
227
|
+
try {
|
|
228
|
+
const base = gitExec(`git merge-base HEAD "${branch}"`, cwd).trim();
|
|
229
|
+
const diff = gitExec(`git diff --name-status ${base} "${branch}"`, cwd);
|
|
230
|
+
for (const line of diff.split("\n")) {
|
|
231
|
+
if (!line)
|
|
232
|
+
continue;
|
|
233
|
+
const fields = line.split("\t");
|
|
234
|
+
const status = fields[0];
|
|
235
|
+
if (status.startsWith("R") || status.startsWith("C")) {
|
|
236
|
+
const from = fields[1];
|
|
237
|
+
const to = fields[2];
|
|
238
|
+
if (status.startsWith("R")) {
|
|
239
|
+
try {
|
|
240
|
+
gitExec(`git rm -f -- "${from}"`, cwd);
|
|
241
|
+
}
|
|
242
|
+
catch { }
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
gitExec(`git checkout "${branch}" -- "${to}"`, cwd);
|
|
246
|
+
gitExec(`git add -- "${to}"`, cwd);
|
|
247
|
+
}
|
|
248
|
+
catch { }
|
|
249
|
+
}
|
|
250
|
+
else if (status.startsWith("D")) {
|
|
251
|
+
try {
|
|
252
|
+
gitExec(`git rm -f -- "${fields[1]}"`, cwd);
|
|
253
|
+
}
|
|
254
|
+
catch { }
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
try {
|
|
258
|
+
gitExec(`git checkout "${branch}" -- "${fields[1]}"`, cwd);
|
|
259
|
+
gitExec(`git add -- "${fields[1]}"`, cwd);
|
|
260
|
+
}
|
|
261
|
+
catch { }
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const dirty = gitExec("git status --porcelain", cwd).trim();
|
|
265
|
+
if (!dirty)
|
|
266
|
+
return true;
|
|
267
|
+
gitExec(`git commit -m 'swarm: force-merge ${branch}'`, cwd);
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
try {
|
|
272
|
+
gitExec("git merge --abort", cwd);
|
|
273
|
+
}
|
|
274
|
+
catch { }
|
|
275
|
+
try {
|
|
276
|
+
gitExec("git reset --hard HEAD", cwd);
|
|
277
|
+
}
|
|
278
|
+
catch { }
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
168
282
|
export function warnDirtyTree(cwd, log) {
|
|
169
283
|
try {
|
|
170
284
|
const status = gitExec("git status --porcelain", cwd);
|
package/dist/planner-query.js
CHANGED
|
@@ -219,6 +219,19 @@ export function postProcess(raw, budget, onLog) {
|
|
|
219
219
|
tasks = tasks.filter((t) => t.prompt && t.prompt.trim().split(/\s+/).length >= 3);
|
|
220
220
|
if (tasks.length < before)
|
|
221
221
|
onLog(`Filtered ${before - tasks.length} task(s) with fewer than 3 words`);
|
|
222
|
+
// Read-only tasks (verify/audit/user-test) shouldn't get a worktree: they
|
|
223
|
+
// don't change files, so they'd just create empty swarm branches that show
|
|
224
|
+
// up as "0 files changed" noise. Run them in the real project directory so
|
|
225
|
+
// env files, dependencies, and local config are available.
|
|
226
|
+
let readOnly = 0;
|
|
227
|
+
for (const t of tasks) {
|
|
228
|
+
if (!t.noWorktree && /^\s*(verify|audit|user[- ]?test)\b/i.test(t.prompt)) {
|
|
229
|
+
t.noWorktree = true;
|
|
230
|
+
readOnly++;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (readOnly > 0)
|
|
234
|
+
onLog(`${readOnly} read-only task(s) marked noWorktree`);
|
|
222
235
|
const dominated = new Set();
|
|
223
236
|
for (let i = 0; i < tasks.length; i++) {
|
|
224
237
|
if (dominated.has(i))
|
package/dist/state.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFileSync, existsSync, mkdirSync, readdirSync, writeFileSync, symlin
|
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
+
import { forceMergeOverlay } from "./merge.js";
|
|
5
6
|
// ── File I/O helpers ──
|
|
6
7
|
export function readMdDir(dir) {
|
|
7
8
|
try {
|
|
@@ -472,8 +473,14 @@ export function autoMergeBranches(cwd, branches, onLog) {
|
|
|
472
473
|
execSync("git merge --abort", { cwd, encoding: "utf-8", stdio: "pipe" });
|
|
473
474
|
}
|
|
474
475
|
catch { }
|
|
475
|
-
br.
|
|
476
|
-
|
|
476
|
+
if (forceMergeOverlay(br.branch, cwd)) {
|
|
477
|
+
br.status = "merged";
|
|
478
|
+
onLog(` ✓ ${br.branch} (force-merged)`);
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
br.status = "merge-failed";
|
|
482
|
+
onLog(` ✗ ${br.branch} (conflict — preserved for manual merge)`);
|
|
483
|
+
}
|
|
477
484
|
}
|
|
478
485
|
}
|
|
479
486
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.16.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.16.4",
|
|
4
|
+
"description": "Local multi-session orchestrator for the Claude Agent SDK. Runs parallel Claude agents in git worktrees overnight — spend caps, rate-limit handling, crash-safe resume, multi-wave steering. Opus/Sonnet/Haiku + Qwen/OpenRouter. A local alternative to hosted agent harnesses.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"claude-overnight": "dist/bin.js"
|
|
@@ -29,19 +29,34 @@
|
|
|
29
29
|
},
|
|
30
30
|
"keywords": [
|
|
31
31
|
"claude",
|
|
32
|
+
"claude-code",
|
|
32
33
|
"claude-agent-sdk",
|
|
34
|
+
"agent-sdk",
|
|
35
|
+
"managed-agents",
|
|
36
|
+
"anthropic",
|
|
33
37
|
"ai-agents",
|
|
34
38
|
"ai-coding",
|
|
35
|
-
"parallel-agents",
|
|
36
39
|
"autonomous-coding",
|
|
40
|
+
"autonomous-agent",
|
|
41
|
+
"coding-agent",
|
|
42
|
+
"background-agent",
|
|
43
|
+
"async-coding",
|
|
44
|
+
"parallel-agents",
|
|
45
|
+
"multi-agent",
|
|
37
46
|
"swarm",
|
|
38
47
|
"overnight",
|
|
39
|
-
"
|
|
48
|
+
"overnight-coding",
|
|
40
49
|
"orchestration",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"anthropic",
|
|
50
|
+
"orchestrator",
|
|
51
|
+
"cli",
|
|
44
52
|
"worktrees",
|
|
53
|
+
"git-worktree",
|
|
54
|
+
"code-generation",
|
|
55
|
+
"code-automation",
|
|
56
|
+
"refactoring",
|
|
57
|
+
"migration",
|
|
58
|
+
"qwen",
|
|
59
|
+
"openrouter",
|
|
45
60
|
"iterative"
|
|
46
61
|
],
|
|
47
62
|
"engines": {
|