claude-overnight 1.16.2 → 1.16.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 +57 -38
- 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,12 @@
|
|
|
1
1
|
# claude-overnight
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**A background lane for your Claude Max plan.** Runs a capped swarm of Claude Agent SDK sessions in isolated git worktrees — stops at a usage cap you set, so your interactive Claude Code always has headroom. Rate-limited? It waits. Crash? It resumes with full context.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Your Max plan rate limits eat interactive coding time. One deep refactor and the 5-hour window is gone before lunch. `claude-overnight` runs background agent sessions up to the percentage cap you pick (90% is typical), leaving the rest free for your own Claude Code session. Hand it an objective and a session budget, walk away, review the diff when the run ends.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Isolated by default. Every agent runs in its own git worktree on its own branch, so a misbehaving agent can't trash your working tree. You choose what agents can do before the run starts — no surprise escalation mid-flight. Unmerged branches are preserved for manual review, never discarded. Built on the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) — not a Claude Code replacement, but a background lane that runs alongside it.
|
|
8
|
+
|
|
9
|
+
Different shape from hosted agent harnesses like [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview): instead of one agent in one cloud container billed separately, you get many parallel sessions on your own machine, in your real repo, against your own Max plan (or API key). Works with Claude Opus, Sonnet, and Haiku — or pair an Anthropic planner with a cheaper executor on Qwen, OpenRouter, or any Anthropic-compatible endpoint.
|
|
8
10
|
|
|
9
11
|
## Install
|
|
10
12
|
|
|
@@ -12,16 +14,7 @@ Different shape from hosted single-session runtimes like [Claude Managed Agents]
|
|
|
12
14
|
npm install -g claude-overnight
|
|
13
15
|
```
|
|
14
16
|
|
|
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
|
-
```
|
|
17
|
+
Requires Node.js ≥ 20 and Claude authentication (`claude auth login` or `ANTHROPIC_API_KEY`).
|
|
25
18
|
|
|
26
19
|
## Quick start
|
|
27
20
|
|
|
@@ -62,50 +55,69 @@ claude-overnight
|
|
|
62
55
|
|
|
63
56
|
◆ Thinking: 5 agents exploring... ← architects analyze your codebase
|
|
64
57
|
◆ Orchestrating plan... ← synthesizes 50 concrete tasks
|
|
65
|
-
◆ Wave 1 · 50 tasks · $4.20 spent ←
|
|
58
|
+
◆ Wave 1 · 50 tasks · $4.20 spent ← runs unattended from here
|
|
66
59
|
↑ 1.2M in ↓ 340K out $4.20 / $4.24 total
|
|
67
60
|
◆ Assessing... how close to amazing?
|
|
68
61
|
◆ Wave 2 · 30 tasks · $18.50 spent ← improvements from assessment
|
|
69
62
|
◆ Reflection: 2 agents reviewing ← deep quality audit
|
|
70
63
|
◆ Wave 3 · 20 tasks · $31.00 spent ← fixes from review findings
|
|
71
|
-
◆ Assessing... ✓
|
|
64
|
+
◆ Assessing... ✓ Done
|
|
72
65
|
```
|
|
73
66
|
|
|
74
|
-
You interact once (objective, budget, model, review themes), then
|
|
67
|
+
You interact once (objective, budget, model, review themes), then the rest runs unattended — 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.
|
|
68
|
+
|
|
69
|
+
## How it differs
|
|
70
|
+
|
|
71
|
+
- vs **Claude Code**: many agents, no driver, capped so your Claude Code session keeps its headroom
|
|
72
|
+
- vs **[Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview)**: on your machine, against your Max plan, in your real git history — not a cloud container billed separately
|
|
73
|
+
- vs **Cursor / Copilot / Cline**: asynchronous, off the keyboard
|
|
74
|
+
|
|
75
|
+
## Use cases
|
|
76
|
+
|
|
77
|
+
- **Overnight refactors** — "Modernize the auth system" at budget 200.
|
|
78
|
+
- **Batch feature implementation** — dozens of features from a task file, parallelized.
|
|
79
|
+
- **Codebase-wide cleanups** — deduplicate, simplify, rename, normalize.
|
|
80
|
+
- **Test generation at scale** — integration tests for every route or module.
|
|
81
|
+
- **Documentation sprints** — API docs, READMEs, inline comments, changelogs.
|
|
82
|
+
- **Framework migrations** — version upgrades, type annotations, config format swaps.
|
|
83
|
+
- **Quality audits** — reflection waves surface architectural issues and code smells.
|
|
84
|
+
- **Long research runs** — architect sessions explore a large codebase before any code lands.
|
|
85
|
+
|
|
86
|
+
Typical shape: one objective + a $20–$200 spend cap + walk away.
|
|
75
87
|
|
|
76
88
|
## How it works
|
|
77
89
|
|
|
78
|
-
### 1. Thinking
|
|
90
|
+
### 1. Thinking phase — parallel architect sessions
|
|
79
91
|
|
|
80
92
|
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
93
|
|
|
82
|
-
### 2.
|
|
94
|
+
### 2. Task orchestration
|
|
83
95
|
|
|
84
|
-
An orchestrator
|
|
96
|
+
An orchestrator session reads all design documents and synthesizes concrete execution tasks — grounded in real files and patterns the architects found. The task plan is also written to a file for resilience — if orchestration is interrupted, partial results survive.
|
|
85
97
|
|
|
86
|
-
### 3.
|
|
98
|
+
### 3. Parallel execution waves
|
|
87
99
|
|
|
88
|
-
Tasks run in parallel (each
|
|
100
|
+
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
101
|
|
|
90
102
|
After each wave, steering assesses: "how good is this?" — not "what's missing?" It can:
|
|
91
103
|
|
|
92
104
|
- **Execute** more tasks to build features, fix bugs, polish UX
|
|
93
|
-
- **Reflect** by spinning up 1-2 review
|
|
105
|
+
- **Reflect** by spinning up 1-2 review sessions for deep quality/architecture audits
|
|
94
106
|
- **Declare done** when the vision is met at high quality
|
|
95
107
|
|
|
96
|
-
### 4. Goal refinement
|
|
108
|
+
### 4. Goal refinement and steering
|
|
97
109
|
|
|
98
|
-
The tool starts with your broad objective but
|
|
110
|
+
The tool starts with your broad objective but refines its definition of quality as it learns your codebase. Steering updates the goal after each wave. Late waves are informed by early discoveries.
|
|
99
111
|
|
|
100
|
-
### 5. Three-layer context
|
|
112
|
+
### 5. Three-layer context memory
|
|
101
113
|
|
|
102
114
|
Long runs stay sharp because steering maintains three layers of memory:
|
|
103
115
|
|
|
104
116
|
- **Status** — a living project snapshot, updated every wave. Compressed, never truncated.
|
|
105
117
|
- **Milestones** — strategic snapshots archived every ~5 waves. Long-term memory.
|
|
106
|
-
- **Goal** — the evolving north star. What
|
|
118
|
+
- **Goal** — the evolving north star. What quality means for this codebase.
|
|
107
119
|
|
|
108
|
-
## Run history and
|
|
120
|
+
## Run history, resume, and knowledge carryforward
|
|
109
121
|
|
|
110
122
|
Every run gets its own folder in `.claude-overnight/runs/`. Nothing is ever overwritten.
|
|
111
123
|
|
|
@@ -133,7 +145,7 @@ Any run that stops before the steering system declares the objective complete
|
|
|
133
145
|
|
|
134
146
|
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
147
|
|
|
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
|
|
148
|
+
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
149
|
|
|
138
150
|
```
|
|
139
151
|
✓ Reusing 5 design docs (from prior attempt)
|
|
@@ -143,15 +155,13 @@ If the thinking phase succeeds but orchestration crashes, the next run detects t
|
|
|
143
155
|
...
|
|
144
156
|
```
|
|
145
157
|
|
|
146
|
-
**Knowledge carries forward** — new runs inherit knowledge from completed previous runs. Thinking
|
|
158
|
+
**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
159
|
|
|
148
160
|
Add `.claude-overnight/` to your `.gitignore` (with the trailing slash — see below).
|
|
149
161
|
|
|
150
162
|
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
163
|
|
|
152
|
-
##
|
|
153
|
-
|
|
154
|
-
### Task file
|
|
164
|
+
## Task file and inline modes
|
|
155
165
|
|
|
156
166
|
```bash
|
|
157
167
|
claude-overnight tasks.json
|
|
@@ -181,7 +191,7 @@ For multi-wave runs, add `objective` and `flexiblePlan`:
|
|
|
181
191
|
}
|
|
182
192
|
```
|
|
183
193
|
|
|
184
|
-
|
|
194
|
+
Inline:
|
|
185
195
|
|
|
186
196
|
```bash
|
|
187
197
|
claude-overnight "fix auth bug in src/auth.ts" "add tests for user model"
|
|
@@ -215,7 +225,7 @@ claude-overnight "fix auth bug in src/auth.ts" "add tests for user model"
|
|
|
215
225
|
| `mergeStrategy` | `"yolo" \| "branch"` | `"yolo"` | Merge into HEAD or new branch |
|
|
216
226
|
| `usageCap` | `number (0-100)` | unlimited | Stop at N% utilization |
|
|
217
227
|
|
|
218
|
-
## Custom providers (Qwen, OpenRouter,
|
|
228
|
+
## Custom providers (Qwen, OpenRouter, any Anthropic-compatible endpoint)
|
|
219
229
|
|
|
220
230
|
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
231
|
|
|
@@ -245,7 +255,7 @@ Saved providers live user-level at `~/.claude/claude-overnight/providers.json` (
|
|
|
245
255
|
|
|
246
256
|
**Non-interactive / CI.** `claude-overnight --model=qwen3-coder-plus` auto-resolves the model id to a saved provider — no separate `--provider` flag.
|
|
247
257
|
|
|
248
|
-
##
|
|
258
|
+
## Spend caps and usage controls
|
|
249
259
|
|
|
250
260
|
### Extra usage protection
|
|
251
261
|
|
|
@@ -272,7 +282,7 @@ The usage bar cycles through all rate limit windows (5h, 7d, etc.) every 3 secon
|
|
|
272
282
|
|
|
273
283
|
When using extra usage with a budget, a dedicated progress bar shows spend vs limit with color-coded fill (magenta → yellow → red).
|
|
274
284
|
|
|
275
|
-
## Rate
|
|
285
|
+
## Rate-limit handling and crash-safe recovery
|
|
276
286
|
|
|
277
287
|
Built for unattended runs lasting hours or days.
|
|
278
288
|
|
|
@@ -286,15 +296,24 @@ Built for unattended runs lasting hours or days.
|
|
|
286
296
|
- **Usage cap**: set a ceiling, active agents finish, no new ones start — run is resumable
|
|
287
297
|
- **Planner retries**: steering and orchestration retry on rate limits (30s/60s/120s backoff) with full context
|
|
288
298
|
|
|
289
|
-
##
|
|
299
|
+
## Git worktrees and branch merging
|
|
290
300
|
|
|
291
|
-
Each agent gets an isolated git worktree (`swarm/task-N` branch). Changes auto-commit. After all
|
|
301
|
+
Each agent session gets an isolated git worktree (`swarm/task-N` branch). Changes auto-commit. After all sessions complete, branches merge back.
|
|
292
302
|
|
|
293
303
|
- `"yolo"` (default): merges into your current branch
|
|
294
304
|
- `"branch"`: creates a new `swarm/run-{timestamp}` branch
|
|
295
305
|
|
|
296
306
|
Conflicts retry with `-X theirs`. Unresolved branches are preserved for manual merge.
|
|
297
307
|
|
|
308
|
+
## Claude Code plugin
|
|
309
|
+
|
|
310
|
+
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:
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
/plugin marketplace add Fornace/claude-overnight
|
|
314
|
+
/plugin install claude-overnight
|
|
315
|
+
```
|
|
316
|
+
|
|
298
317
|
## Exit codes
|
|
299
318
|
|
|
300
319
|
| 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.5",
|
|
4
|
+
"description": "Background lane for your Claude Max plan. Parallel Claude Agent SDK sessions in git worktrees with a usage cap that reserves headroom for your interactive Claude Code. Crash-safe resume. Opus/Sonnet/Haiku + Qwen/OpenRouter.",
|
|
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": {
|