great-cto 3.27.2 → 3.27.3

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.27.2",
4
+ "version": "3.27.3",
5
5
  "author": {
6
6
  "name": "Alexander Velikiy",
7
7
  "url": "https://hashnode.com/@Greatcto"
@@ -284,6 +284,10 @@
284
284
  {
285
285
  "type": "command",
286
286
  "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/pipeline-stall-guard.mjs\" 2>/dev/null || true"
287
+ },
288
+ {
289
+ "type": "command",
290
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/cross-review-gate.mjs\" 2>/dev/null || true"
287
291
  }
288
292
  ]
289
293
  }
@@ -25,6 +25,9 @@ import { parseAgentBudgets, upsertAgentBudget, removeAgentBudget } from '../../.
25
25
  import { resolveSecondOpinion, SECOND_OPINION_PROVIDERS } from '../../../scripts/lib/second-opinion.mjs';
26
26
  import { detectCodex } from '../../../scripts/lib/codex-exec.mjs';
27
27
  import { upsertCapability, capabilitiesFromProjectMd } from '../../../scripts/lib/stack-capabilities.mjs';
28
+ // Moved to scripts/lib so the cross-review Stop hook can ask the same question
29
+ // without importing a server. Re-exported here: this module's callers are unchanged.
30
+ import { secondOpinionForTree } from '../../../scripts/lib/second-opinion-for-tree.mjs';
28
31
  import { getAgentsFleet, getAgentProfile, retireAgent, restoreAgent, appendDecisionLog, readDecisionsLog } from './fleet.mjs';
29
32
  import { getResume, getShareState, toggleShare } from './share.mjs';
30
33
  import { listSessions, readSession, editedFiles, searchSessions } from './transcripts.mjs';
@@ -1952,34 +1955,5 @@ async function dispatch(req, res, url, cwd) {
1952
1955
  * a throw, because "could not tell" is data for the row, not a reason to lose
1953
1956
  * the inbox.
1954
1957
  */
1955
- export function secondOpinionForTree(c) {
1956
- let projectMd = null;
1957
- try { projectMd = fs.readFileSync(path.join(c, '.great_cto', 'PROJECT.md'), 'utf8'); } catch { projectMd = null; }
1958
- let declared = null;
1959
- try { declared = projectMd == null ? null : capabilitiesFromProjectMd(projectMd).map.second_opinion; } catch { declared = null; }
1960
- const tool = declared?.tool ?? null;
1961
- const declaredState = declared?.state ?? (projectMd == null ? 'no-project-md' : 'undeclared');
1962
- let head = null;
1963
- try { head = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: c, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || null; } catch { head = null; }
1964
- const base = { declared: declaredState, tool, head, verdict: null, findings: null, p0: null, sha: null, ts: null };
1965
- if (declaredState !== 'declared' || !tool) {
1966
- return { ...base, state: 'not-run', why: declaredState === 'none' ? 'second_opinion: none — deliberately off' : 'no second opinion declared in PROJECT.md' };
1967
- }
1968
- let lines = [];
1969
- try { lines = fs.readFileSync(path.join(c, '.great_cto', 'cross-review.log'), 'utf8').trim().split('\n').filter(Boolean); }
1970
- catch { return { ...base, state: 'unmeasured', why: `declared (${tool}), no review has been written yet` }; }
1971
- const rows = [];
1972
- for (const line of lines) { try { rows.push(JSON.parse(line)); } catch { /* counted by /api/harnesses; not a verdict either way */ } }
1973
- const paired = rows.filter((r) => typeof r.sha === 'string' && r.sha !== '' && head && (r.sha === head || head.startsWith(r.sha) || r.sha.startsWith(head)) && r.state === 'ok');
1974
- if (paired.length) {
1975
- const r = paired[paired.length - 1];
1976
- return { ...base, state: 'ok', verdict: r.verdict ?? null, findings: r.findings ?? null, p0: r.p0 ?? null, sha: r.sha, ts: r.ts ?? null, why: '' };
1977
- }
1978
- const anyKeyed = rows.some((r) => typeof r.sha === 'string' && r.sha !== '');
1979
- if (!anyKeyed && rows.length) {
1980
- return { ...base, state: 'unreadable', why: `${rows.length} review line(s) predate the join key — none can be paired with this tree` };
1981
- }
1982
- return { ...base, state: 'unmeasured', why: `declared (${tool}), no review line for ${head ? head.slice(0, 8) : 'this tree'}` };
1983
- }
1984
1958
 
1985
- export { dispatch };
1959
+ export { dispatch, secondOpinionForTree };
@@ -0,0 +1,48 @@
1
+ // One reader for "has another model seen THIS tree", used by the board and by
2
+ // the Stop gate.
3
+ //
4
+ // It lived inside the board's route module, which was fine while the board was
5
+ // the only thing asking. The cross-review Stop gate asks the same question, and
6
+ // a hook importing the board's server routes to get it would drag a server into
7
+ // a hook — so the reader moved here and routes.mjs re-exports it. One reader,
8
+ // because two would drift and then disagree about whether a diff was reviewed.
9
+ //
10
+ // The states are the point, and none of them is a pass:
11
+ // ok — a review line joins this HEAD; carries its verdict
12
+ // not-run — no second opinion is declared (including a deliberate `none`)
13
+ // unmeasured — declared, but nothing has reviewed THIS tree
14
+ // unreadable — lines exist and none can be paired (they predate the join key)
15
+ import fs from 'node:fs';
16
+ import path from 'node:path';
17
+ import { execFileSync } from 'node:child_process';
18
+ import { capabilitiesFromProjectMd } from './stack-capabilities.mjs';
19
+
20
+ export function secondOpinionForTree(c) {
21
+ let projectMd = null;
22
+ try { projectMd = fs.readFileSync(path.join(c, '.great_cto', 'PROJECT.md'), 'utf8'); } catch { projectMd = null; }
23
+ let declared = null;
24
+ try { declared = projectMd == null ? null : capabilitiesFromProjectMd(projectMd).map.second_opinion; } catch { declared = null; }
25
+ const tool = declared?.tool ?? null;
26
+ const declaredState = declared?.state ?? (projectMd == null ? 'no-project-md' : 'undeclared');
27
+ let head = null;
28
+ try { head = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: c, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || null; } catch { head = null; }
29
+ const base = { declared: declaredState, tool, head, verdict: null, findings: null, p0: null, sha: null, ts: null };
30
+ if (declaredState !== 'declared' || !tool) {
31
+ return { ...base, state: 'not-run', why: declaredState === 'none' ? 'second_opinion: none — deliberately off' : 'no second opinion declared in PROJECT.md' };
32
+ }
33
+ let lines = [];
34
+ try { lines = fs.readFileSync(path.join(c, '.great_cto', 'cross-review.log'), 'utf8').trim().split('\n').filter(Boolean); }
35
+ catch { return { ...base, state: 'unmeasured', why: `declared (${tool}), no review has been written yet` }; }
36
+ const rows = [];
37
+ for (const line of lines) { try { rows.push(JSON.parse(line)); } catch { /* counted by /api/harnesses; not a verdict either way */ } }
38
+ const paired = rows.filter((r) => typeof r.sha === 'string' && r.sha !== '' && head && (r.sha === head || head.startsWith(r.sha) || r.sha.startsWith(head)) && r.state === 'ok');
39
+ if (paired.length) {
40
+ const r = paired[paired.length - 1];
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
+ }
43
+ const anyKeyed = rows.some((r) => typeof r.sha === 'string' && r.sha !== '');
44
+ if (!anyKeyed && rows.length) {
45
+ return { ...base, state: 'unreadable', why: `${rows.length} review line(s) predate the join key — none can be paired with this tree` };
46
+ }
47
+ return { ...base, state: 'unmeasured', why: `declared (${tool}), no review line for ${head ? head.slice(0, 8) : 'this tree'}` };
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "great-cto",
3
- "version": "3.27.2",
3
+ "version": "3.27.3",
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",