great-cto 3.75.2 → 3.75.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 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.75.
|
|
4
|
+
"version": "3.75.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Alexander Velikiy",
|
|
7
7
|
"url": "https://hashnode.com/@Greatcto"
|
|
@@ -99,7 +99,7 @@ const excludeLogs = ACTIVITY_LOGS.map((p) => `:(exclude)${p}`);
|
|
|
99
99
|
* Returns `null` outside a git repository rather than a fabricated receipt: a
|
|
100
100
|
* receipt that cannot be built must not look like one that matched.
|
|
101
101
|
*/
|
|
102
|
-
export function treeReceipt(cwd = process.cwd(), { base = null, maxFiles = MAX_FILES, verdictsDir = null, exclude = [], hashFilesInProcess = false } = {}) {
|
|
102
|
+
export function treeReceipt(cwd = process.cwd(), { base = null, maxFiles = MAX_FILES, verdictsDir = null, exclude = [], hashFilesInProcess = false, persist = false } = {}) {
|
|
103
103
|
const head = git(['rev-parse', 'HEAD'], cwd)?.trim();
|
|
104
104
|
if (!head) return null;
|
|
105
105
|
// Off-loop board readers avoid one process per untracked file. Preserve Git's
|
|
@@ -107,7 +107,7 @@ export function treeReceipt(cwd = process.cwd(), { base = null, maxFiles = MAX_F
|
|
|
107
107
|
const algorithm = hashFilesInProcess ? git(['rev-parse', '--show-object-format'], cwd)?.trim() : null;
|
|
108
108
|
if (hashFilesInProcess && !['sha1', 'sha256'].includes(algorithm)) return null;
|
|
109
109
|
const digest = p => {
|
|
110
|
-
if (!hashFilesInProcess) return fileDigest(cwd, p);
|
|
110
|
+
if (!hashFilesInProcess) return fileDigest(cwd, p, { write: persist });
|
|
111
111
|
try {
|
|
112
112
|
const fd = fsModule.openSync(join(cwd, p), 'r');
|
|
113
113
|
try {
|
|
@@ -223,8 +223,8 @@ export function treeReceipt(cwd = process.cwd(), { base = null, maxFiles = MAX_F
|
|
|
223
223
|
* what an agent read. `hash-object` on the working file is the thing the
|
|
224
224
|
* reviewer actually saw.
|
|
225
225
|
*/
|
|
226
|
-
export function fileDigest(cwd, path) {
|
|
227
|
-
const out = git(['hash-object', '--no-filters', '--', path], cwd);
|
|
226
|
+
export function fileDigest(cwd, path, { write = false } = {}) {
|
|
227
|
+
const out = git(['hash-object', ...(write ? ['-w'] : []), '--no-filters', '--', path], cwd);
|
|
228
228
|
return out ? out.trim() : null;
|
|
229
229
|
}
|
|
230
230
|
|
|
@@ -500,7 +500,13 @@ if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1
|
|
|
500
500
|
// `--verdicts <dir>`: measure the change from where the previous stage stood.
|
|
501
501
|
const vi = argv.indexOf('--verdicts');
|
|
502
502
|
const verdictsDir = vi >= 0 && argv[vi + 1] ? argv[vi + 1] : null;
|
|
503
|
-
|
|
503
|
+
// `persist`: the bytes go into git's object store, not only their digest. The
|
|
504
|
+
// judge reads the change from those objects later; a digest alone is a
|
|
505
|
+
// promise about content nobody kept, and an uncommitted change — senior-dev's
|
|
506
|
+
// normal case — would otherwise be judged from whatever the disk holds by then.
|
|
507
|
+
// Only here, where a verdict is recorded: the board's frequent readers must
|
|
508
|
+
// not leave objects behind.
|
|
509
|
+
const r = treeReceipt(process.cwd(), { verdictsDir, persist: true });
|
|
504
510
|
process.stdout.write(r ? JSON.stringify(r) : '');
|
|
505
511
|
process.exit(r ? 0 : 1);
|
|
506
512
|
}
|
|
@@ -256,6 +256,24 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
256
256
|
|
|
257
257
|
import { capabilitiesFromProjectMd } from './stack-capabilities.mjs';
|
|
258
258
|
import { runCodexExec, detectCodex } from './codex-exec.mjs';
|
|
259
|
+
import { parseEnv, KEY_NAME } from './router-key.mjs';
|
|
260
|
+
import { readFileSync as readKeyFile } from 'node:fs';
|
|
261
|
+
import { join as joinPath } from 'node:path';
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Is an OpenRouter key reachable — in the environment, or in the secrets file the
|
|
265
|
+
* router itself reads? Checking the environment alone called `openrouter`
|
|
266
|
+
* unavailable on every machine that keeps the key where great_cto tells people to
|
|
267
|
+
* keep it (~/.great_cto/secrets.env): the router would have answered, and the
|
|
268
|
+
* second judge was never asked. The home comes from `env`, so a caller that
|
|
269
|
+
* injects `env` (every test) never reads the real machine.
|
|
270
|
+
*/
|
|
271
|
+
function openrouterKeyPresent(env) {
|
|
272
|
+
if (env.OPENROUTER_API_KEY) return true;
|
|
273
|
+
if (!env.HOME) return false;
|
|
274
|
+
try { return Boolean(parseEnv(readKeyFile(joinPath(env.HOME, '.great_cto', 'secrets.env'), 'utf8')).get(KEY_NAME)); }
|
|
275
|
+
catch { return false; }
|
|
276
|
+
}
|
|
259
277
|
|
|
260
278
|
export const SECOND_OPINION_PROVIDERS = Object.freeze(['codex', 'openrouter', 'none']);
|
|
261
279
|
|
|
@@ -291,7 +309,7 @@ export function resolveSecondOpinion({ projectMd = '', codex = null, env = proce
|
|
|
291
309
|
return { state: 'declared', provider, why: '', codex: c };
|
|
292
310
|
}
|
|
293
311
|
if (provider === 'openrouter') {
|
|
294
|
-
if (!env
|
|
312
|
+
if (!openrouterKeyPresent(env)) return { state: 'unavailable', provider, why: 'second_opinion: openrouter needs OPENROUTER_API_KEY (environment or ~/.great_cto/secrets.env)' };
|
|
295
313
|
return { state: 'declared', provider, why: '' };
|
|
296
314
|
}
|
|
297
315
|
return { state: 'none', provider: 'none', why: '' };
|
package/package.json
CHANGED