great-cto 3.72.0 → 3.73.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 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.72.0",
4
+ "version": "3.73.0",
5
5
  "author": {
6
6
  "name": "Alexander Velikiy",
7
7
  "url": "https://hashnode.com/@Greatcto"
@@ -43,19 +43,54 @@ const save = state => {
43
43
  renameSync(`${file}.tmp`, file);
44
44
  observeWorkRun(state);
45
45
  };
46
+ // Keep this local to the entrypoint: source controller and packaged CLI have
47
+ // separate runtime closures, but both must complete queued pipe writes.
48
+ function drainOutput(stream) {
49
+ if (stream.destroyed || stream.closed || stream.writableFinished) return Promise.resolve(true);
50
+ return new Promise(resolve => {
51
+ let settled = false;
52
+ const finish = complete => {
53
+ if (settled) return;
54
+ settled = true;
55
+ clearTimeout(timer);
56
+ stream.off('error', completed);
57
+ stream.off('close', completed);
58
+ stream.off('finish', completed);
59
+ resolve(complete);
60
+ };
61
+ const completed = () => finish(true);
62
+ const timer = setTimeout(() => finish(false), 1000);
63
+ timer.unref();
64
+ stream.once('error', completed);
65
+ stream.once('close', completed);
66
+ stream.once('finish', completed);
67
+ if (!stream.writableEnded) {
68
+ try {
69
+ stream.write('', error => {
70
+ // The error event follows an errored write callback.
71
+ if (error) queueMicrotask(completed);
72
+ else completed();
73
+ });
74
+ } catch { completed(); }
75
+ }
76
+ });
77
+ }
78
+ async function exitAfterOutput(code) {
79
+ // An unresolved promise and unref timer must not allow an early rc0.
80
+ process.exitCode = code || 1;
81
+ const completed = await Promise.all([drainOutput(process.stdout), drainOutput(process.stderr)]);
82
+ process.exit(completed.every(Boolean) ? code : code || 1);
83
+ }
84
+
46
85
  let locked = null, projectLease = null, work = null, taskForFinish = null;
47
86
  try {
48
87
  if (command === 'doctor') {
49
88
  const result = codexHostDoctor({ pluginRoot: resolve(value('--plugin-root') || join(dirname(fileURLToPath(import.meta.url)), '..')), store });
50
- console.log(JSON.stringify(result, null, 2)); process.exit(result.state === 'ready' ? 0 : 2);
89
+ console.log(JSON.stringify(result, null, 2)); await exitAfterOutput(result.state === 'ready' ? 0 : 2);
51
90
  }
52
91
  if (command === 'list') {
53
- const output = JSON.stringify(listCodexRuns({ root: value('--dir') ? realpathSync(resolve(value('--dir'))) : null, store }), null, 2) + '\n';
54
- // Piped stdout is asynchronous: exiting before the callback can truncate JSON.
55
- await new Promise((resolve, reject) => {
56
- process.stdout.write(output, error => error ? reject(error) : resolve());
57
- });
58
- process.exit(0);
92
+ console.log(JSON.stringify(listCodexRuns({ root: value('--dir') ? realpathSync(resolve(value('--dir'))) : null, store }), null, 2));
93
+ await exitAfterOutput(0);
59
94
  }
60
95
  let state;
61
96
  if (command === 'start') {
@@ -60,6 +60,9 @@ export const DEFAULT_PRICES = {
60
60
  // override via ~/.great_cto/model-prices.json or GREAT_CTO_MODEL_PRICES).
61
61
  'moonshotai/kimi-k2': { input: 0.55, output: 2.2 },
62
62
  'moonshotai/kimi-k3': { input: 3, output: 15 },
63
+ // Standard endpoint snapshot, 2026-10-24. Highspeed is a different rate.
64
+ 'moonshotai/kimi-k3.1-pro': { input: 2, output: 10, cacheRead: 0.2 },
65
+ 'moonshotai/kimi-k3.1-pro-20261023': { input: 2, output: 10, cacheRead: 0.2 },
63
66
  // Read from OpenRouter's /models on 2026-08-27, not guessed. Until now these
64
67
  // reported `priced: false` — correctly, and that honesty is why an eval run on
65
68
  // glm-5.3-flash showed $0.000: not free, unpriced. Now they are priced exactly.
@@ -159,7 +162,7 @@ export function resolvePrice(model, prices = effectivePrices()) {
159
162
  * 172 turns of `claude-fable-5` were billed at $0.00 for exactly that reason,
160
163
  * and the total looked like a total rather than a total plus a hole.
161
164
  *
162
- * @returns {{usd:number, priced:boolean, assumed:boolean, source:string, model:string}}
165
+ * @returns {{usd:number|null, priced:boolean, assumed:boolean, source:string, model:string}}
163
166
  */
164
167
  /**
165
168
  * Cache-write dollars. Anthropic bills a 5-minute cache write at 1.25× the input
@@ -180,7 +183,30 @@ export function cacheWriteUsd(usage, inputPrice) {
180
183
  return total * 1.25 * inputPrice;
181
184
  }
182
185
 
186
+ const KIMI_STANDARD_MODELS = new Set(['moonshotai/kimi-k3.1-pro', 'moonshotai/kimi-k3.1-pro-20261023']);
187
+
188
+ function priceKimiUsage({ model, usage, prices }) {
189
+ const { price, source } = resolvePrice(model, prices);
190
+ const input = usage?.prompt_tokens;
191
+ const output = usage?.completion_tokens;
192
+ const cached = usage?.prompt_tokens_details?.cached_tokens;
193
+ const reasoning = usage?.completion_tokens_details?.reasoning_tokens;
194
+ const providerReportedUsd = typeof usage?.cost === 'number' && Number.isFinite(usage.cost) && usage.cost >= 0 ? usage.cost : null;
195
+ const base = { model, source, assumed: false, providerReportedUsd, costProvenance: 'calculated-standard-token-rates' };
196
+ if (source !== 'exact' || !price || ![price.input, price.output, price.cacheRead].every(n => Number.isFinite(n) && n >= 0)
197
+ || ![input, output, cached, reasoning].every(n => Number.isSafeInteger(n) && n >= 0)
198
+ || (Object.hasOwn(usage, 'total_tokens') && (!Number.isSafeInteger(usage.total_tokens) || usage.total_tokens !== input + output))
199
+ || (Object.hasOwn(usage, 'cost') && providerReportedUsd === null)
200
+ || cached > input || reasoning > output) {
201
+ return { ...base, usd: null, priced: false, usageState: 'unknown', costState: 'unknown' };
202
+ }
203
+ // OpenRouter includes cache reads in prompt and reasoning in completion.
204
+ const usd = ((input - cached) * price.input + cached * price.cacheRead + output * price.output) / 1_000_000;
205
+ return { ...base, usd, priced: true, usageState: 'known', costState: 'known' };
206
+ }
207
+
183
208
  export function priceUsage({ model, usage, prices }) {
209
+ if (KIMI_STANDARD_MODELS.has(model)) return priceKimiUsage({ model, usage, prices: prices || effectivePrices() });
184
210
  const { price, source } = resolvePrice(model, prices || effectivePrices());
185
211
  if (!usage || !price) {
186
212
  return { usd: 0, priced: false, assumed: false, source, model: model || '' };
@@ -199,9 +225,11 @@ export function priceUsage({ model, usage, prices }) {
199
225
  * @param {string} opts.model
200
226
  * @param {{input_tokens?:number, output_tokens?:number}} opts.usage Anthropic response.usage
201
227
  * @param {object} [opts.prices] override table (for tests)
202
- * @returns {number} USD (0 if usage or price unknown)
228
+ * @returns {number|null} USD; exact Kimi requires complete raw OpenRouter usage
229
+ * and returns null when unknown. Legacy models retain the numeric 0 fallback.
203
230
  */
204
231
  export function costForUsage({ model, usage, prices }) {
232
+ if (KIMI_STANDARD_MODELS.has(model)) return priceKimiUsage({ model, usage, prices: prices || effectivePrices() }).usd;
205
233
  if (!usage) return 0;
206
234
  const p = priceForModel(model, prices);
207
235
  if (!p) return 0;
@@ -246,6 +274,10 @@ function main(argv) {
246
274
  return process.exit(2);
247
275
  }
248
276
  const cost = costForUsage({ model, usage: { input_tokens: inTok, output_tokens: outTok } });
277
+ if (cost === null) {
278
+ process.stderr.write('cost-meter: complete provider usage required — cost unknown\n');
279
+ return process.exit(2);
280
+ }
249
281
  process.stdout.write(String(round4(cost)));
250
282
  }
251
283
 
@@ -13,14 +13,14 @@
13
13
  // node scripts/lib/cross-model-review.mjs --diff /tmp/d.diff --spec docs/architecture/ARCH-x.md
14
14
  // GREAT_CTO_CROSS_REVIEW_MODEL=google/gemini-2.5-pro node ... --diff -
15
15
 
16
- import { readFileSync } from 'node:fs';
16
+ import { readFileSync, lstatSync, openSync, closeSync, fstatSync, readSync, constants } from 'node:fs';
17
17
  import { fileURLToPath } from 'node:url';
18
18
  import { execFileSync } from 'node:child_process';
19
19
  import { costForUsage, round4, resolvePrice } from './cost-meter.mjs';
20
20
  import { resolveSecondOpinion, codexReview } from './second-opinion.mjs';
21
21
  import { principalError } from './provider-exhaustion.mjs';
22
22
  import { existsSync, appendFileSync, mkdirSync } from 'node:fs';
23
- import { join } from 'node:path';
23
+ import { join, resolve, parse, sep } from 'node:path';
24
24
  import { reviewInputBinding, parseLegacyReview } from './review-contract.mjs';
25
25
 
26
26
  /**
@@ -34,9 +34,14 @@ import { reviewInputBinding, parseLegacyReview } from './review-contract.mjs';
34
34
  export const EXIT = Object.freeze({ PASS: 0, BLOCK: 1, USAGE: 2, SKIPPED: 3 });
35
35
 
36
36
  /**
37
- * Which provider reviews, decided from three sources in a fixed order:
38
- * an explicit `--provider`, then the project's `capabilities: second_opinion`,
39
- * then — for compatibility with every script that set it — the OpenRouter env.
37
+ * Which provider reviews, decided from two sources in a fixed order:
38
+ * an explicit `--provider`, then the project's `capabilities: second_opinion`.
39
+ *
40
+ * An exported OPENROUTER_API_KEY used to be a third source: with nothing
41
+ * declared, the key chose OpenRouter and the diff left the machine without
42
+ * anyone deciding it should (CSO SEC-2, 27.10). The key is a credential, not a
43
+ * decision — undeclared stays undeclared, and the skip line names the
44
+ * declaration that would turn the review on.
40
45
  *
41
46
  * Returns the resolver's four states plus `source`, so the log line can say
42
47
  * why this provider and not another. Pure: `codex` and `env` are injected.
@@ -48,9 +53,9 @@ export function decideProvider({ argv = [], projectMd = '', codex = null, env =
48
53
  return { ...r, source: '--provider' };
49
54
  }
50
55
  const fromProject = resolveSecondOpinion({ projectMd, codex, env });
51
- if (fromProject.state !== 'undeclared') return { ...fromProject, source: 'PROJECT.md' };
52
- if (env.OPENROUTER_API_KEY) {
53
- return { state: 'declared', provider: 'openrouter', why: '', source: 'OPENROUTER_API_KEY (second_opinion undeclared)' };
56
+ if (fromProject.state === 'undeclared' && env.OPENROUTER_API_KEY) {
57
+ return { ...fromProject, source: 'PROJECT.md',
58
+ why: `${fromProject.why}; OPENROUTER_API_KEY is set but does not choose the provider — declare \`second_opinion: openrouter\` or pass --provider openrouter` };
54
59
  }
55
60
  return { ...fromProject, source: 'PROJECT.md' };
56
61
  }
@@ -128,6 +133,93 @@ export async function callOpenRouter({ apiKey, model, system, user, maxTokens =
128
133
 
129
134
  function readArg(argv, name) { const i = argv.indexOf(name); return i > -1 ? argv[i + 1] : null; }
130
135
 
136
+ /** Read explicitly selected input without following symlinks or special files. */
137
+ function readProfileFile(file, root, cap) {
138
+ if (typeof file !== 'string' || !file || file === '-' || Buffer.byteLength(file) > 4096
139
+ || file.split(/[\\/]/).includes('..') || /[\x00-\x1f\x7f]/.test(file)) throw new Error('invalid-input-file');
140
+ const target = resolve(root, file), parsed = parse(target);
141
+ const parts = target.slice(parsed.root.length).split(sep);
142
+ let current = parsed.root;
143
+ for (let i = 0; i < parts.length; i++) {
144
+ if (parts[i] === '.git' || /^\.env(?:\.|$)/i.test(parts[i])) throw new Error('invalid-input-file');
145
+ current = join(current, parts[i]);
146
+ const st = lstatSync(current);
147
+ if (st.isSymbolicLink() || (i < parts.length - 1 ? !st.isDirectory() : !st.isFile())) throw new Error('invalid-input-file');
148
+ }
149
+ const fd = openSync(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
150
+ try {
151
+ const st = fstatSync(fd);
152
+ if (!st.isFile() || st.size > cap) throw new Error('invalid-input-file');
153
+ const bytes = Buffer.alloc(cap + 1);
154
+ let size = 0, n;
155
+ while (size <= cap && (n = readSync(fd, bytes, size, cap + 1 - size, null)) > 0) size += n;
156
+ if (size > cap) throw new Error('invalid-input-file');
157
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(0, size));
158
+ } finally { closeSync(fd); }
159
+ }
160
+
161
+ /** Check explicit runtime roots before any credential getter is evaluated. */
162
+ export function validateKimiRuntimePaths(root, operatorRoot) {
163
+ for (const [directory, allowMissing] of [[root, false], [operatorRoot, true]]) {
164
+ if (typeof directory !== 'string' || !directory || Buffer.byteLength(directory) > 4096
165
+ || /[\x00-\x1f\x7f]/.test(directory)) throw new Error('invalid-runtime-path');
166
+ const target = resolve(directory), parsed = parse(target);
167
+ let current = parsed.root, missing = false;
168
+ for (const part of target.slice(parsed.root.length).split(sep).filter(Boolean)) {
169
+ if (Buffer.byteLength(part) > 255 || part === '.git' || /^\.env(?:\.|$)/i.test(part)) throw new Error('invalid-runtime-path');
170
+ current = join(current, part);
171
+ if (missing) continue;
172
+ try {
173
+ const st = lstatSync(current);
174
+ if (st.isSymbolicLink() || !st.isDirectory()) throw new Error('invalid-runtime-path');
175
+ } catch (error) {
176
+ if (allowMissing && error.code === 'ENOENT') missing = true;
177
+ else throw error;
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ /** Explicit extra opinion. Runtime capabilities are resolved only after admission. */
184
+ export async function runProfileReview(options = {}) {
185
+ const argv = options.argv ?? [];
186
+ if (!argv.includes('--profile')) return null;
187
+ const denied = () => ({ exitCode: EXIT.USAGE, summary: { status: 'usage-error', verdict: null,
188
+ advisory: true, authority: 'NONE', releaseEligibility: 'NOT_CHECKED', reasonCode: 'invalid-profile-invocation' } });
189
+ const allowed = new Set(['--profile', '--envelope', '--task', '--context-class', '--reason', '--root', '--operator-root', '--diff', '--spec']);
190
+ const flags = new Map();
191
+ for (let i = 0; i < argv.length; i += 2) {
192
+ const flag = argv[i], value = argv[i + 1];
193
+ if (!allowed.has(flag) || flags.has(flag) || typeof value !== 'string' || !value || value.startsWith('--')) return denied();
194
+ flags.set(flag, value);
195
+ }
196
+ if (flags.get('--profile') !== 'kimi-readonly-v1'
197
+ || ['--envelope', '--task', '--context-class', '--reason', '--operator-root'].some(k => !flags.has(k))) return denied();
198
+ let root, operatorRoot, envelope, profileApi;
199
+ try {
200
+ root = resolve(flags.get('--root') || process.cwd());
201
+ operatorRoot = resolve(root, flags.get('--operator-root'));
202
+ validateKimiRuntimePaths(root, operatorRoot);
203
+ envelope = JSON.parse(readProfileFile(flags.get('--envelope'), root, 131072));
204
+ if (!envelope || envelope.mode !== 'diff-review' || envelope.taskId !== flags.get('--task')
205
+ || envelope.contextClass !== flags.get('--context-class') || envelope.reason !== flags.get('--reason')) return denied();
206
+ for (const [flag, field, cap] of [['--diff', 'diff', 96000], ['--spec', 'spec', 16000]]) {
207
+ if (flags.has(flag) && readProfileFile(flags.get(flag), root, cap) !== envelope[field]) return denied();
208
+ }
209
+ profileApi = await import('./kimi-review-profile.mjs');
210
+ profileApi.buildKimiInputBinding(envelope);
211
+ } catch { return denied(); }
212
+ const result = await profileApi.runKimiReview({ profile: flags.get('--profile'), envelope, root,
213
+ operatorRoot,
214
+ get apiKey() { return options.apiKey ?? (options.env ?? process.env).OPENROUTER_API_KEY; },
215
+ fetchImpl: options.fetchImpl, now: options.now, uuid: options.uuid, signal: options.signal, timeoutMs: options.timeoutMs });
216
+ const complete = result.status === 'complete' && ['PASS', 'BLOCK'].includes(result.verdict);
217
+ return { exitCode: complete ? result.verdict === 'BLOCK' ? EXIT.BLOCK : EXIT.PASS : EXIT.SKIPPED,
218
+ summary: { status: result.status, reasonCode: result.reasonCode, verdict: complete ? result.verdict : null,
219
+ advisory: true, authority: 'NONE', releaseEligibility: 'NOT_CHECKED', bindingDigest: result.bindingDigest,
220
+ findings: result.result?.findings?.length ?? null, costState: result.costState, costUsd: result.costUsd } };
221
+ }
222
+
131
223
  /**
132
224
  * The tree's identity at review time — git HEAD and whether it was dirty.
133
225
  * CLI-only: the pure `reviewLogLine` above never shells out; per this file's
@@ -147,6 +239,11 @@ function gitIdentity(cwd) {
147
239
  }
148
240
 
149
241
  async function main(argv) {
242
+ if (argv.includes('--profile')) {
243
+ const r = await runProfileReview({ argv, fetchImpl: fetch });
244
+ console.log(JSON.stringify(r.summary));
245
+ process.exit(r.exitCode);
246
+ }
150
247
  const diffPath = readArg(argv, '--diff');
151
248
  if (!diffPath) { console.error('Usage: cross-model-review.mjs --diff <file|-> [--spec <file>] [--model <slug>] [--provider codex|openrouter]'); process.exit(EXIT.USAGE); }
152
249
 
@@ -216,7 +313,8 @@ async function main(argv) {
216
313
  // was present and costForUsage prices an unknown model at nothing. A reviewer
217
314
  // that reads as free is the defect this repository has removed twice already.
218
315
  const priced = resolvePrice(res.model).price != null;
219
- const cost = res.usage && priced ? round4(costForUsage({ model: res.model, usage: res.usage })) : null;
316
+ const calculated = res.usage && priced ? costForUsage({ model: res.model, usage: res.usage }) : null;
317
+ const cost = calculated === null ? null : round4(calculated);
220
318
 
221
319
  for (const f of findings) console.log(` ${f.severity} ${f.file}:${f.line} — ${f.issue}`);
222
320
  if (verdict == null) {