great-cto 3.55.0 → 3.56.0

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 72 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.55.0",
4
+ "version": "3.56.0",
5
5
  "author": {
6
6
  "name": "Alexander Velikiy",
7
7
  "url": "https://hashnode.com/@Greatcto"
@@ -6296,16 +6296,19 @@ function usageAgentsCard(d) {
6296
6296
  const rate = a.stopRate == null ? absent('none', 'no run of this agent reached a conclusion in this period') : `<span class="usage-rate" aria-hidden="true"><span style="width:${Math.round(a.stopRate * 100)}%"></span></span>${Math.round(a.stopRate * 100)}%`;
6297
6297
  const disp = dispatched[a.agent];
6298
6298
  const gap = disp != null && disp > a.runs ? ` title="${esc(`dispatched ${disp} times in Claude Code, ${a.runs} verdicts recorded — the rest ran without one`)}"` : '';
6299
- return `<tr><td>${esc(a.agent)}</td><td class="num">${a.runs}</td><td class="num">${a.pass}</td><td class="num">${a.stopped}</td><td class="num">${a.failed}</td><td class="num">${rate}</td><td class="num"${gap}>${disp == null ? '—' : disp}</td></tr>`;
6299
+ const nv = a.noVerdictTotal || 0;
6300
+ const why = Object.entries(a.noVerdict || {}).map(([k, n]) => `${n} ${k === 'reported' ? 'finished without recording one' : k === 'cut-off' ? 'cut off mid-run' : 'ended, shape unknown'}`).join(', ');
6301
+ const noVerdict = nv ? `<span title="${esc(why)}">${nv}</span>` : '0';
6302
+ return `<tr><td>${esc(a.agent)}</td><td class="num">${a.runs}</td><td class="num">${a.pass}</td><td class="num">${a.stopped}</td><td class="num">${a.failed}</td><td class="num">${rate}</td><td class="num">${noVerdict}</td><td class="num"${gap}>${disp == null ? '—' : disp}</td></tr>`;
6300
6303
  }).join('');
6301
6304
  const notes = [];
6302
6305
  if (A.agents.some((a) => a.unknown)) notes.push(`${A.agents.reduce((n, a) => n + a.unknown, 0)} verdicts used a word no rule classifies — counted as runs, not as passes.`);
6303
6306
  if (A.other && A.other.runs) notes.push(`${A.other.runs} verdicts from names outside great_cto's roster (${esc(A.other.names.slice(0, 4).join(', '))}) are not shown.`);
6304
6307
  if (A.unreadable) notes.push(`${A.unreadable} verdict files could not be read.`);
6305
6308
  return `<section class="usage-card" data-outcomes="agents">${head}<div class="usage-table-wrap"><table class="usage-table">
6306
- <thead><tr><th>Agent</th><th class="num">Verdicts</th><th class="num">Pass</th><th class="num">Stopped</th><th class="num">Failed</th><th class="num">Stop rate</th><th class="num" title="Agent dispatches seen in Claude Code's logs in the same period">Dispatched</th></tr></thead>
6309
+ <thead><tr><th>Agent</th><th class="num">Verdicts</th><th class="num">Pass</th><th class="num">Stopped</th><th class="num">Failed</th><th class="num">Stop rate</th><th class="num" title="Runs the completion check saw end with no verdict — hover a number for how they ended">No verdict</th><th class="num" title="Agent dispatches seen in Claude Code's logs in the same period">Dispatched</th></tr></thead>
6307
6310
  <tbody>${rows}</tbody></table></div>
6308
- <p class="usage-sub">Stopped = BLOCKED, REWORK, REJECTED, ESCALATED — the pipeline halted on the agent's word. Stop rate is of the verdicts that concluded.${notes.length ? ` ${notes.join(' ')}` : ''}</p></section>`;
6311
+ <p class="usage-sub">Stopped = BLOCKED, REWORK, REJECTED, ESCALATED — the pipeline halted on the agent's word. Stop rate is of the verdicts that concluded. No verdict counts runs that ended without one, as the completion check recorded them (from 3.56).${notes.length ? ` ${notes.join(' ')}` : ''}</p></section>`;
6309
6312
  }
6310
6313
 
6311
6314
  function usageFindingsCard(d) {
@@ -85,7 +85,7 @@ export function agentOutcomes({ projects, globalDir = path.join(os.homedir(), '.
85
85
  seen.add(key);
86
86
  const agent = verdictAgent(rec.agent || fileAgent) || fileAgent;
87
87
  if (known.size && !known.has(agent)) { other.runs++; other.names.add(agent); continue; }
88
- const a = agents[agent] || (agents[agent] = { agent, runs: 0, pass: 0, stopped: 0, failed: 0, skipped: 0, unknown: 0, needImplementer: 0, needDecision: 0, last: null, projects: new Set() });
88
+ const a = agents[agent] || (agents[agent] = blankAgent(agent));
89
89
  a.runs++;
90
90
  a[outcomeOf(rec.verdict)]++;
91
91
  if (rec.meta?.need === 'implementer') a.needImplementer++;
@@ -96,15 +96,39 @@ export function agentOutcomes({ projects, globalDir = path.join(os.homedir(), '.
96
96
  }
97
97
  }
98
98
  }
99
+ // How runs ENDED, from the completion hook's agent-stop events: a run that
100
+ // recorded nothing leaves no verdict line, so only these can count it.
101
+ for (const p of projects) {
102
+ let text = '';
103
+ try { text = fs.readFileSync(path.join(p.path, '.great_cto', 'events.jsonl'), 'utf8'); } catch { continue; }
104
+ for (const line of text.split('\n')) {
105
+ if (!line.includes('"agent-stop"') || !line.includes('no-verdict')) continue;
106
+ let e;
107
+ try { e = JSON.parse(line); } catch { continue; }
108
+ const t = Date.parse(e.ts);
109
+ if (!Number.isFinite(t) || t < from || t > now || !/^no-verdict-/.test(e.outcome || '')) continue;
110
+ const agent = verdictAgent(e.agent);
111
+ if (!agent || (known.size && !known.has(agent))) continue;
112
+ const a = agents[agent] || (agents[agent] = blankAgent(agent));
113
+ const why = e.outcome.slice('no-verdict-'.length);
114
+ a.noVerdict[why] = (a.noVerdict[why] || 0) + 1;
115
+ a.noVerdictTotal++;
116
+ a.projects.add(p.name);
117
+ }
118
+ }
99
119
  const rows = Object.values(agents).map((a) => ({
100
120
  ...a,
101
121
  projects: a.projects.size,
102
122
  // Of the runs that reached a conclusion, how many stopped the pipeline.
103
123
  stopRate: a.pass + a.stopped + a.failed ? (a.stopped + a.failed) / (a.pass + a.stopped + a.failed) : null,
104
- })).sort((x, y) => y.runs - x.runs);
124
+ })).sort((x, y) => (y.runs + y.noVerdictTotal) - (x.runs + x.noVerdictTotal));
105
125
  return { agents: rows, other: { runs: other.runs, names: [...other.names].sort().slice(0, 20) }, unreadable };
106
126
  }
107
127
 
128
+ function blankAgent(agent) {
129
+ return { agent, runs: 0, pass: 0, stopped: 0, failed: 0, skipped: 0, unknown: 0, needImplementer: 0, needDecision: 0, noVerdict: {}, noVerdictTotal: 0, last: null, projects: new Set() };
130
+ }
131
+
108
132
  /** Run `bd` in a project; resolves to parsed JSON or a reason it could not. */
109
133
  function bdJson(cwd, args, { timeoutMs = 20000, bin = 'bd' } = {}) {
110
134
  return new Promise((resolve) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "great-cto",
3
- "version": "3.55.0",
3
+ "version": "3.56.0",
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",