claude-overnight 1.11.7 → 1.11.9
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/dist/index.js +19 -8
- package/dist/merge.js +7 -2
- package/dist/state.d.ts +13 -0
- package/dist/state.js +64 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { RunDisplay } from "./ui.js";
|
|
|
11
11
|
import { renderSummary } from "./render.js";
|
|
12
12
|
import { executeRun } from "./run.js";
|
|
13
13
|
import { parseCliFlags, isAuthError, fetchModels, ask, select, selectKey, loadTaskFile, validateConcurrency, isGitRepo, validateGitRepo, showPlan, BRAILLE, makeProgressLog, } from "./cli.js";
|
|
14
|
-
import { loadRunState, findIncompleteRuns, findOrphanedDesigns, formatTimeAgo, showRunHistory, readPreviousRunKnowledge, createRunDir, updateLatestSymlink, readMdDir, saveRunState, autoMergeBranches, } from "./state.js";
|
|
14
|
+
import { loadRunState, findIncompleteRuns, findOrphanedDesigns, backfillOrphanedPlans, formatTimeAgo, showRunHistory, readPreviousRunKnowledge, createRunDir, updateLatestSymlink, readMdDir, saveRunState, autoMergeBranches, } from "./state.js";
|
|
15
15
|
function countTasksInFile(path) {
|
|
16
16
|
try {
|
|
17
17
|
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -123,6 +123,12 @@ async function main() {
|
|
|
123
123
|
// ── Run history ──
|
|
124
124
|
const rootDir = join(cwd, ".claude-overnight");
|
|
125
125
|
const runsDir = join(rootDir, "runs");
|
|
126
|
+
// Backfill run.json for pre-1.11.7 orphaned plans so they become visible
|
|
127
|
+
// to the resume picker. One-shot, idempotent, silent if there's nothing.
|
|
128
|
+
const backfilled = backfillOrphanedPlans(rootDir, cwd);
|
|
129
|
+
if (backfilled > 0 && !noTTY) {
|
|
130
|
+
console.log(chalk.dim(`\n ↻ Recovered ${backfilled} orphaned plan${backfilled > 1 ? "s" : ""} from disk`));
|
|
131
|
+
}
|
|
126
132
|
const allRuns = [];
|
|
127
133
|
try {
|
|
128
134
|
for (const d of readdirSync(runsDir).sort().reverse()) {
|
|
@@ -256,17 +262,22 @@ async function main() {
|
|
|
256
262
|
}
|
|
257
263
|
}
|
|
258
264
|
if (resuming && resumeState && resumeRunDir) {
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
|
|
265
|
+
// If currentTasks is empty but tasks.json exists on disk, reload it.
|
|
266
|
+
// Covers two cases:
|
|
267
|
+
// 1. Planning-phase resumes (the prior run died before executeRun).
|
|
268
|
+
// 2. Stopped/capped runs whose state was saved with currentTasks: []
|
|
269
|
+
// (saveRunState always stores [] — the plan is on disk in tasks.json).
|
|
270
|
+
if (resumeState.currentTasks.length === 0) {
|
|
263
271
|
const loaded = salvageFromFile(join(resumeRunDir, "tasks.json"), resumeState.budget, () => { }, "resume");
|
|
264
|
-
if (!loaded) {
|
|
272
|
+
if (!loaded && resumeState.phase === "planning") {
|
|
265
273
|
console.error(chalk.red(`\n Planning-phase run has no usable tasks.json — start Fresh instead.\n`));
|
|
266
274
|
process.exit(1);
|
|
267
275
|
}
|
|
268
|
-
|
|
269
|
-
|
|
276
|
+
if (loaded) {
|
|
277
|
+
resumeState.currentTasks = loaded;
|
|
278
|
+
const label = resumeState.phase === "planning" ? "Resuming plan" : `Resuming ${resumeState.phase} run`;
|
|
279
|
+
console.log(chalk.green(`\n ✓ ${label} · ${loaded.length} tasks loaded from tasks.json`));
|
|
280
|
+
}
|
|
270
281
|
}
|
|
271
282
|
const unmerged = resumeState.branches.filter(b => b.status === "unmerged").length;
|
|
272
283
|
if (unmerged > 0) {
|
package/dist/merge.js
CHANGED
|
@@ -162,11 +162,16 @@ export function cleanStaleWorktrees(cwd, log) {
|
|
|
162
162
|
try {
|
|
163
163
|
const list = gitExec("git worktree list --porcelain", cwd);
|
|
164
164
|
const stale = [];
|
|
165
|
-
|
|
165
|
+
// Match any worktree whose path contains our mkdtemp prefix. We used to
|
|
166
|
+
// gate on `startsWith(tmpdir())` too, but on macOS `os.tmpdir()` returns
|
|
167
|
+
// `/var/folders/...` while git reports worktrees as `/private/var/...`
|
|
168
|
+
// (realpath-resolved), so the prefix never matched and stale worktrees
|
|
169
|
+
// silently accumulated. The `claude-overnight-` substring is unambiguous
|
|
170
|
+
// enough on its own — nothing else in the repo uses that prefix.
|
|
166
171
|
for (const line of list.split("\n")) {
|
|
167
172
|
if (line.startsWith("worktree ")) {
|
|
168
173
|
const wpath = line.slice("worktree ".length);
|
|
169
|
-
if (wpath.
|
|
174
|
+
if (wpath.includes("/claude-overnight-"))
|
|
170
175
|
stale.push(wpath);
|
|
171
176
|
}
|
|
172
177
|
}
|
package/dist/state.d.ts
CHANGED
|
@@ -36,6 +36,19 @@ export declare function findIncompleteRuns(rootDir: string, filterCwd: string):
|
|
|
36
36
|
state: RunState;
|
|
37
37
|
}[];
|
|
38
38
|
export declare function findOrphanedDesigns(rootDir: string): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Backfill run.json for pre-1.11.7 orphaned plans: runs where orchestrate's
|
|
41
|
+
* agent wrote tasks.json via its Write tool but the process died before
|
|
42
|
+
* executeRun ever got to saveRunState. Without this, those runs are invisible
|
|
43
|
+
* to findIncompleteRuns forever.
|
|
44
|
+
*
|
|
45
|
+
* Idempotent: runs with an existing run.json are skipped. Synthesizes a
|
|
46
|
+
* minimal "planning" state from what can be read off disk — dir name for
|
|
47
|
+
* timestamp, task count for budget, sane defaults for everything else.
|
|
48
|
+
* The cwd field is set to filterCwd so findIncompleteRuns picks it up on the
|
|
49
|
+
* current project (which is safe — rootDir is already scoped to `cwd`).
|
|
50
|
+
*/
|
|
51
|
+
export declare function backfillOrphanedPlans(rootDir: string, filterCwd: string): number;
|
|
39
52
|
export declare function formatTimeAgo(isoStr: string): string;
|
|
40
53
|
export declare function showRunHistory(allRuns: {
|
|
41
54
|
dir: string;
|
package/dist/state.js
CHANGED
|
@@ -219,6 +219,70 @@ export function findOrphanedDesigns(rootDir) {
|
|
|
219
219
|
catch { }
|
|
220
220
|
return null;
|
|
221
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Backfill run.json for pre-1.11.7 orphaned plans: runs where orchestrate's
|
|
224
|
+
* agent wrote tasks.json via its Write tool but the process died before
|
|
225
|
+
* executeRun ever got to saveRunState. Without this, those runs are invisible
|
|
226
|
+
* to findIncompleteRuns forever.
|
|
227
|
+
*
|
|
228
|
+
* Idempotent: runs with an existing run.json are skipped. Synthesizes a
|
|
229
|
+
* minimal "planning" state from what can be read off disk — dir name for
|
|
230
|
+
* timestamp, task count for budget, sane defaults for everything else.
|
|
231
|
+
* The cwd field is set to filterCwd so findIncompleteRuns picks it up on the
|
|
232
|
+
* current project (which is safe — rootDir is already scoped to `cwd`).
|
|
233
|
+
*/
|
|
234
|
+
export function backfillOrphanedPlans(rootDir, filterCwd) {
|
|
235
|
+
const runsDir = join(rootDir, "runs");
|
|
236
|
+
let count = 0;
|
|
237
|
+
try {
|
|
238
|
+
const dirs = readdirSync(runsDir);
|
|
239
|
+
for (const d of dirs) {
|
|
240
|
+
const runDir = join(runsDir, d);
|
|
241
|
+
if (existsSync(join(runDir, "run.json")))
|
|
242
|
+
continue;
|
|
243
|
+
const tasksFile = join(runDir, "tasks.json");
|
|
244
|
+
if (!existsSync(tasksFile))
|
|
245
|
+
continue;
|
|
246
|
+
let taskCount = 0;
|
|
247
|
+
try {
|
|
248
|
+
const parsed = JSON.parse(readFileSync(tasksFile, "utf-8"));
|
|
249
|
+
if (!Array.isArray(parsed?.tasks))
|
|
250
|
+
continue;
|
|
251
|
+
taskCount = parsed.tasks.length;
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (taskCount === 0)
|
|
257
|
+
continue;
|
|
258
|
+
// Dir name format: 2026-04-12T13-03-57 (UTC). Convert to ISO.
|
|
259
|
+
const m = d.match(/^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})$/);
|
|
260
|
+
const startedAt = m ? `${m[1]}T${m[2]}:${m[3]}:${m[4]}.000Z` : new Date(0).toISOString();
|
|
261
|
+
try {
|
|
262
|
+
saveRunState(runDir, {
|
|
263
|
+
id: d,
|
|
264
|
+
objective: `(recovered pre-1.11.7 plan · ${taskCount} tasks)`,
|
|
265
|
+
budget: taskCount, remaining: taskCount,
|
|
266
|
+
workerModel: "claude-opus-4-6", plannerModel: "claude-opus-4-6",
|
|
267
|
+
concurrency: 5, permissionMode: "bypassPermissions",
|
|
268
|
+
flex: false, useWorktrees: true, mergeStrategy: "yolo",
|
|
269
|
+
allowExtraUsage: false,
|
|
270
|
+
waveNum: 0, currentTasks: [],
|
|
271
|
+
accCost: 0, accCompleted: 0, accFailed: 0,
|
|
272
|
+
accIn: 0, accOut: 0, accTools: 0,
|
|
273
|
+
branches: [],
|
|
274
|
+
phase: "planning",
|
|
275
|
+
startedAt,
|
|
276
|
+
cwd: filterCwd,
|
|
277
|
+
});
|
|
278
|
+
count++;
|
|
279
|
+
}
|
|
280
|
+
catch { }
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch { }
|
|
284
|
+
return count;
|
|
285
|
+
}
|
|
222
286
|
// ── History display ──
|
|
223
287
|
export function formatTimeAgo(isoStr) {
|
|
224
288
|
const ms = Date.now() - new Date(isoStr).getTime();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.9",
|
|
4
4
|
"description": "Run 10, 100, or 1000 Claude agents overnight. Parallel autonomous AI coding with thinking waves, iterative quality steering, crash recovery, and rate limit handling. Built on the Claude Agent SDK.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|