sprag-cli 3.67.0 → 3.69.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.
Files changed (54) hide show
  1. package/README.ko.md +33 -66
  2. package/README.md +41 -74
  3. package/bin/cli.js +150 -62
  4. package/docs/CODEX.ko.md +32 -0
  5. package/docs/CODEX.md +34 -0
  6. package/package.json +15 -4
  7. package/src/{codex-brief.js → codex/brief.js} +7 -7
  8. package/src/{codex-budget.js → codex/budget.js} +2 -2
  9. package/src/{codex-cache-policy.js → codex/cache-policy.js} +9 -9
  10. package/src/{codex-delegation-diagnostics.js → codex/delegation-diagnostics.js} +2 -2
  11. package/src/{codex-delegation.js → codex/delegation.js} +11 -11
  12. package/src/{codex-doc2md-ledger.js → codex/doc2md-ledger.js} +5 -5
  13. package/src/{codex-doc2md.js → codex/doc2md.js} +4 -4
  14. package/src/{codex-doctor.js → codex/doctor.js} +4 -4
  15. package/src/{codex-harness.js → codex/harness.js} +5 -5
  16. package/src/{codex-hooks.js → codex/hooks.js} +20 -20
  17. package/src/{codex-installer.js → codex/installer.js} +2 -2
  18. package/src/{codex-ledger.js → codex/ledger.js} +6 -6
  19. package/src/{codex-panel-launcher.js → codex/panel-launcher.js} +2 -2
  20. package/src/{codex-panel-runner.js → codex/panel-runner.js} +2 -2
  21. package/src/{codex-panel-session.js → codex/panel-session.js} +1 -1
  22. package/src/{codex-panel-setup.js → codex/panel-setup.js} +1 -1
  23. package/src/{codex-panel-state.js → codex/panel-state.js} +3 -3
  24. package/src/{codex-panel.js → codex/panel.js} +43 -34
  25. package/src/{codex-parser.js → codex/parser.js} +5 -9
  26. package/src/{codex-reply-language.js → codex/reply-language.js} +2 -2
  27. package/src/{codex-route-scan.js → codex/route-scan.js} +10 -10
  28. package/src/{codex-rule-health.js → codex/rule-health.js} +4 -4
  29. package/src/commands/codex-delegate.js +6 -6
  30. package/src/commands/codex.js +44 -44
  31. package/src/commands/install.js +10 -7
  32. package/src/commands/korean.js +1 -1
  33. package/src/commands/litellm-budget.js +56 -0
  34. package/src/commands/saved.js +60 -5
  35. package/src/commands/uninstall.js +3 -3
  36. package/src/config.js +4 -0
  37. package/src/delegation-agents.js +13 -0
  38. package/src/first-run-note.js +16 -0
  39. package/src/formatters/statusline.js +725 -504
  40. package/src/formatters/table.js +33 -2
  41. package/src/glyphs.js +2 -0
  42. package/src/installer.js +77 -1
  43. package/src/model-alias.js +45 -60
  44. package/src/model-rules.js +25 -4
  45. package/src/model-share.js +136 -0
  46. package/src/parser.js +4 -13
  47. package/src/session-records.js +2 -8
  48. package/src/star-note.js +62 -0
  49. package/src/statusline-base.js +9 -3
  50. package/src/stdin-payload.js +1 -1
  51. package/src/transcript-lines.js +40 -0
  52. package/src/changelog.js +0 -70
  53. /package/src/{codex-cache.js → codex/cache.js} +0 -0
  54. /package/src/{codex-panel-shell.js → codex/panel-shell.js} +0 -0
@@ -3,25 +3,25 @@ import { spawn } from 'node:child_process';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { mkdirSync, openSync, readFileSync, readSync, closeSync, statSync } from 'node:fs';
5
5
  import { join, resolve } from 'node:path';
6
- import { loadConfig, userLanguage } from './config.js';
7
- import { koreanStyleInjection, koreanStyleEnabled } from './korean-style.js';
8
- import { cohesionInjection } from './cohesion.js';
9
- import { codexHarnessPaths, syncCodexKoreanBlock } from './codex-harness.js';
10
- import { writeViaTmp } from './state-file.js';
11
- import { findProjectRoot } from './harness.js';
12
- import { runCodexBrief } from './codex-brief.js';
13
- import { codexDocumentTool, codexDocumentNote, convertCodexDocument, codexDocumentContext } from './codex-doc2md.js';
14
- import { codexDelegationTool, codexRouteHint, codexDelegateEnabled } from './codex-delegation.js';
15
- import { bindCodexSubagent, closeStaleCodexRoutes, unsettledCodexRoutes } from './codex-ledger.js';
16
- import { seedOfferBlock } from './seed-rules.js';
17
- import { readCodexRouteScan, openCodexCandidates, shouldRescanCodex } from './codex-route-scan.js';
18
- import { userDataDir } from './paths.js';
19
- import { debug } from './debug.js';
20
- import { updateOffer } from './update-notice.js';
6
+ import { loadConfig, userLanguage } from '../config.js';
7
+ import { koreanStyleInjection, koreanStyleEnabled } from '../korean-style.js';
8
+ import { cohesionInjection } from '../cohesion.js';
9
+ import { codexHarnessPaths, syncCodexKoreanBlock } from './harness.js';
10
+ import { writeViaTmp } from '../state-file.js';
11
+ import { findProjectRoot } from '../harness.js';
12
+ import { runCodexBrief } from './brief.js';
13
+ import { codexDocumentTool, codexDocumentNote, convertCodexDocument, codexDocumentContext } from './doc2md.js';
14
+ import { codexDelegationTool, codexRouteHint, codexDelegateEnabled } from './delegation.js';
15
+ import { bindCodexSubagent, closeStaleCodexRoutes, unsettledCodexRoutes } from './ledger.js';
16
+ import { seedOfferBlock } from '../seed-rules.js';
17
+ import { readCodexRouteScan, openCodexCandidates, shouldRescanCodex } from './route-scan.js';
18
+ import { userDataDir } from '../paths.js';
19
+ import { debug } from '../debug.js';
20
+ import { updateOffer } from '../update-notice.js';
21
21
 
22
22
  const require = createRequire(import.meta.url);
23
- const lint = require('./korean-lint.cjs');
24
- const doc2md = require('./doc2md.cjs');
23
+ const lint = require('../korean-lint.cjs');
24
+ const doc2md = require('../doc2md.cjs');
25
25
 
26
26
  function context(event, text) {
27
27
  return text ? { hookSpecificOutput: { hookEventName: event, additionalContext: text } } : null;
@@ -167,7 +167,7 @@ export async function codexHookOutput(event, payload, { cfg = loadConfig(), refr
167
167
  if (!payload || typeof payload !== 'object') return null;
168
168
  if (event === 'stop') {
169
169
  if (payload.stop_hook_active || !koreanStyleEnabled(cfg) || cfg?.koreanReplyCheck === false) return null;
170
- const { checkCodexReplyLanguage } = await import('./codex-reply-language.js');
170
+ const { checkCodexReplyLanguage } = await import('./reply-language.js');
171
171
  if (!checkCodexReplyLanguage(payload)) return null;
172
172
  return { decision: 'block', reason: '사용자는 한국어로 질문했는데 마지막 답변이 영어로 작성됐습니다. 같은 내용을 한국어로 다시 답하십시오. 코드, 명령어, 오류 원문, 커밋 메시지는 그대로 유지하십시오.' };
173
173
  }
@@ -180,9 +180,9 @@ export async function codexHookOutput(event, payload, { cfg = loadConfig(), refr
180
180
  const parts = [];
181
181
  // Keep the preset file current with this version's wording before it is read.
182
182
  if (event === 'session-start') {
183
- try { (await import('./preset-ratchet.js')).preparePresetRatchet({ agent: 'codex' }); } catch (e) { debug('codex:preset', e); }
183
+ try { (await import('../preset-ratchet.js')).preparePresetRatchet({ agent: 'codex' }); } catch (e) { debug('codex:preset', e); }
184
184
  }
185
- const { presetRatchetPath } = await import('./preset-ratchet.js');
185
+ const { presetRatchetPath } = await import('../preset-ratchet.js');
186
186
  // Lower priority survives the budget first. Rules and style move to a file
187
187
  // when they do not fit; offers and notices are simply left out.
188
188
  for (const [scope, ratchet] of [['global', codexHarnessPaths(root, 'global').ratchet],
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
- import { codexUserDir } from './agent.js';
4
- import { writeViaTmp } from './state-file.js';
3
+ import { codexUserDir } from '../agent.js';
4
+ import { writeViaTmp } from '../state-file.js';
5
5
 
6
6
  export const CODEX_HANDLERS = {
7
7
  SessionStart: { matcher: 'startup|resume|clear|compact', command: 'sprag codex-hook --agent codex --event session-start', timeout: 15 },
@@ -1,12 +1,12 @@
1
1
  import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { randomBytes, randomUUID } from 'node:crypto';
4
- import { userDataDir } from './paths.js';
5
- import { writeViaTmp } from './state-file.js';
6
- import { roundUsd } from './savings-ledger.js';
7
- import { readResetMarks, isBeforeReset } from './saved-reset.cjs';
8
- import { discoverCodexSessionFiles, parseCodexTurns } from './codex-parser.js';
9
- import { codexRunCost, codexPriceBook, readCodexPrices } from './codex-cache-policy.js';
4
+ import { userDataDir } from '../paths.js';
5
+ import { writeViaTmp } from '../state-file.js';
6
+ import { roundUsd } from '../savings-ledger.js';
7
+ import { readResetMarks, isBeforeReset } from '../saved-reset.cjs';
8
+ import { discoverCodexSessionFiles, parseCodexTurns } from './parser.js';
9
+ import { codexRunCost, codexPriceBook, readCodexPrices } from './cache-policy.js';
10
10
 
11
11
  /*
12
12
  * Codex routing savings, kept apart from Claude's delegation ledger.
@@ -2,11 +2,11 @@ import { execFileSync } from 'node:child_process';
2
2
  import { createHash } from 'node:crypto';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { resolve } from 'node:path';
5
- import { codexUserDir } from './agent.js';
5
+ import { codexUserDir } from '../agent.js';
6
6
 
7
7
  const quote = (value) => `'${String(value).replace(/'/g, `'"'"'`)}'`;
8
8
 
9
- export function panelLaunchCommand(root, { node = process.execPath, cli = fileURLToPath(new URL('../bin/cli.js', import.meta.url)), home = codexUserDir(), sessionId, sessionFile } = {}) {
9
+ export function panelLaunchCommand(root, { node = process.execPath, cli = fileURLToPath(new URL('../../bin/cli.js', import.meta.url)), home = codexUserDir(), sessionId, sessionFile } = {}) {
10
10
  return ['env', `CODEX_HOME=${home}`, node, cli, 'panel', '--agent', 'codex', '--project', resolve(root),
11
11
  ...(sessionId ? ['--session', sessionId] : []), ...(sessionFile ? ['--session-file', sessionFile] : [])]
12
12
  .map(quote).join(' ');
@@ -3,8 +3,8 @@ import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
3
3
  import { tmpdir, homedir } from 'node:os';
4
4
  import { join, resolve } from 'node:path';
5
5
  import { randomUUID } from 'node:crypto';
6
- import { panelLaunchCommand } from './codex-panel-launcher.js';
7
- import { createPanelBinding } from './codex-panel-session.js';
6
+ import { panelLaunchCommand } from './panel-launcher.js';
7
+ import { createPanelBinding } from './panel-session.js';
8
8
 
9
9
  const quote = (s) => `'${String(s).replace(/'/g, `'"'"'`)}'`;
10
10
 
@@ -1,6 +1,6 @@
1
1
  import { readFileSync, writeFileSync } from 'node:fs';
2
2
  import { isAbsolute } from 'node:path';
3
- import { writeViaTmp } from './state-file.js';
3
+ import { writeViaTmp } from '../state-file.js';
4
4
 
5
5
  export function createPanelBinding(file) {
6
6
  writeFileSync(file, JSON.stringify({ version: 1, sessionId: null, transcriptPath: null }), { mode: 0o600 });
@@ -11,7 +11,7 @@
11
11
  import { execFileSync } from 'node:child_process';
12
12
  import { existsSync } from 'node:fs';
13
13
  import { basename, dirname, join } from 'node:path';
14
- import { installPanelShell } from './codex-panel-shell.js';
14
+ import { installPanelShell } from './panel-shell.js';
15
15
 
16
16
  /** Whether a tmux binary answers `-V`. */
17
17
  export function tmuxAvailable({ exec = execFileSync } = {}) {
@@ -1,9 +1,9 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { readFileSync, mkdirSync, readdirSync, lstatSync, unlinkSync } from 'node:fs';
3
3
  import { join, resolve } from 'node:path';
4
- import { userDataDir } from './paths.js';
5
- import { codexUserDir } from './agent.js';
6
- import { writeViaTmp, runHousekeeping, sweepStaleTmp } from './state-file.js';
4
+ import { userDataDir } from '../paths.js';
5
+ import { codexUserDir } from '../agent.js';
6
+ import { writeViaTmp, runHousekeeping, sweepStaleTmp } from '../state-file.js';
7
7
 
8
8
  // Every panel session leaves a `-frame` and a `-hook` file behind, keyed by a
9
9
  // hash that is not reused once the session is gone. They are diagnostics for a
@@ -1,33 +1,39 @@
1
1
  import { resolve, relative, isAbsolute, basename } from 'node:path';
2
2
  import { realpathSync, statSync } from 'node:fs';
3
3
  import { stat } from 'node:fs/promises';
4
- import { readPanelBinding } from './codex-panel-session.js';
5
- import { discoverCodexSessionFiles, parseCodexSessionFile } from './codex-parser.js';
6
- import { codexHarnessStatus, codexRatchetCandidates } from './codex-harness.js';
7
- import { harnessListRules, findProjectRoot } from './harness.js';
8
- import { loadConfig, statuslineDefaults } from './config.js';
9
- import { glyphsFor } from './glyphs.js';
10
- import { gaugeBar, formatMoney } from './formatters/statusline.js';
11
- import { codexCacheTimer } from './codex-cache.js';
12
- import { readCodexCachePolicy, readCodexPrices, isDirectOpenAI } from './codex-cache-policy.js';
13
- import { codexIssues } from './codex-brief.js';
14
- import { labelForKey } from './window-labels.js';
15
- import { resolveLabelMode } from './statusline-mode.js';
16
- import { formatResetIn } from './format-time.js';
17
- import { codexDocumentTotals } from './codex-doc2md-ledger.js';
18
- import { codexHookStatus } from './codex-installer.js';
19
- import { koreanStyleEnabled } from './korean-style.js';
20
- import { codexRoutingSavedTotals, loadCodexLedger, codexDelegationStatus } from './codex-ledger.js';
21
- import { loadCodexModelRules, codexDelegateEnabled } from './codex-delegation.js';
22
- import { codexRulesInReview, codexSharedRuleHealth } from './codex-rule-health.js';
23
- import { readCodexRouteScan, openCodexCandidates } from './codex-route-scan.js';
24
- import { codexBudgetProvider } from './codex-budget.js';
25
- import { readUpdateChip } from './update-notice.js';
4
+ import { readPanelBinding } from './panel-session.js';
5
+ import { discoverCodexSessionFiles, parseCodexSessionFile } from './parser.js';
6
+ import { codexHarnessStatus, codexRatchetCandidates } from './harness.js';
7
+ import { harnessListRules, findProjectRoot } from '../harness.js';
8
+ import { loadConfig, statuslineDefaults } from '../config.js';
9
+ import { glyphsFor } from '../glyphs.js';
10
+ import { gaugeBar, formatMoney, DEFAULT_SEGMENT_ORDER } from '../formatters/statusline.js';
11
+ import { codexCacheTimer } from './cache.js';
12
+ import { readCodexCachePolicy, readCodexPrices, isDirectOpenAI } from './cache-policy.js';
13
+ import { codexIssues } from './brief.js';
14
+ import { labelForKey } from '../window-labels.js';
15
+ import { resolveLabelMode } from '../statusline-mode.js';
16
+ import { formatResetIn } from '../format-time.js';
17
+ import { codexDocumentTotals } from './doc2md-ledger.js';
18
+ import { codexHookStatus } from './installer.js';
19
+ import { koreanStyleEnabled } from '../korean-style.js';
20
+ import { codexRoutingSavedTotals, loadCodexLedger, codexDelegationStatus } from './ledger.js';
21
+ import { loadCodexModelRules, codexDelegateEnabled } from './delegation.js';
22
+ import { codexRulesInReview, codexSharedRuleHealth } from './rule-health.js';
23
+ import { readCodexRouteScan, openCodexCandidates } from './route-scan.js';
24
+ import { codexBudgetProvider } from './budget.js';
25
+ import { readUpdateChip } from '../update-notice.js';
26
26
 
27
- export const CODEX_SEGMENT_ORDER = [
28
- 'cap-warn', 'update', 'usage', 'ctx', 'ttl', 'model', 'effort',
29
- 'hit', 'saved', 'doc2md', 'routing', 'harness', 'korean', 'version',
30
- ];
27
+ const CODEX_SUPPORTED_SEGMENTS = new Set([
28
+ 'cap-warn', 'spike', 'usage', 'ctx', 'ttl', 'model', 'effort',
29
+ 'delegated', 'doc2md', 'hit', 'saved', 'harness', 'korean', 'version',
30
+ ]);
31
+ // The Claude statusline order, restricted to what Codex draws. Codex keeps its
32
+ // own upgrade chip right after the cap warning; the Claude line has no such
33
+ // segment and moves `version` forward instead.
34
+ export const CODEX_SEGMENT_ORDER = DEFAULT_SEGMENT_ORDER
35
+ .filter((name) => CODEX_SUPPORTED_SEGMENTS.has(name))
36
+ .flatMap((name) => (name === 'cap-warn' ? [name, 'update'] : [name]));
31
37
 
32
38
  // Only cache full-session parses. A moving time cutoff would make an unchanged
33
39
  // file's cached totals stale. The panel reports session totals, not window totals.
@@ -233,12 +239,13 @@ function panelGroups(data, { now, version = '', full = false, mode = full ? 'tex
233
239
  const level = (n) => n >= 90 ? 31 : n >= 70 ? 33 : 32;
234
240
  const bar = (n) => full ? gauge(n) : gaugeBar(n, mode === 'text' ? 'narrow' : mode);
235
241
  const warnings = [];
242
+ const contextWarnings = [];
236
243
  if (data.error) warnings.push(chip(`ERROR: ${terminalText(data.error)}`, 31));
237
244
  for (const issue of codexIssues(s, { now })) {
238
245
  const key = issue.key.slice(6), win = s.rateLimits?.[key];
239
246
  const text = issue.key === 'context' ? `WARNING: Ctx ${pct(context)}`
240
247
  : `WARNING: ${windowLabel(win.window_minutes, key)} limit ${pct(win.used_percent)}`;
241
- warnings.push(chip(text, issue.level === 'critical' ? 31 : 33));
248
+ (issue.key === 'context' ? contextWarnings : warnings).push(chip(text, issue.level === 'critical' ? 31 : 33));
242
249
  }
243
250
  register('ctx', 'priority', context !== null ? [chip(full
244
251
  ? `Ctx ${bar(context)} ${pct(context)} ${short(s.lastContextTokens)} / ${short(s.contextWindow)}`
@@ -274,6 +281,7 @@ function panelGroups(data, { now, version = '', full = false, mode = full ? 'tex
274
281
  }
275
282
  if (limits.length && s?.rateLimitsAt) limits.push(chip(`snapshot ${age(s.rateLimitsAt, now)}`));
276
283
  register('cap-warn', 'priority', warnings);
284
+ register('spike', 'priority', contextWarnings);
277
285
  register('usage', 'priority', limits);
278
286
  register('hit', 'totals', input > 0 ? [chip(`${icon('hit')}Cache hit ${pct(s.totals.cacheRead / input * 100)} (session)`,
279
287
  s.totals.cacheRead / input >= 0.85 ? 32 : s.totals.cacheRead / input >= 0.7 ? 33 : 31)] : []);
@@ -283,8 +291,14 @@ function panelGroups(data, { now, version = '', full = false, mode = full ? 'tex
283
291
  chip(`Reasoning ${short(s.reasoningOutputTokens)} (in output)`),
284
292
  ] : []);
285
293
  const docs = data.documents;
294
+ // Lifetime figures, in the Claude statusline's order: routing, then documents.
295
+ const delegation = data.delegation;
296
+ register('delegated', 'lifetime', [routingChip(data.routingSaved, chip),
297
+ delegation?.offers ? chip(`Delegation ${delegation.settled} completed / ${delegation.bound} bound / ${delegation.pending} pending / ${delegation.unclaimed} unclaimed (session)`,
298
+ delegation.unclaimed && !delegation.settled && !delegation.bound ? 33 : 90) : null,
299
+ ]);
286
300
  const docSavings = docs?.scope === 'codex-total' ? docs.savings : null;
287
- register('doc2md', 'savings', [
301
+ register('doc2md', 'lifetime', [
288
302
  chip(`Doc2md saved ${docSavings?.priced ? `${signedMoney(docSavings.total)} est. (XML vs MD)`
289
303
  : `n/a (${docs?.docs ? `${docs.docs} unpriced docs` : 'no documents'})`}`, docSavings?.priced ? (docSavings.total < 0 ? 33 : 32) : 90),
290
304
  ...(docSavings?.priced && docSavings.unpriced ? [chip(`${docSavings.unpriced} docs unpriced`)] : []),
@@ -292,11 +306,6 @@ function panelGroups(data, { now, version = '', full = false, mode = full ? 'tex
292
306
  ...(docs.byExt || []).map((row) => chip(`${terminalText(row.ext)} ${number(row.docs)}x`)),
293
307
  ] : []),
294
308
  ]);
295
- const delegation = data.delegation;
296
- register('routing', 'savings', [routingChip(data.routingSaved, chip),
297
- delegation?.offers ? chip(`Delegation ${delegation.settled} completed / ${delegation.bound} bound / ${delegation.pending} pending / ${delegation.unclaimed} unclaimed (session)`,
298
- delegation.unclaimed && !delegation.settled && !delegation.bound ? 33 : 90) : null,
299
- ]);
300
309
  register('harness', 'preferences', [
301
310
  chip(`${icon('harness')}Harness ${h?.error ? 'ERROR' : h ? `${h.configured}/${h.total}` : 'n/a'}`, h?.configured === 5 ? 32 : 33),
302
311
  chip(`Ratchet ${data.rules?.global ?? '?'}/${data.rules?.project ?? '?'}`),
@@ -322,7 +331,7 @@ function panelGroups(data, { now, version = '', full = false, mode = full ? 'tex
322
331
  chip(`SPRAG${version ? ` v${terminalText(version)}` : ''} / ${s ? age(s.lastActivity, now) : 'waiting'}`,
323
332
  s && (!s.lastActivity || now - new Date(s.lastActivity) > 300000) ? 33 : 90),
324
333
  ]);
325
- // Keep session measurements together, before lifetime document counts.
334
+ // Adjacent entries with the same timeframe share a wrapping group.
326
335
  const groups = [];
327
336
  let lastGroup;
328
337
  for (const name of CODEX_SEGMENT_ORDER) {
@@ -1,8 +1,8 @@
1
- import { createReadStream, openSync, readSync, closeSync, fstatSync } from 'node:fs';
1
+ import { openSync, readSync, closeSync, fstatSync } from 'node:fs';
2
2
  import { readdir, stat } from 'node:fs/promises';
3
- import { createInterface } from 'node:readline';
3
+ import { transcriptLines } from '../transcript-lines.js';
4
4
  import { join, resolve } from 'node:path';
5
- import { codexUserDir } from './agent.js';
5
+ import { codexUserDir } from '../agent.js';
6
6
 
7
7
  const count = (n) => Number.isSafeInteger(n) && n >= 0 ? n : 0;
8
8
  const emptyTotals = () => ({ input: 0, cacheRead: 0, cacheCreation: 0, ephemeral5m: 0, ephemeral1h: 0, output: 0 });
@@ -212,9 +212,7 @@ export async function parseCodexTurns(filePath, { cutoffMs = -Infinity } = {}) {
212
212
  let previousUsage = null;
213
213
  let current = null;
214
214
  let context = { model: null, effort: null, cwd: null };
215
- const stream = createReadStream(filePath, { encoding: 'utf8' });
216
- const lines = createInterface({ input: stream, crlfDelay: Infinity });
217
- for await (const line of lines) {
215
+ for await (const line of transcriptLines(filePath)) {
218
216
  let e;
219
217
  try { e = JSON.parse(line); } catch { continue; }
220
218
  const p = e?.payload;
@@ -341,9 +339,7 @@ export async function parseCodexSessionFile(filePath, { cutoffMs = -Infinity } =
341
339
  };
342
340
  const copied = forkedCopyFilter();
343
341
  let previous = null;
344
- const stream = createReadStream(filePath, { encoding: 'utf8' });
345
- const lines = createInterface({ input: stream, crlfDelay: Infinity });
346
- for await (const line of lines) {
342
+ for await (const line of transcriptLines(filePath)) {
347
343
  let e;
348
344
  try { e = JSON.parse(line); } catch { continue; }
349
345
  const p = e?.payload;
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
- import { forkedCopyFilter, codexToolEvents } from './codex-parser.js';
2
+ import { forkedCopyFilter, codexToolEvents } from './parser.js';
3
3
 
4
- const { checkReplyLanguage } = createRequire(import.meta.url)('./reply-language.cjs');
4
+ const { checkReplyLanguage } = createRequire(import.meta.url)('../reply-language.cjs');
5
5
 
6
6
  /** Ignore copied history and commentary; only the current turn's final reply counts. */
7
7
  export function lastCodexTurn(lines, { turnId, sessionId } = {}) {
@@ -1,17 +1,17 @@
1
1
  import { readFileSync, mkdirSync } from 'node:fs';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { userDataDir } from './paths.js';
5
- import { writeViaTmp } from './state-file.js';
6
- import { loadConfig } from './config.js';
7
- import { findProjectRoot } from './harness.js';
8
- import { discoverCodexSessionFiles, parseCodexTurns } from './codex-parser.js';
4
+ import { userDataDir } from '../paths.js';
5
+ import { writeViaTmp } from '../state-file.js';
6
+ import { loadConfig } from '../config.js';
7
+ import { findProjectRoot } from '../harness.js';
8
+ import { discoverCodexSessionFiles, parseCodexTurns } from './parser.js';
9
9
  import { categorize, isSkippable, tierOf, calibrateThresholds, MIN_RECURRENCE,
10
- RESCAN_MIN_INTERVAL_MS, RESCAN_BIG_DELTA_BYTES, RESCAN_MAX_AGE_MS } from './route-scan.js';
11
- import { codexPromptCategory, loadCodexModelRules, sharedCodexModelRules, codexSharedTargets } from './codex-delegation.js';
12
- import { codexRunCost, codexPriceBook, isDirectOpenAI, readCodexPrices } from './codex-cache-policy.js';
13
- import { OPENAI_PRICES_CHECKED_AT } from './openai-prices.js';
14
- import { refreshCodexLedger } from './codex-ledger.js';
10
+ RESCAN_MIN_INTERVAL_MS, RESCAN_BIG_DELTA_BYTES, RESCAN_MAX_AGE_MS } from '../route-scan.js';
11
+ import { codexPromptCategory, loadCodexModelRules, sharedCodexModelRules, codexSharedTargets } from './delegation.js';
12
+ import { codexRunCost, codexPriceBook, isDirectOpenAI, readCodexPrices } from './cache-policy.js';
13
+ import { OPENAI_PRICES_CHECKED_AT } from '../openai-prices.js';
14
+ import { refreshCodexLedger } from './ledger.js';
15
15
 
16
16
  /*
17
17
  * Codex route-scan: recurring easy turns become delegation-rule candidates.
@@ -1,8 +1,8 @@
1
1
  import { resolve } from 'node:path';
2
- import { loadCodexLedger } from './codex-ledger.js';
3
- import { isFailedRun } from './route-scan.js';
4
- import { wilsonLowerBound, HEALTH_ERR_RATE, HEALTH_MIN_SAMPLE_DELEGATED } from './model-rules.js';
5
- import { loadSharedModelRules, sharedModelRulesForProject, realProjectRoot } from './shared-model-rules.js';
2
+ import { loadCodexLedger } from './ledger.js';
3
+ import { isFailedRun } from '../route-scan.js';
4
+ import { wilsonLowerBound, HEALTH_ERR_RATE, HEALTH_MIN_SAMPLE_DELEGATED } from '../model-rules.js';
5
+ import { loadSharedModelRules, sharedModelRulesForProject, realProjectRoot } from '../shared-model-rules.js';
6
6
 
7
7
  /**
8
8
  * Measured outcome of one Codex rule, judged as Claude's rule health judges
@@ -1,15 +1,15 @@
1
1
  import { loadConfig, saveConfig } from '../config.js';
2
- import { configureCodexHooks } from '../codex-installer.js';
2
+ import { configureCodexHooks } from '../codex/installer.js';
3
3
  import { validateCodexTarget, loadCodexModelRules, addCodexModelRule, removeCodexModelRule,
4
4
  validateCodexSharedTarget, codexSharedTargets, codexDelegateMinContext, DEFAULT_DELEGATE_MIN_CONTEXT,
5
- autoCodexSharedTargets, codexDelegateEnabled, codexImplementationHint } from '../codex-delegation.js';
6
- import { findCodexCandidate, resolveCodexCandidate } from '../codex-route-scan.js';
7
- import { codexRoutingSavedTotals, loadCodexLedger } from '../codex-ledger.js';
8
- import { codexRuleHealth, codexRulesInReview, codexSharedRuleHealth } from '../codex-rule-health.js';
5
+ autoCodexSharedTargets, codexDelegateEnabled, codexImplementationHint } from '../codex/delegation.js';
6
+ import { findCodexCandidate, resolveCodexCandidate } from '../codex/route-scan.js';
7
+ import { codexRoutingSavedTotals, loadCodexLedger } from '../codex/ledger.js';
8
+ import { codexRuleHealth, codexRulesInReview, codexSharedRuleHealth } from '../codex/rule-health.js';
9
9
  import { signedUsd } from '../money.js';
10
10
  import { codexProviderName, codexModelName } from '../agent.js';
11
11
  import { loadSharedModelRules, sharedModelRulesForProject, SHARED_RULE_TIERS } from '../shared-model-rules.js';
12
- import { codexDelegationDiagnostics } from '../codex-delegation-diagnostics.js';
12
+ import { codexDelegationDiagnostics } from '../codex/delegation-diagnostics.js';
13
13
 
14
14
  // Starts from the working directory, as the hooks do, so a project policy
15
15
  // stored under either agent's project root is counted.