nightytidy 0.2.9 → 0.2.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nightytidy",
3
- "version": "0.2.9",
3
+ "version": "0.2.12",
4
4
  "description": "Automated overnight codebase improvement through Claude Code",
5
5
  "license": "MIT",
6
6
  "author": "Dorian Spitz",
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
2
2
  import path from 'node:path';
3
3
  import { debug, warn, error as logError } from '../logger.js';
4
4
 
5
- const INIT_TIMEOUT_MS = 5 * 60_000; // 5 minutes — init should never take this long
5
+ const INIT_TIMEOUT_MS = 10 * 60_000; // 10 minutes — generous but not infinite
6
6
  const FINISH_TIMEOUT_MS = 10 * 60_000; // 10 minutes — finish includes report generation
7
7
 
8
8
  export class CliBridge {
@@ -378,6 +378,12 @@ export async function startAgent() {
378
378
 
379
379
  case 'cancel-queued': {
380
380
  runQueue.cancel(msg.runId);
381
+ // Also notify Firestore — the run may exist there even if not in local queue
382
+ // (e.g. orphaned after a timeout/crash where the agent already discarded it)
383
+ dispatchWithQueue('run_failed', {
384
+ projectId: msg.projectId || '',
385
+ run: { id: msg.runId },
386
+ }, []);
381
387
  wsServer.broadcast({ type: 'queue-updated', queue: runQueue.getQueue() });
382
388
  reply({ type: 'queue-updated', queue: runQueue.getQueue() });
383
389
  break;
@@ -527,6 +533,8 @@ export async function startAgent() {
527
533
  ? 'Initialization timed out — Claude Code may be unavailable. Restart the agent to retry.'
528
534
  : (initResult.parsed?.error || initResult.stderr || 'Unknown init error');
529
535
  info(` ✗ Init failed: ${errorMsg}`);
536
+ if (initResult.stdout) debug(` Init stdout: ${initResult.stdout.slice(-500)}`);
537
+ if (initResult.stderr) debug(` Init stderr: ${initResult.stderr.slice(-500)}`);
530
538
  wsServer.broadcast({ type: 'run-failed', runId: run.id, error: errorMsg });
531
539
  dispatchWithQueue('run_failed', {
532
540
  project: project.name,
@@ -1080,22 +1088,35 @@ export async function startAgent() {
1080
1088
  const progress = interrupted.lastProgress || {};
1081
1089
  const completed = progress.completedCount || 0;
1082
1090
  const total = interrupted.steps?.length || 0;
1083
- info(`Found interrupted run: ${projName} (${completed}/${total} steps completed)`);
1084
- info(` Run ID: ${interrupted.id}`);
1085
- info(` Use the web app to Resume, Finish with Partial Results, or Discard`);
1086
1091
 
1087
- // Best-effort: notify Firestore that this run is interrupted
1088
- // (in case the shutdown webhook didn't make it)
1089
- if (proj) {
1090
- dispatchWithQueue('run_interrupted', {
1091
- projectId: proj.id,
1092
- run: {
1093
- id: interrupted.id,
1094
- completedSteps: completed,
1095
- failedSteps: progress.failedCount || 0,
1096
- totalCost: progress.totalCost || 0,
1097
- },
1092
+ if (completed === 0) {
1093
+ // Run never actually started (init timed out or crashed before any steps).
1094
+ // Auto-discard — there's nothing to resume or finish, and blocking the
1095
+ // queue for user action is pointless.
1096
+ info(`Auto-discarding interrupted run with 0 completed steps: ${projName} (${interrupted.id})`);
1097
+ runQueue.clearInterrupted();
1098
+ dispatchWithQueue('run_failed', {
1099
+ projectId: proj?.id || interrupted.projectId,
1100
+ run: { id: interrupted.id },
1098
1101
  }, []);
1102
+ } else {
1103
+ info(`Found interrupted run: ${projName} (${completed}/${total} steps completed)`);
1104
+ info(` Run ID: ${interrupted.id}`);
1105
+ info(` Use the web app to Resume, Finish with Partial Results, or Discard`);
1106
+
1107
+ // Best-effort: notify Firestore that this run is interrupted
1108
+ // (in case the shutdown webhook didn't make it)
1109
+ if (proj) {
1110
+ dispatchWithQueue('run_interrupted', {
1111
+ projectId: proj.id,
1112
+ run: {
1113
+ id: interrupted.id,
1114
+ completedSteps: completed,
1115
+ failedSteps: progress.failedCount || 0,
1116
+ totalCost: progress.totalCost || 0,
1117
+ },
1118
+ }, []);
1119
+ }
1099
1120
  }
1100
1121
  }
1101
1122
 
@@ -1103,8 +1124,13 @@ export async function startAgent() {
1103
1124
  // (agent died without graceful shutdown, so markInterrupted was never called)
1104
1125
  const current = runQueue.getCurrent();
1105
1126
  if (current && current.status === 'running' && !activeBridge) {
1106
- info(`Found orphaned running run: ${current.id}marking as interrupted`);
1107
- runQueue.markInterrupted({ completedCount: 0, failedCount: 0, totalCost: 0, stepList: [], currentStepNum: null });
1127
+ // Orphaned run with no progress auto-discard instead of blocking the queue
1128
+ info(`Found orphaned running run: ${current.id} auto-discarding (0 steps completed)`);
1129
+ runQueue.completeCurrent({ success: false });
1130
+ dispatchWithQueue('run_failed', {
1131
+ projectId: current.projectId,
1132
+ run: { id: current.id },
1133
+ }, []);
1108
1134
  }
1109
1135
 
1110
1136
  // Process any queued runs left from a previous session
package/src/checks.js CHANGED
@@ -203,18 +203,19 @@ async function checkClaudeAuthenticated() {
203
203
  async function getFreeBytesWindows(projectDir) {
204
204
  const driveLetter = projectDir.charAt(0).toUpperCase();
205
205
  // Try PowerShell first (wmic is deprecated on newer Windows)
206
+ // 10s timeout — PowerShell can hang on OneDrive/network drive systems
206
207
  const psResult = await runCommand('powershell', [
207
208
  '-NoProfile', '-Command',
208
209
  `(Get-PSDrive ${driveLetter}).Free`,
209
- ]);
210
+ ], { timeoutMs: 10_000 });
210
211
  const psMatch = psResult.stdout.trim().match(/^(\d+)$/);
211
212
  if (psResult.code === 0 && psMatch) {
212
213
  return parseInt(psMatch[1], 10);
213
214
  }
214
- // Fallback to wmic for older Windows
215
+ // Fallback to wmic for older Windows (also with timeout)
215
216
  const result = await runCommand('wmic', [
216
217
  'logicaldisk', 'where', `DeviceID='${driveLetter}:'`, 'get', 'FreeSpace',
217
- ]);
218
+ ], { timeoutMs: 10_000 });
218
219
  const match = result.stdout.match(/(\d+)/);
219
220
  return match ? parseInt(match[1], 10) : null;
220
221
  }