great-cto 3.44.0 → 3.46.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 71 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.44.0",
4
+ "version": "3.46.0",
5
5
  "author": {
6
6
  "name": "Alexander Velikiy",
7
7
  "url": "https://hashnode.com/@Greatcto"
@@ -5815,6 +5815,7 @@ function renderAgentActivity(snap) {
5815
5815
  return `${esc(e.tool || 'tool')}${p}`;
5816
5816
  }
5817
5817
  if (e.kind === 'pipeline') return `pipeline: ${esc(e.outcome || '')}${e.verdict ? ' · ' + esc(e.verdict) : ''}`;
5818
+ if (e.kind === 'hint') return `hint: ${esc(e.hook || '')}${Array.isArray(e.paths) && e.paths.length ? ' ' + esc(e.paths[0]) : ''}${e.host === 'codex' ? ' · codex' : ''}`;
5818
5819
  return esc(e.kind || '');
5819
5820
  };
5820
5821
  const attention = (Array.isArray(snap.attention) ? snap.attention : []).map((e) =>
@@ -28,8 +28,10 @@ import { appendFileSync, closeSync, fstatSync, mkdirSync, openSync, readFileSync
28
28
  import { join } from 'node:path';
29
29
 
30
30
  export const EVENTS_FILE = 'events.jsonl';
31
- export const EVENT_KINDS = Object.freeze(['agent-start', 'agent-stop', 'tool', 'denied', 'stop', 'pipeline']);
32
- export const EVENT_FIELDS = Object.freeze(['v', 'ts', 'kind', 'session', 'agent', 'tool', 'paths', 'ok', 'duration_ms', 'outcome', 'verdict']);
31
+ // `hint`: a context hook (edit-impact, lesson-tripwire) put text in front of the model.
32
+ // Recorded so its noise can be measured — how often, on what, how long — not what it said.
33
+ export const EVENT_KINDS = Object.freeze(['agent-start', 'agent-stop', 'tool', 'denied', 'stop', 'pipeline', 'hint']);
34
+ export const EVENT_FIELDS = Object.freeze(['v', 'ts', 'kind', 'session', 'agent', 'tool', 'paths', 'ok', 'duration_ms', 'outcome', 'verdict', 'hook', 'chars', 'host']);
33
35
  export const MAX_BYTES = 5 * 1024 * 1024;
34
36
 
35
37
  const MAX_PATHS = 10;
@@ -55,6 +57,9 @@ export function makeEvent(input, { now = Date.now() } = {}) {
55
57
  if (Number.isFinite(input.duration_ms) && input.duration_ms >= 0) e.duration_ms = Math.round(input.duration_ms);
56
58
  if (typeof input.outcome === 'string' && /^[a-z][a-z-]{0,39}$/.test(input.outcome)) e.outcome = input.outcome;
57
59
  if (typeof input.verdict === 'string' && /^[A-Z][A-Z_-]{0,39}$/.test(input.verdict)) e.verdict = input.verdict;
60
+ if (typeof input.hook === 'string' && /^[a-z][a-z-]{0,39}$/.test(input.hook)) e.hook = input.hook;
61
+ if (Number.isInteger(input.chars) && input.chars >= 0) e.chars = input.chars;
62
+ if (input.host === 'claude' || input.host === 'codex') e.host = input.host;
58
63
  return e;
59
64
  }
60
65
 
package/dist/codex.js CHANGED
@@ -48,7 +48,7 @@ export function codexInstallPlan({ repoDir, codexOnPath }) {
48
48
  "skills — all of them, the same tree Claude Code reads",
49
49
  "the MCP server (great_cto), resolved from npm",
50
50
  "the controlled role pipeline — run `npx --yes great-cto codex-host doctor` then `npx --yes great-cto codex-host start`",
51
- "six safety guards as Codex plugin hooks (destructive commands, gate bypasses, a neighbour session's work, secrets in files, frozen gates, weakened checks) and two hints that never block (edit-impact, lesson-tripwire) — Codex asks you to review them once on the next interactive `codex` start; they do not run until you do",
51
+ "six safety guards as Codex plugin hooks (destructive commands, gate bypasses, a neighbour session's work, secrets in files, frozen gates, weakened checks) and two hints that never block (edit-impact, lesson-tripwire) — Codex asks you to review them once on the next interactive `codex` start; they do not run until you do. Codex does not update great_cto by itself — `great-cto upgrade` (or `great-cto upgrade codex`) does",
52
52
  ],
53
53
  // Codex runs plugin hooks (verified 2026-09-28, codex-cli 0.153.4) but has no
54
54
  // plugin surface for commands or role agents. Saying "installed" without saying
@@ -60,3 +60,92 @@ export function codexInstallPlan({ repoDir, codexOnPath }) {
60
60
  ],
61
61
  };
62
62
  }
63
+ // ── Keeping the Codex install current ────────────────────────────────────────
64
+ //
65
+ // Codex installs great_cto from a Git marketplace snapshot and never refreshes it on
66
+ // its own. `release.sh` and `install-local` update the Claude Code plugin only, so on
67
+ // the maintainer's machine Codex sat at 3.37.0 while 3.45.0 shipped — every Codex
68
+ // guard released in between (3.42–3.45) had never reached it. Measured 2026-09-30:
69
+ // `codex plugin marketplace upgrade great-cto` refreshes the snapshot AND the installed
70
+ // plugin cache in one step. `great-cto upgrade` now runs it when Codex has the
71
+ // great-cto marketplace configured.
72
+ import { existsSync as fsExists, readdirSync, readFileSync as fsRead } from "node:fs";
73
+ import { homedir as home } from "node:os";
74
+ import { join as pjoin } from "node:path";
75
+ import { execFileSync } from "node:child_process";
76
+ /** A usable `codex` binary: PATH first, then nvm installs, then the desktop app. Null when none. */
77
+ export function findCodexBinary(env = process.env, h = home()) {
78
+ for (const dir of String(env.PATH || "").split(":")) {
79
+ if (dir && fsExists(pjoin(dir, "codex")))
80
+ return pjoin(dir, "codex");
81
+ }
82
+ // A shell that did not load nvm (a conda base, a login shell without .nvmrc) cannot
83
+ // see an npm-installed codex; look where nvm puts it.
84
+ const nvm = pjoin(h, ".nvm", "versions", "node");
85
+ let versions = [];
86
+ try {
87
+ versions = readdirSync(nvm).sort().reverse();
88
+ }
89
+ catch { /* no nvm */ }
90
+ for (const v of versions) {
91
+ const p = pjoin(nvm, v, "bin", "codex");
92
+ if (fsExists(p))
93
+ return p;
94
+ }
95
+ const app = "/Applications/Codex.app/Contents/Resources/codex";
96
+ return fsExists(app) ? app : null;
97
+ }
98
+ /** Does Codex have great_cto's marketplace configured? Read from its config.toml. */
99
+ export function codexHasGreatCtoMarketplace(codexHome = pjoin(home(), ".codex")) {
100
+ try {
101
+ return /^\s*\[marketplaces\.(?:"great-cto"|great-cto)\]\s*$/m.test(fsRead(pjoin(codexHome, "config.toml"), "utf8"));
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ /** The newest great_cto version in Codex's plugin cache, or null. */
108
+ export function installedCodexVersion(codexHome = pjoin(home(), ".codex")) {
109
+ const cache = pjoin(codexHome, "plugins", "cache");
110
+ let markets = [];
111
+ try {
112
+ markets = readdirSync(cache);
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ const versions = [];
118
+ for (const m of markets) {
119
+ try {
120
+ versions.push(...readdirSync(pjoin(cache, m, "great-cto")).filter((v) => /^\d+\.\d+\.\d+/.test(v)));
121
+ }
122
+ catch { /* not this market */ }
123
+ }
124
+ const key = (v) => v.split(".").map((n) => parseInt(n, 10) || 0);
125
+ versions.sort((a, b) => { const x = key(a), y = key(b); for (let i = 0; i < 3; i++)
126
+ if (x[i] !== y[i])
127
+ return x[i] - y[i]; return 0; });
128
+ return versions.at(-1) ?? null;
129
+ }
130
+ /** Refresh great_cto in Codex. Skips, with the reason, when Codex or the marketplace is absent. */
131
+ export function upgradeCodexPlugin(opts = {}) {
132
+ const codexHome = opts.codexHome ?? pjoin(home(), ".codex");
133
+ if (!codexHasGreatCtoMarketplace(codexHome)) {
134
+ return { status: "skipped", fromVersion: null, toVersion: null, reason: "great_cto is not installed for Codex" };
135
+ }
136
+ const from = installedCodexVersion(codexHome);
137
+ const bin = opts.bin === undefined ? findCodexBinary() : opts.bin;
138
+ if (!bin) {
139
+ return { status: "skipped", fromVersion: from, toVersion: from, reason: "codex not found on PATH or under ~/.nvm — run: codex plugin marketplace upgrade great-cto" };
140
+ }
141
+ try {
142
+ execFileSync(bin, ["plugin", "marketplace", "upgrade", "great-cto"], {
143
+ stdio: ["ignore", "pipe", "pipe"], timeout: 180_000, env: { ...process.env, CODEX_HOME: codexHome },
144
+ });
145
+ }
146
+ catch (e) {
147
+ return { status: "failed", fromVersion: from, toVersion: from, reason: String(e.message).split("\n")[0] };
148
+ }
149
+ const to = installedCodexVersion(codexHome);
150
+ return { status: to && to !== from ? "upgraded" : "already_latest", fromVersion: from, toVersion: to };
151
+ }
package/dist/main.js CHANGED
@@ -772,6 +772,7 @@ ${bold("Upgrade:")}
772
772
  great-cto upgrade superpowers Upgrade superpowers only
773
773
  great-cto upgrade beads Upgrade beads only
774
774
  great-cto upgrade --self Upgrade the great-cto CLI itself (also: upgrade self)
775
+ great-cto upgrade codex Refresh great_cto in Codex (also part of plain upgrade)
775
776
  great-cto uninstall Plan: what would be removed, what is kept (changes nothing)
776
777
  great-cto uninstall --yes Remove plugin, agents, commands, board service, caches
777
778
  --purge-data Also move ~/.great_cto aside (reversible, never deleted)
@@ -1435,6 +1436,9 @@ async function runUpgrade(rawArgv, args) {
1435
1436
  // Optional positional: great-cto upgrade [plugin-name]
1436
1437
  const pluginArg = firstArgAfterUpgrade;
1437
1438
  const targetPlugin = pluginArg && !pluginArg.startsWith("--") ? pluginArg : undefined;
1439
+ // `great-cto upgrade codex` refreshes only the Codex install.
1440
+ if (targetPlugin === "codex")
1441
+ return reportCodexUpgrade();
1438
1442
  let results;
1439
1443
  if (targetPlugin) {
1440
1444
  const plugin = COMPANION_PLUGINS.find((p) => p.name === targetPlugin);
@@ -1458,6 +1462,28 @@ async function runUpgrade(rawArgv, args) {
1458
1462
  warn(`${r.name} skipped — ${r.reason ?? "unknown reason"}`);
1459
1463
  }
1460
1464
  }
1465
+ // Codex keeps its own snapshot of great_cto and never refreshes it; without this
1466
+ // step every Codex guard shipped after the first install stays out of reach.
1467
+ if (!targetPlugin)
1468
+ await reportCodexUpgrade();
1469
+ return 0;
1470
+ }
1471
+ async function reportCodexUpgrade() {
1472
+ const { upgradeCodexPlugin } = await import("./codex.js");
1473
+ const r = upgradeCodexPlugin();
1474
+ if (r.status === "upgraded") {
1475
+ success(`codex: great_cto ${r.fromVersion ?? "—"} → ${r.toVersion} — new or changed hooks need one review at the next \`codex\` start`);
1476
+ }
1477
+ else if (r.status === "already_latest") {
1478
+ log(` ${dim(`codex: great_cto ${r.toVersion} already at latest`)}`);
1479
+ }
1480
+ else if (r.status === "failed") {
1481
+ warn(`codex: upgrade failed — ${r.reason}`);
1482
+ return 1;
1483
+ }
1484
+ else if (r.reason && r.reason !== "great_cto is not installed for Codex") {
1485
+ warn(`codex: ${r.reason}`);
1486
+ }
1461
1487
  return 0;
1462
1488
  }
1463
1489
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "great-cto",
3
- "version": "3.44.0",
3
+ "version": "3.46.0",
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",