nightytidy 0.2.7 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nightytidy",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Automated overnight codebase improvement through Claude Code",
5
5
  "license": "MIT",
6
6
  "author": "Dorian Spitz",
@@ -76,7 +76,7 @@ export class CliBridge {
76
76
  }
77
77
 
78
78
  _run(args, onOutput, opts = {}) {
79
- return new Promise((resolve, reject) => {
79
+ return new Promise((resolve) => {
80
80
  const binPath = path.resolve(import.meta.dirname, '../../bin/nightytidy.js');
81
81
  const proc = spawn('node', [binPath, ...args], {
82
82
  cwd: this.projectDir,
@@ -87,15 +87,40 @@ export class CliBridge {
87
87
  let stdout = '';
88
88
  let stderr = '';
89
89
  let killed = false;
90
+ let settled = false;
91
+
92
+ const settle = (result) => {
93
+ if (settled) return;
94
+ settled = true;
95
+ if (timer) clearTimeout(timer);
96
+ if (killTimer) clearTimeout(killTimer);
97
+ resolve(result);
98
+ };
90
99
 
91
100
  // Timeout — kill the process if it takes too long
92
101
  let timer = null;
102
+ let killTimer = null;
93
103
  if (opts.timeout) {
94
104
  timer = setTimeout(() => {
95
105
  killed = true;
96
106
  const timeoutSec = Math.round(opts.timeout / 1000);
97
107
  warn(`CLI process timed out after ${timeoutSec}s: ${args.join(' ')}`);
98
108
  this.kill();
109
+ // On Windows, taskkill is fire-and-forget — the 'close' event may
110
+ // never fire. Force-resolve after 5s to prevent the agent from
111
+ // hanging forever.
112
+ killTimer = setTimeout(() => {
113
+ warn(`CLI process did not exit within 5s after kill — force-resolving`);
114
+ this.activeProcess = null;
115
+ settle({
116
+ success: false,
117
+ exitCode: -1,
118
+ stdout,
119
+ stderr: `Process timed out after ${timeoutSec}s — Claude Code may be unavailable`,
120
+ parsed: CliBridge.parseOutput(stdout),
121
+ timedOut: true,
122
+ });
123
+ }, 5000);
99
124
  }, opts.timeout);
100
125
  }
101
126
 
@@ -127,26 +152,23 @@ export class CliBridge {
127
152
  });
128
153
 
129
154
  proc.on('close', (code) => {
130
- if (timer) clearTimeout(timer);
131
155
  this.activeProcess = null;
132
- const parsed = CliBridge.parseOutput(stdout);
133
- resolve({
156
+ settle({
134
157
  success: code === 0 && !killed,
135
158
  exitCode: code,
136
159
  stdout,
137
160
  stderr: killed
138
161
  ? `Process timed out after ${Math.round(opts.timeout / 1000)}s — Claude Code may be unavailable`
139
162
  : stderr,
140
- parsed,
163
+ parsed: CliBridge.parseOutput(stdout),
141
164
  timedOut: killed,
142
165
  });
143
166
  });
144
167
 
145
168
  proc.on('error', (err) => {
146
- if (timer) clearTimeout(timer);
147
169
  this.activeProcess = null;
148
170
  logError(`CLI process error: ${err.message}`);
149
- resolve({
171
+ settle({
150
172
  success: false,
151
173
  exitCode: -1,
152
174
  stdout,
@@ -372,6 +372,7 @@ export async function startAgent() {
372
372
  run: { id: interrupted.id },
373
373
  }, []);
374
374
  reply({ type: 'interrupted-discarded', runId: interrupted.id });
375
+ if (!runQueue.getCurrent()) processQueue();
375
376
  break;
376
377
  }
377
378
 
@@ -717,7 +718,8 @@ export async function startAgent() {
717
718
 
718
719
  stopHeartbeat();
719
720
  info(` Finishing run (report + merge)...`);
720
- await bridge.finishRun();
721
+ const finishResult = await bridge.finishRun();
722
+ const reportMarkdown = finishResult?.parsed?.reportContent || null;
721
723
  projectManager.updateProject(run.projectId, { lastRunAt: Date.now() });
722
724
 
723
725
  const elapsedMs = Date.now() - run.startedAt;
@@ -733,6 +735,7 @@ export async function startAgent() {
733
735
  project: project.name,
734
736
  projectId: project.id,
735
737
  run: { id: run.id, totalSteps, completedSteps: run.steps.length, elapsedMs: Date.now() - run.startedAt },
738
+ reportMarkdown,
736
739
  }, project.webhooks);
737
740
 
738
741
  activeBridge = null;
@@ -927,7 +930,8 @@ export async function startAgent() {
927
930
  // Finish the run
928
931
  stopHeartbeat();
929
932
  info(` Finishing resumed run (report + merge)...`);
930
- await bridge.finishRun();
933
+ const finishResult = await bridge.finishRun();
934
+ const reportMarkdown = finishResult?.parsed?.reportContent || null;
931
935
  projectManager.updateProject(interrupted.projectId, { lastRunAt: Date.now() });
932
936
 
933
937
  wsServer.broadcast({ type: 'run-completed', runId: interrupted.id, results: {} });
@@ -936,6 +940,7 @@ export async function startAgent() {
936
940
  dispatchWithQueue('run_completed', {
937
941
  project: project.name, projectId: project.id,
938
942
  run: { id: interrupted.id, totalSteps: interrupted.steps.length, completedSteps: runProgress.completedCount, elapsedMs: Date.now() - interrupted.startedAt },
943
+ reportMarkdown,
939
944
  }, project.webhooks);
940
945
 
941
946
  activeBridge = null;
@@ -956,11 +961,13 @@ export async function startAgent() {
956
961
  interrupted.status = 'running';
957
962
  runQueue._save();
958
963
 
964
+ let finishResult = null;
959
965
  try {
960
- await bridge.finishRun();
966
+ finishResult = await bridge.finishRun();
961
967
  } catch (err) {
962
968
  warn(` finishRun failed: ${err.message}`);
963
969
  }
970
+ const reportMarkdown = finishResult?.parsed?.reportContent || null;
964
971
 
965
972
  projectManager.updateProject(interrupted.projectId, { lastRunAt: Date.now() });
966
973
  wsServer.broadcast({ type: 'run-completed', runId: interrupted.id, status: 'completed', results: {} });
@@ -969,6 +976,7 @@ export async function startAgent() {
969
976
  dispatchWithQueue('run_completed', {
970
977
  project: project.name, projectId: project.id,
971
978
  run: { id: interrupted.id, totalSteps: interrupted.steps.length, completedSteps: interrupted.lastProgress?.completedCount || 0, elapsedMs: Date.now() - interrupted.startedAt },
979
+ reportMarkdown,
972
980
  }, project.webhooks);
973
981
 
974
982
  activeBridge = null;
@@ -1099,6 +1107,15 @@ export async function startAgent() {
1099
1107
  runQueue.markInterrupted({ completedCount: 0, failedCount: 0, totalCost: 0, stepList: [], currentStepNum: null });
1100
1108
  }
1101
1109
 
1110
+ // Process any queued runs left from a previous session
1111
+ if (!runQueue.getInterrupted() && !runQueue.getCurrent()) {
1112
+ const pending = runQueue.getQueue();
1113
+ if (pending.length > 0) {
1114
+ info(`Found ${pending.length} queued run(s) — starting queue processing`);
1115
+ processQueue();
1116
+ }
1117
+ }
1118
+
1102
1119
  // Print startup info
1103
1120
  console.log(`\nNightyTidy Agent v${AGENT_VERSION}`);
1104
1121
  console.log(`WebSocket: ws://127.0.0.1:${actualPort}`);