great-cto 3.28.6 → 3.28.8

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.6",
4
+ "version": "3.28.8",
5
5
  "author": {
6
6
  "name": "Alexander Velikiy",
7
7
  "url": "https://hashnode.com/@Greatcto"
@@ -13,15 +13,33 @@ import { readVerdicts, readPlanCosts, readQAStats, readSecStats } from './verdic
13
13
  // accepted change yet" is the honest reading (same discipline as the null-score
14
14
  // oracle). Only two of the five metrics the literature lists are built here on
15
15
  // purpose — a solo project's denominator is tiny and the other three would be noise.
16
+ //
17
+ // A halting verdict is split three ways by the `need` its author declared:
18
+ // `implementer` is a pass the implementer can make (rework), `decision` is a
19
+ // question for a human, and anything else is undeclared. Counting every halt as
20
+ // rework called a waiver request "rework"; and REWORK itself, the token the
21
+ // dispatcher sends work back with, matched none of the old patterns and was never
22
+ // counted. Undeclared is its own count because it is not known to be either.
16
23
  function acceptanceMetrics(verdicts = [], windowCostUsd = null) {
17
- const isAccepted = (v) => /^APPROVED$/i.test(v.verdict || '');
18
- const isRework = (v) => /^(CHANGES(_REQUESTED)?|REJECT(ED)?|FAIL(ED)?|BLOCKED)$/i.test(v.verdict || '');
24
+ const token = (v) => String(v?.verdict || '');
25
+ const isAccepted = (v) => /^APPROVED$/i.test(token(v));
26
+ const isSentBack = (v) => /^(REWORK|CHANGES(_REQUESTED)?)$/i.test(token(v));
27
+ const isHalt = (v) => /^(REJECT(ED)?|FAIL(ED)?|BLOCKED)$/i.test(token(v));
28
+ const needOf = (v) => v?.meta?.need;
29
+ let rework_rounds = 0, decisions = 0, undeclared_blocks = 0;
30
+ for (const v of verdicts) {
31
+ if (isSentBack(v)) rework_rounds++;
32
+ else if (isHalt(v)) {
33
+ if (needOf(v) === 'implementer') rework_rounds++;
34
+ else if (needOf(v) === 'decision') decisions++;
35
+ else undeclared_blocks++;
36
+ }
37
+ }
19
38
  const accepted = verdicts.filter(isAccepted).length;
20
- const rework_rounds = verdicts.filter(isRework).length;
21
39
  const cost_per_accepted = (accepted > 0 && windowCostUsd != null)
22
40
  ? Math.round((windowCostUsd / accepted) * 100) / 100
23
41
  : null;
24
- return { accepted, cost_per_accepted, rework_rounds };
42
+ return { accepted, cost_per_accepted, rework_rounds, decisions, undeclared_blocks };
25
43
  }
26
44
 
27
45
  // ── Metrics ────────────────────────────────────────────────────────────────────
@@ -140,6 +140,9 @@ function readVerdicts(cwd = null, health = null) {
140
140
  agent,
141
141
  verdict,
142
142
  cost_usd: parsed.rec.cost_usd ?? null,
143
+ // Kept so a metric can read what the author declared (`need`, `task`).
144
+ // Dropping it made every halt look undeclared.
145
+ meta: parsed.rec.meta ?? null,
143
146
  raw: line.replace(/\s*\bcost=\$?\d+\.?\d*\b/i, ''),
144
147
  });
145
148
  }
@@ -55,6 +55,24 @@ export const KNOWN_VERDICTS = Object.freeze([
55
55
  * @property {object} [meta] free-form key/value from the caller
56
56
  */
57
57
 
58
+ /**
59
+ * Who a halting verdict is for, as `meta.need`.
60
+ *
61
+ * implementer — the agent that did the work can fix this; no human choice
62
+ * decision — a waiver, a scope change or a trade-off; a human decides
63
+ *
64
+ * Anything else, including absence, reads as `undeclared`, and undeclared is never
65
+ * routed anywhere: it halts exactly as a BLOCKED always has. qa and security wrote
66
+ * this distinction in prose for months and no verdict line carried it.
67
+ */
68
+ export const NEED_VALUES = Object.freeze(['implementer', 'decision']);
69
+
70
+ /** 'implementer' | 'decision' | 'undeclared'. Never throws. */
71
+ export function needOf(rec) {
72
+ const n = rec?.meta?.need;
73
+ return NEED_VALUES.includes(n) ? n : 'undeclared';
74
+ }
75
+
58
76
  const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
59
77
 
60
78
  /**
@@ -97,9 +115,15 @@ export function makeVerdict({ ts, agent, verdict, project, cost_usd, meta, recei
97
115
  if (receipt && typeof receipt === 'object' && receipt.head) rec.receipt = receipt;
98
116
 
99
117
  const { valid, errors } = validateVerdict(rec);
118
+ // An unknown `need` is refused HERE, at write time, and not in validateVerdict:
119
+ // that also runs on read, where refusing would discard the verdict itself. On
120
+ // read the value survives and needOf() calls it undeclared.
121
+ if (rec.meta && 'need' in rec.meta && !NEED_VALUES.includes(rec.meta.need)) {
122
+ errors.push(`meta.need must be one of ${NEED_VALUES.join(', ')}, got ${JSON.stringify(rec.meta.need)}`);
123
+ }
100
124
  // Refusing here is the point: a malformed record that reaches the log is read
101
125
  // by something later, and by then nobody knows which agent wrote it.
102
- if (!valid) throw new Error(`invalid verdict record: ${errors.join('; ')}`);
126
+ if (errors.length) throw new Error(`invalid verdict record: ${errors.join('; ')}`);
103
127
  return rec;
104
128
  }
105
129
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "great-cto",
3
- "version": "3.28.6",
3
+ "version": "3.28.8",
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",