great-cto 3.75.2 → 3.75.4

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.2",
4
+ "version": "3.75.4",
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
- const r = treeReceipt(process.cwd(), { verdictsDir });
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.OPENROUTER_API_KEY) return { state: 'unavailable', provider, why: 'second_opinion: openrouter needs OPENROUTER_API_KEY' };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "great-cto",
3
- "version": "3.75.2",
3
+ "version": "3.75.4",
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",