great-cto 3.28.3 → 3.28.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "great-cto",
3
3
  "description": "You already have the agent. This is everything around it. great_cto runs Claude Code as a pipeline of 70 specialist agents \u2014 an independent model checks each stage before the next builds on it, spending caps refuse rather than warn, and three decisions stay yours: what gets built, how, and whether it ships.",
4
- "version": "3.28.3",
4
+ "version": "3.28.5",
5
5
  "author": {
6
6
  "name": "Alexander Velikiy",
7
7
  "url": "https://hashnode.com/@Greatcto"
@@ -85,24 +85,43 @@ export function runCodexExec({
85
85
  if (cwd) args.push('-C', cwd);
86
86
  args.push(...extraArgs, '-');
87
87
 
88
- const proc = spawn(bin, args, { cwd, stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env } });
88
+ // Its own process group, so a timeout stops the CLI AND whatever it started.
89
+ // Killing only the CLI left its children running — the same orphan that kept
90
+ // a release gate's test runner alive for a day (2026-09-10).
91
+ const group = process.platform !== 'win32';
92
+ const proc = spawn(bin, args, { cwd, stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env }, detached: group });
89
93
  let out = '';
90
94
  let err = '';
91
- const timer = setTimeout(() => { try { proc.kill('SIGKILL'); } catch { /* gone */ } }, timeoutMs);
95
+ let timedOut = false;
96
+ const killGroup = () => {
97
+ try {
98
+ if (group && proc.pid) process.kill(-proc.pid, 'SIGKILL');
99
+ else proc.kill('SIGKILL');
100
+ } catch { try { proc.kill('SIGKILL'); } catch { /* gone */ } }
101
+ };
102
+ const timer = setTimeout(() => { timedOut = true; killGroup(); }, timeoutMs);
92
103
 
93
104
  proc.stdout.on('data', (b) => { out += String(b); });
94
105
  proc.stderr.on('data', (b) => { err += String(b); });
95
106
  proc.on('error', (e) => {
96
107
  clearTimeout(timer);
97
- resolve({ state: 'unreadable', text: null, usage: null, errors: [String(e.message || e)], code: null, model });
108
+ resolve({ state: 'unreadable', text: null, usage: null, errors: [String(e.message || e)], code: null, model, timedOut });
98
109
  });
99
110
  proc.on('close', (code) => {
100
111
  clearTimeout(timer);
112
+ // Best effort: a child the CLI left behind must not outlive the review.
113
+ if (group && proc.pid) { try { process.kill(-proc.pid, 'SIGKILL'); } catch { /* group already gone */ } }
101
114
  const parsed = parseCodexStream(out);
102
115
  // stderr is kept even on success: Codex writes warnings there that change
103
116
  // how a result should be read.
104
117
  if (err.trim()) parsed.errors.push(err.trim().slice(0, 500));
105
- resolve({ ...parsed, code, model });
118
+ // A run cut off by the clock may have printed a verdict before it was done.
119
+ // That is a truncated answer, and a truncated answer is not an answer.
120
+ if (timedOut) {
121
+ parsed.errors.push(`timed out after ${timeoutMs}ms`);
122
+ if (parsed.state === 'ok') parsed.state = 'unreadable';
123
+ }
124
+ resolve({ ...parsed, code, model, timedOut });
106
125
  });
107
126
 
108
127
  proc.stdin.write(prompt);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "great-cto",
3
- "version": "3.28.3",
3
+ "version": "3.28.5",
4
4
  "description": "One command install for the great_cto Claude Code plugin. Auto-detects your stack, picks the right archetype, bootstraps PROJECT.md.",
5
5
  "keywords": [
6
6
  "claude-code",