qualia-framework 6.2.7 → 6.2.9

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.
package/README.md CHANGED
@@ -1,9 +1,13 @@
1
- # Qualia Framework v6.2.7
1
+ # Qualia Framework v6.2.9
2
2
 
3
3
  A harness engineering framework for Claude Code and OpenAI Codex. It installs into `~/.claude/` and/or `~/.codex/` and wraps your AI-assisted development workflow with structured planning, execution, verification, and deployment gates.
4
4
 
5
5
  It is not an application framework like Rails or Next.js. It doesn't generate code, run servers, or process data. It's an opinionated workflow layer that tells Claude how to plan, build, and verify your projects end-to-end, from "tell me what you want to make" to "here's the handoff doc for your client."
6
6
 
7
+ **v6.2.9** — Codex hook noise + status line. Conditional PreToolUse hooks no longer status-message on every Bash call (Codex was printing 8 "Running hook…" lines on every command). Self-filtering added to `pre-deploy-gate.js` and `pre-push.js` so they never trip on unrelated commands (Claude's substring matcher was firing them on for-loop arguments). Installer now writes `[tui] status_line = [...]` to Codex's `config.toml` for the rich native bottom status line.
8
+
9
+ **v6.2.8** — Codex `/goal` integration + install hardening. Phase-start skills now set the Codex thread goal (with token budget) via `bin/codex-goal.js` and `rules/codex-goal.md`. Installer fixes: agent TOMLs now emit `name = "..."` (Codex 0.133 was rejecting all 9), ERP API key is mirrored from `~/.claude/` → `~/.codex/`, and deprecated skills (`qualia-task`, `qualia-quick`, `qualia-polish-loop`, `qualia-design`, `qualia-prd`) are pruned on upgrade.
10
+
7
11
  **v6.2.7** — Codex runtime compatibility. The installer now writes Codex-native hooks, TOML agents, bin scripts, rules, skills, templates, knowledge, guide, and role config under `~/.codex/`, not just `AGENTS.md`.
8
12
 
9
13
  **The v5 line (preserved):**
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ // codex-goal — suggest a Codex /goal objective + token budget from current
3
+ // .planning/STATE.md + ROADMAP.md state.
4
+ //
5
+ // Output is a single line meant to be pasted into a Codex session, or quoted
6
+ // in an agent prompt that will then issue update_goal itself. The framework
7
+ // does NOT write directly to ~/.codex/goals_1.sqlite — Codex owns the schema
8
+ // and the thread_id; the /goal slash command (and the update_goal tool the
9
+ // model can call) are the stable interfaces.
10
+ //
11
+ // Usage: node bin/codex-goal.js [phase|task|feature|quick]
12
+ // default scope = phase
13
+
14
+ const fs = require("fs");
15
+ const path = require("path");
16
+
17
+ const SCOPE = (process.argv[2] || "phase").toLowerCase();
18
+
19
+ // Token budgets are calibrated to Codex's per-thread context window and the
20
+ // typical Qualia work-unit size. Phase = full builder loop with verification.
21
+ // Task/feature = single fresh-context spawn. Quick = one-shot inline edit.
22
+ const TOKEN_BUDGETS = {
23
+ phase: 80000,
24
+ task: 30000,
25
+ feature: 30000,
26
+ quick: 10000,
27
+ };
28
+
29
+ const PLANNING = path.join(process.cwd(), ".planning");
30
+ const STATE_FILE = path.join(PLANNING, "STATE.md");
31
+ const ROADMAP_FILE = path.join(PLANNING, "ROADMAP.md");
32
+
33
+ function readSafe(p) {
34
+ try { return fs.readFileSync(p, "utf8"); } catch { return ""; }
35
+ }
36
+
37
+ function parseState(content) {
38
+ if (!content) return null;
39
+ const phaseMatch = content.match(/^Phase:\s*(\d+)\s+of\s+(\d+)\s*[—-]\s*(.+?)\r?$/m);
40
+ if (!phaseMatch) return null;
41
+ return {
42
+ phase: parseInt(phaseMatch[1], 10),
43
+ total: parseInt(phaseMatch[2], 10),
44
+ name: phaseMatch[3].trim(),
45
+ };
46
+ }
47
+
48
+ function readPhaseGoal(roadmap, phaseNum) {
49
+ if (!roadmap) return "";
50
+ // Match a heading like "## Phase 2 — Name" or "## Phase 2: Name", then
51
+ // grab the "Goal:" line that follows.
52
+ const heading = new RegExp(`##\\s*Phase\\s*${phaseNum}\\s*[—:\\-]\\s*[^\\n]+`, "i");
53
+ const idx = roadmap.search(heading);
54
+ if (idx === -1) return "";
55
+ const body = roadmap.slice(idx, idx + 2000);
56
+ const goalMatch = body.match(/^\s*\*?\*?Goal:?\*?\*?\s*(.+?)$/im);
57
+ return goalMatch ? goalMatch[1].trim() : "";
58
+ }
59
+
60
+ function clip(s, max) {
61
+ return s.length <= max ? s : s.slice(0, max - 1).trimEnd() + "…";
62
+ }
63
+
64
+ function main() {
65
+ const budget = TOKEN_BUDGETS[SCOPE];
66
+ if (budget == null) {
67
+ process.stderr.write(`unknown scope: ${SCOPE} (use phase|task|feature|quick)\n`);
68
+ process.exit(2);
69
+ }
70
+
71
+ const state = parseState(readSafe(STATE_FILE));
72
+ if (!state) {
73
+ // No .planning/STATE.md — fall back to a generic shape so the helper
74
+ // still emits something useful for ad-hoc invocations.
75
+ const objective = `${SCOPE[0].toUpperCase() + SCOPE.slice(1)} in ${path.basename(process.cwd())}`;
76
+ process.stdout.write(`/goal ${objective}\n`);
77
+ process.stdout.write(`# token_budget suggestion: ${budget}\n`);
78
+ return;
79
+ }
80
+
81
+ const phaseGoal = readPhaseGoal(readSafe(ROADMAP_FILE), state.phase);
82
+ const objectivePrefix = `Phase ${state.phase}/${state.total} — ${state.name}`;
83
+ const objective = clip(
84
+ phaseGoal ? `${objectivePrefix}: ${phaseGoal}` : objectivePrefix,
85
+ 280
86
+ );
87
+
88
+ process.stdout.write(`/goal ${objective}\n`);
89
+ process.stdout.write(`# token_budget suggestion: ${budget}\n`);
90
+ }
91
+
92
+ main();
package/bin/install.js CHANGED
@@ -205,19 +205,48 @@ function parseAgentMarkdown(content) {
205
205
  return result;
206
206
  }
207
207
 
208
- function renderCodexAgentToml(markdown) {
208
+ function renderCodexAgentToml(markdown, filenameFallback) {
209
209
  const parsed = parseAgentMarkdown(markdown);
210
210
  const body = parsed.body
211
211
  .replaceAll("~/.claude/", "~/.codex/")
212
212
  .replaceAll("@~/.claude/", "@~/.codex/");
213
+ const name = (parsed.name || filenameFallback || "").replace(/^qualia-/, "");
213
214
  const description = parsed.description || "Qualia Framework specialist agent.";
214
215
  return [
216
+ `name = ${tomlString(name)}`,
215
217
  `description = ${tomlString(description)}`,
216
218
  `developer_instructions = ${tomlString(body)}`,
217
219
  "",
218
220
  ].join("\n");
219
221
  }
220
222
 
223
+ // Skills removed in past versions but still present in older installs.
224
+ // Pruned from BOTH ~/.claude/skills/ and ~/.codex/skills/ on every install run
225
+ // so the active surface matches what the framework currently ships.
226
+ const DEPRECATED_SKILLS = [
227
+ "qualia-task", // v5.7.0 — folded into qualia-feature
228
+ "qualia-quick", // v5.7.0 — folded into qualia-feature
229
+ "qualia-polish-loop", // v5.8.0 — folded into qualia-polish --loop
230
+ "qualia-design", // v4 wave 2 — folded into scope-adaptive qualia-polish
231
+ "qualia-prd", // v5.8.0 — surface cleanup
232
+ ];
233
+
234
+ function pruneDeprecatedSkills(baseDir) {
235
+ const skillsDir = path.join(baseDir, "skills");
236
+ if (!fs.existsSync(skillsDir)) return [];
237
+ const removed = [];
238
+ for (const name of DEPRECATED_SKILLS) {
239
+ const target = path.join(skillsDir, name);
240
+ try {
241
+ if (fs.existsSync(target)) {
242
+ fs.rmSync(target, { recursive: true, force: true });
243
+ removed.push(name);
244
+ }
245
+ } catch {}
246
+ }
247
+ return removed;
248
+ }
249
+
221
250
  // Surgically remove orphaned v2.6 install cruft from ~/.claude/ on upgrade.
222
251
  // v2.6 installed a separate ~/.claude/qualia-framework/ directory with workflows/,
223
252
  // references/, assets/, bin/qualia-tools.js. v3 doesn't use any of that — it was
@@ -474,6 +503,8 @@ async function main() {
474
503
  .filter((d) => fs.statSync(path.join(skillsDir, d)).isDirectory());
475
504
 
476
505
  printSection("Skills");
506
+ const claudePruned = pruneDeprecatedSkills(CLAUDE_DIR);
507
+ for (const name of claudePruned) ok(`pruned deprecated: ${name}`);
477
508
  for (const skill of skills) {
478
509
  try {
479
510
  copy(
@@ -783,6 +814,12 @@ async function main() {
783
814
  );
784
815
  fs.chmodSync(path.join(binDest, "project-snapshot.js"), 0o755);
785
816
  ok("project-snapshot.js (ERP/admin project progress snapshot)");
817
+ copy(
818
+ path.join(FRAMEWORK_DIR, "bin", "codex-goal.js"),
819
+ path.join(binDest, "codex-goal.js")
820
+ );
821
+ fs.chmodSync(path.join(binDest, "codex-goal.js"), 0o755);
822
+ ok("codex-goal.js (Codex /goal objective + token-budget suggester)");
786
823
  } catch (e) {
787
824
  warn(`scripts — ${e.message}`);
788
825
  }
@@ -1317,10 +1354,40 @@ async function installCodex(member, target) {
1317
1354
  "hooks = true",
1318
1355
  "plugin_hooks = true",
1319
1356
  "",
1357
+ "# Codex's built-in status line is rendered at the bottom of the TUI.",
1358
+ "# It takes an ARRAY of pre-defined segment names; Codex does NOT support",
1359
+ "# custom-command status lines (unlike Claude's settings.json statusLine),",
1360
+ "# so the Qualia phase/state info is rendered via the SessionStart banner",
1361
+ "# at the top of the session instead. The segment list below mirrors the",
1362
+ "# Codex default rich layout.",
1363
+ "[tui]",
1364
+ 'status_line = ["model-with-reasoning", "task-progress", "current-dir", "git-branch", "context-used", "five-hour-limit", "weekly-limit"]',
1365
+ "status_line_use_colors = true",
1366
+ "",
1320
1367
  ].join("\n"));
1321
1368
  ok("config.toml (minimal Codex config)");
1322
1369
  } else {
1323
- log(`${DIM}config.toml (kept user has customized)${RESET}`);
1370
+ // Existing user config — append [tui] block only if absent. Leaves
1371
+ // every other user setting untouched.
1372
+ try {
1373
+ const existing = fs.readFileSync(configToml, "utf8");
1374
+ if (!/^\[tui\]/m.test(existing) && !/^status_line\s*=/m.test(existing)) {
1375
+ const append = [
1376
+ "",
1377
+ "# Added by qualia-framework — Codex bottom status line.",
1378
+ "[tui]",
1379
+ 'status_line = ["model-with-reasoning", "task-progress", "current-dir", "git-branch", "context-used", "five-hour-limit", "weekly-limit"]',
1380
+ "status_line_use_colors = true",
1381
+ "",
1382
+ ].join("\n");
1383
+ fs.appendFileSync(configToml, append);
1384
+ ok("config.toml (appended [tui] status line block)");
1385
+ } else {
1386
+ log(`${DIM}config.toml (kept — user has customized)${RESET}`);
1387
+ }
1388
+ } catch {
1389
+ log(`${DIM}config.toml (kept — user has customized)${RESET}`);
1390
+ }
1324
1391
  }
1325
1392
  } catch (e) {
1326
1393
  warn(`Codex config.toml — ${e.message}`);
@@ -1347,6 +1414,24 @@ async function installCodex(member, target) {
1347
1414
  warn(`Codex config — ${e.message}`);
1348
1415
  }
1349
1416
 
1417
+ // Mirror the ERP API key from Claude → Codex so erp-retry/report-payload can
1418
+ // post from Codex sessions without a separate provisioning step. The key
1419
+ // resolver at runtime looks in $CODEX_DIR/.erp-api-key only; without this
1420
+ // copy, every Codex ERP write 401s and the queue grows silently.
1421
+ try {
1422
+ const claudeKey = path.join(CLAUDE_DIR, ".erp-api-key");
1423
+ const codexKey = path.join(CODEX_DIR, ".erp-api-key");
1424
+ if (fs.existsSync(claudeKey) && !fs.existsSync(codexKey)) {
1425
+ const key = fs.readFileSync(claudeKey, "utf8");
1426
+ atomicWrite(codexKey, key, 0o600);
1427
+ ok(".erp-api-key (mirrored from ~/.claude/)");
1428
+ } else if (fs.existsSync(codexKey)) {
1429
+ log(`${DIM}.erp-api-key (existing — preserved)${RESET}`);
1430
+ }
1431
+ } catch (e) {
1432
+ warn(`Codex .erp-api-key — ${e.message}`);
1433
+ }
1434
+
1350
1435
  // Scripts
1351
1436
  try {
1352
1437
  const binDest = path.join(CODEX_DIR, "bin");
@@ -1363,6 +1448,7 @@ async function installCodex(member, target) {
1363
1448
  "erp-retry.js",
1364
1449
  "report-payload.js",
1365
1450
  "project-snapshot.js",
1451
+ "codex-goal.js",
1366
1452
  ];
1367
1453
  for (const script of scripts) {
1368
1454
  const src = path.join(FRAMEWORK_DIR, "bin", script);
@@ -1386,7 +1472,7 @@ async function installCodex(member, target) {
1386
1472
  const parsed = parseAgentMarkdown(source);
1387
1473
  const base = parsed.name ? parsed.name.replace(/^qualia-/, "") : path.basename(file, ".md");
1388
1474
  const out = path.join(agentsDest, `${base}.toml`);
1389
- const toml = renderCodexAgentToml(source);
1475
+ const toml = renderCodexAgentToml(source, base);
1390
1476
  backupIfDifferent(out, toml, `agents/${base}.toml`);
1391
1477
  atomicWrite(out, toml);
1392
1478
  }
@@ -1415,6 +1501,8 @@ async function installCodex(member, target) {
1415
1501
  try {
1416
1502
  const skillsSrc = path.join(FRAMEWORK_DIR, "skills");
1417
1503
  const skillsDest = path.join(CODEX_DIR, "skills");
1504
+ const codexPruned = pruneDeprecatedSkills(CODEX_DIR);
1505
+ for (const name of codexPruned) ok(`pruned deprecated: ${name}`);
1418
1506
  for (const skill of fs.readdirSync(skillsSrc)) {
1419
1507
  const src = path.join(skillsSrc, skill);
1420
1508
  if (!fs.statSync(src).isDirectory()) continue;
@@ -1471,6 +1559,18 @@ async function installCodex(member, target) {
1471
1559
  try { fs.chmodSync(out, 0o755); } catch {}
1472
1560
  }
1473
1561
  const nodeCmd = (hookFile) => `node "${path.join(hooksDest, hookFile)}"`;
1562
+ // Codex's hook schema does NOT include an `if` field — only `command`,
1563
+ // `commandWindows`, `timeout`, `async`, `statusMessage`. Filtering on
1564
+ // tool_input.command happens inside each hook script (they read stdin
1565
+ // JSON and `process.exit(0)` fast when the command doesn't match).
1566
+ //
1567
+ // Codex prints `statusMessage` for every entry in the matched group BEFORE
1568
+ // running the hook. Including statusMessage on conditional hooks produced
1569
+ // 8 lines of "Running PreToolUse hook: Qualia X..." on every Bash call
1570
+ // even when 6 of the 8 immediately exited 0. We only set statusMessage on
1571
+ // hooks that always do real work (auto-update + git-guardrails). The
1572
+ // conditional hooks stay registered (so they still fire when applicable)
1573
+ // but render silently.
1474
1574
  const qualiaHooks = {
1475
1575
  hooks: {
1476
1576
  SessionStart: [
@@ -1482,18 +1582,18 @@ async function installCodex(member, target) {
1482
1582
  hooks: [
1483
1583
  { type: "command", command: nodeCmd("auto-update.js"), timeout: 5, statusMessage: "Qualia update check..." },
1484
1584
  { type: "command", command: nodeCmd("git-guardrails.js"), timeout: 5, statusMessage: "Qualia git safety..." },
1485
- { type: "command", if: "Bash(git push*)", command: nodeCmd("branch-guard.js"), timeout: 5, statusMessage: "Qualia branch guard..." },
1486
- { type: "command", if: "Bash(git push*)", command: nodeCmd("pre-push.js"), timeout: 15, statusMessage: "Qualia tracking stamp..." },
1487
- { type: "command", if: "Bash(vercel --prod*)", command: nodeCmd("pre-deploy-gate.js"), timeout: 180, statusMessage: "Qualia deploy gate..." },
1488
- { type: "command", if: "Bash(vercel --prod*)|Bash(vercel deploy*)", command: nodeCmd("vercel-account-guard.js"), timeout: 8, statusMessage: "Qualia Vercel account..." },
1489
- { type: "command", if: "Bash(vercel env*)", command: nodeCmd("env-empty-guard.js"), timeout: 5, statusMessage: "Qualia env guard..." },
1490
- { type: "command", if: "Bash(supabase*)|Bash(npx supabase*)", command: nodeCmd("supabase-destructive-guard.js"), timeout: 5, statusMessage: "Qualia Supabase guard..." },
1585
+ { type: "command", command: nodeCmd("branch-guard.js"), timeout: 5 },
1586
+ { type: "command", command: nodeCmd("pre-push.js"), timeout: 15 },
1587
+ { type: "command", command: nodeCmd("pre-deploy-gate.js"), timeout: 180 },
1588
+ { type: "command", command: nodeCmd("vercel-account-guard.js"), timeout: 8 },
1589
+ { type: "command", command: nodeCmd("env-empty-guard.js"), timeout: 5 },
1590
+ { type: "command", command: nodeCmd("supabase-destructive-guard.js"), timeout: 5 },
1491
1591
  ],
1492
1592
  },
1493
1593
  {
1494
1594
  matcher: "Edit|Write",
1495
1595
  hooks: [
1496
- { type: "command", if: "Edit(*migration*)|Write(*migration*)|Edit(*.sql)|Write(*.sql)", command: nodeCmd("migration-guard.js"), timeout: 10, statusMessage: "Qualia migration guard..." },
1596
+ { type: "command", command: nodeCmd("migration-guard.js"), timeout: 10 },
1497
1597
  ],
1498
1598
  },
1499
1599
  ],
@@ -22,6 +22,33 @@ function qualiaHome() {
22
22
 
23
23
  const QUALIA_HOME = qualiaHome();
24
24
 
25
+ // Self-filter on the proposed bash command — only act when the user is
26
+ // actually trying to deploy. Claude Code's `if: "Bash(vercel --prod*)"` does
27
+ // substring matching (not glob), so commands that contain the literal text
28
+ // "vercel --prod" inside a for-loop or grep argument trip the gate. Codex
29
+ // ignores the `if` field entirely. Both runtimes therefore need this hook to
30
+ // inspect tool_input.command itself before doing any work.
31
+ //
32
+ // Direct invocation (no stdin — `node pre-deploy-gate.js`) is treated as
33
+ // "run the gate" so the test suite and manual `qualia-framework verify` flows
34
+ // keep working. Only when stdin contains a parseable tool_input.command that
35
+ // does NOT match a deploy pattern do we exit early.
36
+ (function selfFilter() {
37
+ if (process.stdin.isTTY) return; // direct invocation — run full gate
38
+ let command = null; // null = no stdin payload, "" = empty command, "str" = parsed
39
+ try {
40
+ const raw = fs.readFileSync(0, "utf8");
41
+ if (raw) {
42
+ const payload = JSON.parse(raw);
43
+ command = (payload && payload.tool_input && payload.tool_input.command) || "";
44
+ }
45
+ } catch {}
46
+ if (command === null) return; // malformed or empty stdin — run full gate
47
+ if (!/^\s*(npx\s+)?vercel\s+(--prod|deploy\s+--prod)\b/.test(command)) {
48
+ process.exit(0);
49
+ }
50
+ })();
51
+
25
52
  function _trace(hookName, result, extra) {
26
53
  try {
27
54
  const os = require("os");
package/hooks/pre-push.js CHANGED
@@ -40,6 +40,25 @@ const QUALIA_HOME = qualiaHome();
40
40
  const TRACKING = path.join(".planning", "tracking.json");
41
41
  const SHELL = process.platform === "win32";
42
42
 
43
+ // Self-filter — only stamp on actual `git push`. Claude's `if` matcher does
44
+ // substring matching (so unrelated commands with "git push" as a literal
45
+ // argument trip the hook) and Codex ignores the `if` field entirely. Direct
46
+ // invocation (no stdin) falls through to the legacy behavior so tests and
47
+ // manual runs still stamp tracking.json.
48
+ (function selfFilter() {
49
+ if (process.stdin.isTTY) return;
50
+ let command = null;
51
+ try {
52
+ const raw = fs.readFileSync(0, "utf8");
53
+ if (raw) {
54
+ const payload = JSON.parse(raw);
55
+ command = (payload && payload.tool_input && payload.tool_input.command) || "";
56
+ }
57
+ } catch {}
58
+ if (command === null) return;
59
+ if (!/^\s*git\s+push\b/.test(command)) process.exit(0);
60
+ })();
61
+
43
62
  function git(args) {
44
63
  return spawnSync("git", args, { encoding: "utf8", timeout: 5000, shell: SHELL });
45
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qualia-framework",
3
- "version": "6.2.7",
3
+ "version": "6.2.9",
4
4
  "description": "Claude Code and Codex workflow framework by Qualia Solutions. Plan, build, verify, ship.",
5
5
  "bin": {
6
6
  "qualia-framework": "./bin/cli.js"
@@ -0,0 +1,46 @@
1
+ # Codex /goal integration
2
+
3
+ When this skill spawns a unit of work on **Codex** (not Claude Code), set the thread goal at the start so Codex's native token-budget + status tracking takes over.
4
+
5
+ ## Runtime detection
6
+
7
+ You are on Codex when `~/.codex/` exists and `~/.claude/` is absent or stale. The simplest probe:
8
+
9
+ ```bash
10
+ test -f ~/.codex/AGENTS.md && echo codex || echo claude
11
+ ```
12
+
13
+ If the answer is `claude`, **skip this entire rule** — Claude Code has no equivalent surface and emitting `/goal` text would just be noise.
14
+
15
+ ## How to set the goal
16
+
17
+ 1. Run the helper to produce the objective string + suggested token budget:
18
+
19
+ ```bash
20
+ node ~/.codex/bin/codex-goal.js {scope}
21
+ ```
22
+
23
+ `{scope}` is one of: `phase` · `task` · `feature` · `quick`. Use the scope of the current skill.
24
+
25
+ 2. The output is two lines:
26
+
27
+ ```
28
+ /goal {objective text from STATE.md + ROADMAP.md}
29
+ # token_budget suggestion: {N}
30
+ ```
31
+
32
+ 3. **If the `update_goal` tool is available** to you (Codex exposes it as a model-callable tool), call it directly with:
33
+ - `objective` = the text after `/goal ` on line 1
34
+ - `token_budget` = the integer suggestion on line 2
35
+
36
+ 4. **If `update_goal` is not available**, surface the `/goal` line to the user in your next message and let them paste it. Do not silently skip — the goal-set takes 1 second and is the only way Codex's budget telemetry knows what to track.
37
+
38
+ ## When NOT to set a goal
39
+
40
+ - The user is on Claude Code (no `/goal` surface).
41
+ - A goal is already active for this thread (Codex rejects `update_goal` when one exists — call `thread/goal/get` first if you're using the tool API directly).
42
+ - The work is open-ended exploration with no clear objective (e.g. `/qualia-idk`, `/qualia-discuss`). Goals are for executing a defined scope.
43
+
44
+ ## Why
45
+
46
+ Codex's `thread_goals` table tracks `objective`, `token_budget`, `tokens_used`, and a `status` enum (`active | paused | blocked | usage_limited | budget_limited | complete`). Setting the goal lets the user see burn-vs-budget in the TUI without the framework reinventing it. The token-budget number also makes the model self-aware of how much context it has left for the current unit of work.
@@ -24,6 +24,10 @@ Execute phase plan. Each task = fresh subagent. Independent tasks run parallel.
24
24
 
25
25
  ## Process
26
26
 
27
+ ### 0. Codex goal (Codex runtime only)
28
+
29
+ Per `rules/codex-goal.md` — set the thread goal at phase start with scope `phase`.
30
+
27
31
  ### 1. Load Plan
28
32
 
29
33
  ```bash
@@ -39,6 +39,10 @@ One command for everything between a typo and a phase. Auto-detects scope from t
39
39
 
40
40
  ## Process
41
41
 
42
+ ### 0. Codex goal (Codex runtime only)
43
+
44
+ Per `rules/codex-goal.md` — set the thread goal with scope matching the auto-detected bucket (`quick` for inline, `feature` for spawn). Do this AFTER Step 2 (auto-detect scope) so the budget matches the actual work shape.
45
+
42
46
  ### 1. Capture description
43
47
 
44
48
  If invoked without args, ask: **"What do you want to build?"**
@@ -27,6 +27,10 @@ Spawn planner to break phase into tasks, validate with checker (max 2 revision c
27
27
 
28
28
  ## Process
29
29
 
30
+ ### 0. Codex goal (Codex runtime only)
31
+
32
+ Per `rules/codex-goal.md` — set the thread goal at plan start with scope `phase`. The objective covers both planning and the subsequent build, so a single goal-set at this stage is enough.
33
+
30
34
  ### 1. Determine Phase & Load Context
31
35
 
32
36
  ```bash
package/tests/bin.test.sh CHANGED
@@ -1360,7 +1360,7 @@ if [ "$EXIT" -eq 0 ] \
1360
1360
  && [ ! -d "$TMP/.claude" ] \
1361
1361
  && grep -q "Role: OWNER" "$TMP/.codex/AGENTS.md" \
1362
1362
  && ! grep -q "{{ROLE}}" "$TMP/.codex/AGENTS.md" \
1363
- && grep -q "Qualia deploy gate" "$TMP/.codex/hooks.json" \
1363
+ && grep -q "pre-deploy-gate.js" "$TMP/.codex/hooks.json" \
1364
1364
  && grep -q "developer_instructions" "$TMP/.codex/agents/planner.toml" \
1365
1365
  && ! grep -R "\.claude/bin" "$TMP/.codex/skills" >/dev/null 2>&1; then
1366
1366
  pass "target=2 → Codex runtime files with Role: OWNER, ~/.claude/ skipped"
@@ -96,7 +96,7 @@ if [ -f "$HOME_DIR/.codex/AGENTS.md" ] \
96
96
  && [ -f "$HOME_DIR/.codex/qualia-references/questioning.md" ] \
97
97
  && grep -q "Role: OWNER" "$HOME_DIR/.codex/AGENTS.md" \
98
98
  && ! grep -q "{{ROLE}}" "$HOME_DIR/.codex/AGENTS.md" \
99
- && grep -q "Qualia deploy gate" "$HOME_DIR/.codex/hooks.json" \
99
+ && grep -q "pre-deploy-gate.js" "$HOME_DIR/.codex/hooks.json" \
100
100
  && ! grep -R "\.claude/bin" "$HOME_DIR/.codex/skills" >/dev/null 2>&1; then
101
101
  pass "Codex runtime installed with OWNER role"
102
102
  else