great-cto 3.27.5 → 3.28.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 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.
|
|
4
|
+
"version": "3.28.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Alexander Velikiy",
|
|
7
7
|
"url": "https://hashnode.com/@Greatcto"
|
|
@@ -6957,10 +6957,19 @@ async function init() {
|
|
|
6957
6957
|
shareState = share || { enabled: false, url: null };
|
|
6958
6958
|
allProjects = projs || [];
|
|
6959
6959
|
|
|
6960
|
-
// Prefer URL
|
|
6960
|
+
// Prefer the URL, then a REGISTERED project, then a guess that is known to be
|
|
6961
|
+
// one. Deriving the slug from a task id's prefix is a heuristic: bd ids are
|
|
6962
|
+
// short, so a project registered as `acme-storefront` whose tasks read
|
|
6963
|
+
// `acme-6f` yielded `acme`, which is not a project, and the board then told
|
|
6964
|
+
// the operator in red that `acme was not found` — on a perfectly healthy
|
|
6965
|
+
// setup. Every README screenshot has carried that banner since.
|
|
6966
|
+
// A prefix is used only when it names something the registry actually has.
|
|
6967
|
+
const fromTaskId = (allTasks[0]?.id || '').split('-')[0];
|
|
6968
|
+
const registered = (allProjects || []).some((p) => p.slug === fromTaskId) ? fromTaskId : '';
|
|
6961
6969
|
const proj = urlProject
|
|
6962
|
-
||
|
|
6970
|
+
|| registered
|
|
6963
6971
|
|| (allProjects[0]?.slug)
|
|
6972
|
+
|| fromTaskId
|
|
6964
6973
|
|| 'project';
|
|
6965
6974
|
currentProject = proj;
|
|
6966
6975
|
document.getElementById('project-name').textContent = proj;
|
|
@@ -112,8 +112,21 @@ export function parseFindings(text) {
|
|
|
112
112
|
const m = line.match(/^(.+?):(\d+)\s*\|\s*(P[012])\s*\|\s*(.+)$/i);
|
|
113
113
|
if (m) findings.push({ file: m[1].trim(), line: parseInt(m[2], 10), severity: m[3].toUpperCase(), issue: m[4].trim() });
|
|
114
114
|
}
|
|
115
|
-
// Derive verdict
|
|
116
|
-
|
|
115
|
+
// Derive a verdict only in the direction that is safe to be wrong about.
|
|
116
|
+
//
|
|
117
|
+
// This used to answer PASS whenever no `VERDICT:` line was found and no P0
|
|
118
|
+
// was parsed. But an empty body, a refusal, a reply in some other format and
|
|
119
|
+
// a model that genuinely said "looks clean" are the SAME input here — all
|
|
120
|
+
// four leave `verdict` null and `findings` without a P0. Reading any of them
|
|
121
|
+
// as PASS reads all of them as PASS, so a review that could not be read
|
|
122
|
+
// became a review that approved, and the Stop hook downstream ended the turn
|
|
123
|
+
// on it.
|
|
124
|
+
//
|
|
125
|
+
// A derivation that BLOCKS costs a second look. A derivation that PASSES
|
|
126
|
+
// spends the guarantee. So: a P0 with no verdict line still blocks; nothing
|
|
127
|
+
// else is turned into a verdict, and `null` means the model never reached
|
|
128
|
+
// one. Callers must treat null as "no verdict", never as a pass.
|
|
129
|
+
if (!verdict && findings.some(f => f.severity === 'P0')) verdict = 'BLOCK';
|
|
117
130
|
return { findings, verdict };
|
|
118
131
|
}
|
|
119
132
|
|
|
@@ -216,6 +229,17 @@ async function main(argv) {
|
|
|
216
229
|
const cost = res.usage && priced ? round4(costForUsage({ model: res.model, usage: res.usage })) : null;
|
|
217
230
|
|
|
218
231
|
for (const f of findings) console.log(` ${f.severity} ${f.file}:${f.line} — ${f.issue}`);
|
|
232
|
+
if (verdict == null) {
|
|
233
|
+
// The call succeeded and the answer is unusable. `ok` would put it in the
|
|
234
|
+
// log as a reviewed tree and exit 0 would tell the caller it passed; both
|
|
235
|
+
// are the claim this tool exists to refuse to make.
|
|
236
|
+
console.log(`\ncross-model-review (${decision.provider}:${res.model}): the answer carries no verdict `
|
|
237
|
+
+ `(${findings.length} finding(s) parsed). Not a pass — re-run, or say in your answer that the reviewer `
|
|
238
|
+
+ `returned nothing readable.`);
|
|
239
|
+
log({ provider: decision.provider, model: res.model, state: 'unreadable', verdict: null, findings, cost,
|
|
240
|
+
error_kind: 'no-verdict' });
|
|
241
|
+
process.exit(EXIT.SKIPPED);
|
|
242
|
+
}
|
|
219
243
|
console.log(`\ncross-model-review (${decision.provider}:${res.model}): ${findings.length} finding(s), VERDICT: ${verdict} (${cost == null ? 'cost unpriced' : '$' + cost})`);
|
|
220
244
|
log({ provider: decision.provider, model: res.model, state: 'ok', verdict, findings, cost });
|
|
221
245
|
process.exit(verdict === 'BLOCK' ? EXIT.BLOCK : EXIT.PASS);
|
|
@@ -40,6 +40,17 @@ export function secondOpinionForTree(c) {
|
|
|
40
40
|
const r = paired[paired.length - 1];
|
|
41
41
|
return { ...base, state: 'ok', verdict: r.verdict ?? null, findings: r.findings ?? null, p0: r.p0 ?? null, sha: r.sha, ts: r.ts ?? null, why: '' };
|
|
42
42
|
}
|
|
43
|
+
// A line that joins this tree but reached no verdict is not "no review line".
|
|
44
|
+
// It is a review that produced nothing readable, and saying so is the whole
|
|
45
|
+
// point — falling through to `unmeasured` would tell the operator to run a
|
|
46
|
+
// review that already ran.
|
|
47
|
+
const joins = (r) => typeof r.sha === 'string' && r.sha !== '' && head && (r.sha === head || head.startsWith(r.sha) || r.sha.startsWith(head));
|
|
48
|
+
const noVerdict = rows.filter((r) => joins(r) && r.state === 'unreadable');
|
|
49
|
+
if (noVerdict.length) {
|
|
50
|
+
const r = noVerdict[noVerdict.length - 1];
|
|
51
|
+
return { ...base, state: 'unreadable', sha: r.sha, ts: r.ts ?? null,
|
|
52
|
+
why: `${tool} answered for this tree and the answer carries no verdict — it is not a pass` };
|
|
53
|
+
}
|
|
43
54
|
const anyKeyed = rows.some((r) => typeof r.sha === 'string' && r.sha !== '');
|
|
44
55
|
if (!anyKeyed && rows.length) {
|
|
45
56
|
return { ...base, state: 'unreadable', why: `${rows.length} review line(s) predate the join key — none can be paired with this tree` };
|
package/package.json
CHANGED