@lyupro/codex-bridge 0.5.0 → 0.5.2

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 (83) hide show
  1. package/CHANGELOG.md +104 -1
  2. package/README.md +62 -20
  3. package/bin/codex-bridge.mjs +4 -0
  4. package/cli/doctor.mjs +118 -19
  5. package/cli/frontmatter.mjs +137 -0
  6. package/cli/hook.mjs +2 -2
  7. package/cli/install-record.mjs +2 -2
  8. package/cli/install.mjs +14 -2
  9. package/cli/manifest.mjs +23 -22
  10. package/cli/permissions.mjs +2 -2
  11. package/cli/projects.mjs +3 -3
  12. package/cli/prune-plan.mjs +2 -2
  13. package/cli/prune.mjs +1 -1
  14. package/cli/read.mjs +2 -2
  15. package/cli/rules-owners.mjs +26 -4
  16. package/cli/run-lookup.mjs +3 -3
  17. package/cli/runs-inventory.mjs +3 -3
  18. package/cli/settings-merge.mjs +1 -1
  19. package/cli/stop.mjs +5 -5
  20. package/cli/unlock.mjs +7 -7
  21. package/package.json +2 -2
  22. package/scripts/check-file-size.mjs +1 -1
  23. package/src/agents/codex-build.md +265 -231
  24. package/src/agents/codex-review.md +219 -184
  25. package/src/agents/codex-scout.md +232 -200
  26. package/src/{hooks → home/hooks}/guard-tries.mjs +1 -1
  27. package/src/{hooks → home/hooks}/live-runs.mjs +43 -3
  28. package/src/home/hooks/order-gate.mjs +135 -0
  29. package/src/{hooks → home/hooks}/prune-guard.mjs +2 -2
  30. package/src/{hooks → home/hooks}/reply-guard.mjs +117 -97
  31. package/src/home/hooks/reply-parser.mjs +27 -0
  32. package/src/home/hooks/reply-verdicts.mjs +174 -0
  33. package/src/{hooks → home/hooks}/stop-guard.mjs +3 -3
  34. package/src/{hooks → home/hooks}/worktree-lock.mjs +3 -3
  35. package/src/home/lib/dispatcher-order.mjs +36 -0
  36. package/src/{hook-definitions.mjs → home/lib/hook-definitions.mjs} +1 -1
  37. package/src/home/lib/host-refusal.mjs +20 -0
  38. package/src/{json-file.mjs → home/lib/json-file.mjs} +4 -4
  39. package/src/{meta → home/lib/meta}/paths.mjs +20 -1
  40. package/src/{meta → home/lib/meta}/reply.mjs +1 -1
  41. package/src/{meta → home/lib/meta}/verdict.mjs +1 -1
  42. package/src/{no-self-execution.mjs → home/lib/no-self-execution.mjs} +7 -2
  43. package/src/{required-inputs.mjs → home/lib/required-inputs.mjs} +56 -5
  44. package/src/{retention.mjs → home/lib/retention.mjs} +1 -1
  45. package/src/{run-codex.mjs → home/lib/run-codex.mjs} +39 -11
  46. package/src/{run-config.mjs → home/lib/run-config.mjs} +2 -2
  47. package/src/{runner → home/lib/runner}/args.mjs +86 -10
  48. package/src/{runner → home/lib/runner}/attach.mjs +58 -9
  49. package/src/{runner → home/lib/runner}/launcher.mjs +33 -21
  50. package/src/home/lib/runner/order-owner.mjs +28 -0
  51. package/src/{runner → home/lib/runner}/run-env.mjs +1 -1
  52. package/src/home/lib/runner/task-file.mjs +92 -0
  53. package/src/home/lib/runner/task-input.mjs +26 -0
  54. package/src/{runner → home/lib/runner}/worker.mjs +1 -1
  55. package/src/home/lib/shell-unsafe.mjs +26 -0
  56. package/src/{write-meta.mjs → home/lib/write-meta.mjs} +12 -1
  57. package/src/hooks/order-gate.mjs +0 -73
  58. /package/src/{run-config.json → home/config.json} +0 -0
  59. /package/src/{conventions.md → home/conventions.md} +0 -0
  60. /package/src/{cli-names.mjs → home/lib/cli-names.mjs} +0 -0
  61. /package/src/{heartbeat.mjs → home/lib/heartbeat.mjs} +0 -0
  62. /package/src/{meta → home/lib/meta}/chain.mjs +0 -0
  63. /package/src/{meta → home/lib/meta}/deadline.mjs +0 -0
  64. /package/src/{meta → home/lib/meta}/environment.mjs +0 -0
  65. /package/src/{meta → home/lib/meta}/events.mjs +0 -0
  66. /package/src/{meta → home/lib/meta}/outcome.mjs +0 -0
  67. /package/src/{meta → home/lib/meta}/pre-start.mjs +0 -0
  68. /package/src/{meta → home/lib/meta}/reason.mjs +0 -0
  69. /package/src/{meta → home/lib/meta}/run-state.mjs +0 -0
  70. /package/src/{meta → home/lib/meta}/startup.mjs +0 -0
  71. /package/src/{meta → home/lib/meta}/transport.mjs +0 -0
  72. /package/src/{process-identity.mjs → home/lib/process-identity.mjs} +0 -0
  73. /package/src/{runner → home/lib/runner}/codex-cmd.mjs +0 -0
  74. /package/src/{runner → home/lib/runner}/continuation.mjs +0 -0
  75. /package/src/{runner → home/lib/runner}/conventions.mjs +0 -0
  76. /package/src/{runner → home/lib/runner}/git-state.mjs +0 -0
  77. /package/src/{runner → home/lib/runner}/project-dir.mjs +0 -0
  78. /package/src/{runner → home/lib/runner}/prompts.mjs +0 -0
  79. /package/src/{runner → home/lib/runner}/run-context.mjs +0 -0
  80. /package/src/{runner → home/lib/runner}/runs-root.mjs +0 -0
  81. /package/src/{runner → home/lib/runner}/schemas.mjs +0 -0
  82. /package/src/{runner → home/lib/runner}/scope-check.mjs +0 -0
  83. /package/src/{stop-contract.mjs → home/lib/stop-contract.mjs} +0 -0
package/cli/install.mjs CHANGED
@@ -25,7 +25,8 @@ import {
25
25
  withSettingsRun,
26
26
  } from './settings-merge.mjs';
27
27
  import { addRulesOwner, readRulesRegistry } from './rules-owners.mjs';
28
- import { readRunConfig, retentionNotice } from '../src/run-config.mjs';
28
+ import { addPermissionRules } from './permissions.mjs';
29
+ import { readRunConfig, retentionNotice } from '../src/home/lib/run-config.mjs';
29
30
 
30
31
  const WARNING = '\u001b[33m';
31
32
  const RESET = '\u001b[0m';
@@ -158,6 +159,13 @@ async function installInRun({ host, dryRun = false, force = false, packageRoot,
158
159
  }
159
160
  const hookResults = [];
160
161
  for (const { spec } of targets) hookResults.push(await mergeHook(host.settingsPath, spec));
162
+ // Uninstall has always removed these rules, and install never granted them — so removal took
163
+ // away what installation never gave. Without the rule the host refuses the package command,
164
+ // and the 2026-08-15 `cartoons-r136-scout-lock-scope` order shows what a dispatcher does next:
165
+ // it goes looking for a way around, down to the absolute path to run-codex.mjs that Plan_41
166
+ // removed. The rule follows the scope of the install, because host.settingsPath already is the
167
+ // scope — a --scope project install must not reach into the operator's global config.
168
+ const permissionResult = dryRun ? { added: 0 } : await addPermissionRules(host.settingsPath);
161
169
  const fingerprints = {};
162
170
  for (const item of plan) {
163
171
  fingerprints[item.root] ??= {};
@@ -188,7 +196,11 @@ async function installInRun({ host, dryRun = false, force = false, packageRoot,
188
196
  });
189
197
  return {
190
198
  exitCode: 0,
191
- output: retentionOutput(configuredRetentionLine, `Installed ${plan.length + 1} files and registered the ${targets.map(({ definition }) => definition.event).join(' and ')} hooks.`),
199
+ output: retentionOutput(
200
+ configuredRetentionLine,
201
+ `Installed ${plan.length + 1} files and registered the ${targets.map(({ definition }) => definition.event).join(' and ')} hooks.`
202
+ + `\nGranted ${permissionResult.added} permission rule(s) in ${host.settingsPath} so the host runs the package command without asking.`,
203
+ ),
192
204
  };
193
205
  }
194
206
 
package/cli/manifest.mjs CHANGED
@@ -3,14 +3,15 @@ import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { createHash } from 'node:crypto';
5
5
  import { fileURLToPath } from 'node:url';
6
- import { HOOK_DEFINITIONS } from '../src/hook-definitions.mjs';
6
+ import { HOOK_DEFINITIONS } from '../src/home/lib/hook-definitions.mjs';
7
7
  import {
8
8
  renderRequiredInputSummary,
9
9
  renderRequiredInputs,
10
- } from '../src/required-inputs.mjs';
11
- import { renderNoSelfExecution } from '../src/no-self-execution.mjs';
12
- import { renderStopSummary } from '../src/stop-contract.mjs';
13
- import { readJsonFile } from '../src/json-file.mjs';
10
+ } from '../src/home/lib/required-inputs.mjs';
11
+ import { renderNoSelfExecution } from '../src/home/lib/no-self-execution.mjs';
12
+ import { renderStopSummary } from '../src/home/lib/stop-contract.mjs';
13
+ import { readJsonFile } from '../src/home/lib/json-file.mjs';
14
+ import { normalizeFrontmatter } from './frontmatter.mjs';
14
15
  import {
15
16
  INSTALL_RECORD_NAME,
16
17
  RULES_NAME,
@@ -49,18 +50,19 @@ const RULES_SOURCE = `src/rules/${RULES_NAME}`;
49
50
 
50
51
  /**
51
52
  * Files the operator owns once they exist. Seeded on first install so the defaults are
52
- * visible and editable, then never written, never compared, never removed: run-config.json
53
+ * visible and editable, then never written, never compared, never removed: config.json
53
54
  * is where a host states which model each mode runs on, conventions.md is the rule set it hands
54
55
  * every run, and an installer that overwrites either would erase host decisions on the next
55
56
  * update — the same way overwriting a .env would.
56
57
  */
57
- export const SEEDED_SOURCES = Object.freeze(['src/run-config.json', 'src/conventions.md']);
58
+ export const SEEDED_SOURCES = Object.freeze(['src/home/config.json', 'src/home/conventions.md']);
58
59
 
59
60
  export const INSTALL_TABLE = Object.freeze([
60
61
  { source: 'src/agents/*.md', root: 'claude', target: 'agentsDir', processing: 'placeholders' },
61
62
  { source: 'src/commands/*.md', root: 'claude', target: 'commandsDir', processing: 'placeholders' },
62
- { source: 'src/hooks/**', root: 'brand', target: 'brandHooksDir', processing: 'copy' },
63
- { source: 'src/**', root: 'brand', target: 'brandRunnerDir', processing: 'copy' },
63
+ // The source is the host image. Keeping one source root and one target root makes any future
64
+ // remapping visible as a test failure instead of another clone-only import success.
65
+ { source: 'src/home/**', root: 'brand', target: 'brandRoot', processing: 'copy' },
64
66
  // The runner reads its own version out of the package.json beside it, so meta.json can say
65
67
  // which code wrote a run (Plan_34). In the clone that file sits one level above src/; without
66
68
  // this line the installed layout has nothing one level above lib/, and every run on a real host
@@ -72,12 +74,13 @@ const posix = (value) => value.split(path.sep).join('/');
72
74
 
73
75
  export function replacePlaceholders(content, installationRoot) {
74
76
  const agentType = content.match(/^name:\s*([^\r\n]+)$/m)?.[1].trim();
75
- return content
77
+ const replaced = content
76
78
  .replaceAll('{{CODEX_BRIDGE_DIR}}', posix(path.resolve(installationRoot)))
77
79
  .replaceAll('{{CODEX_REQUIRED_INPUTS_SUMMARY}}', renderRequiredInputSummary(agentType))
78
80
  .replaceAll('{{CODEX_REQUIRED_INPUTS}}', renderRequiredInputs(agentType))
79
81
  .replaceAll('{{CODEX_NO_SELF_EXECUTION}}', renderNoSelfExecution())
80
82
  .replaceAll('{{CODEX_STOP_SUMMARY}}', renderStopSummary());
83
+ return normalizeFrontmatter(replaced);
81
84
  }
82
85
 
83
86
  export async function packageInfo(packageRoot = PACKAGE_ROOT) {
@@ -92,14 +95,13 @@ export function installedHookPath(host, definition) {
92
95
  /** Where each seeded file comes from and where it goes; contents are copied verbatim. */
93
96
  export function seedPlan(host, packageRoot = PACKAGE_ROOT) {
94
97
  return SEEDED_SOURCES.map((source) => {
95
- const target = source.endsWith('run-config.json')
96
- ? host.brandConfigPath
97
- : host.brandConventionsPath;
98
+ const relativeToRoot = posix(path.relative('src/home', source));
99
+ const target = path.join(host.brandRoot, relativeToRoot);
98
100
  return {
99
101
  source: path.join(packageRoot, source),
100
102
  target,
101
103
  root: 'brand',
102
- relativeToRoot: posix(path.relative(host.brandRoot, target)),
104
+ relativeToRoot,
103
105
  processing: 'copy',
104
106
  };
105
107
  });
@@ -115,13 +117,17 @@ export function rulesPlan(host, packageRoot = PACKAGE_ROOT) {
115
117
 
116
118
  export async function fileFingerprint(absolutePath) {
117
119
  try {
118
- return createHash('sha256').update(await fs.readFile(absolutePath)).digest('hex');
120
+ return contentFingerprint(await fs.readFile(absolutePath));
119
121
  } catch (err) {
120
122
  if (err.code === 'ENOENT') return null;
121
123
  throw err;
122
124
  }
123
125
  }
124
126
 
127
+ export function contentFingerprint(content) {
128
+ return createHash('sha256').update(content).digest('hex');
129
+ }
130
+
125
131
  function targetBase(host, mapping) {
126
132
  const base = host[mapping.target];
127
133
  if (!base) throw new Error(`host has no ${mapping.target} install target`);
@@ -136,17 +142,13 @@ function targetRelative(packageRoot, source, mapping) {
136
142
  if (mapping.processing === 'placeholders' || mapping.source === 'package.json') {
137
143
  return path.basename(source);
138
144
  }
139
- const sourceRoot = mapping.source === 'src/hooks/**'
140
- ? path.join(packageRoot, 'src', 'hooks')
141
- : path.join(packageRoot, 'src');
142
- return path.relative(sourceRoot, source);
145
+ return path.relative(path.join(packageRoot, 'src', 'home'), source);
143
146
  }
144
147
 
145
148
  export async function buildInstallPlan(host, packageRoot = PACKAGE_ROOT) {
146
149
  const files = [];
147
- const claimedSources = [];
148
150
  for (const mapping of INSTALL_TABLE) {
149
- for await (const relative of fs.glob(mapping.source, { cwd: packageRoot, exclude: claimedSources })) {
151
+ for await (const relative of fs.glob(mapping.source, { cwd: packageRoot })) {
150
152
  if (posix(relative) === RULES_SOURCE || SEEDED_SOURCES.includes(posix(relative))) continue;
151
153
  const source = path.join(packageRoot, relative);
152
154
  if (!(await fs.stat(source)).isFile()) continue;
@@ -165,7 +167,6 @@ export async function buildInstallPlan(host, packageRoot = PACKAGE_ROOT) {
165
167
  : rootFor(host, mapping),
166
168
  });
167
169
  }
168
- claimedSources.push(mapping.source);
169
170
  }
170
171
  return files.sort((a, b) => recordFileKey(a).localeCompare(recordFileKey(b)));
171
172
  }
@@ -1,8 +1,8 @@
1
1
  /** Manages the optional shell permission rules for every codex-bridge command spelling. */
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { CLI_NAMES } from '../src/cli-names.mjs';
5
- import { SHELL_TOOLS } from '../src/hook-definitions.mjs';
4
+ import { CLI_NAMES } from '../src/home/lib/cli-names.mjs';
5
+ import { SHELL_TOOLS } from '../src/home/lib/hook-definitions.mjs';
6
6
  import { readSettings, updateSettings, withSettingsRun } from './settings-merge.mjs';
7
7
 
8
8
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
package/cli/projects.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  /** Implements the projects command. */
2
2
  import { listProjectRuns, listProjects } from './runs-inventory.mjs';
3
3
  import { renderTable } from './table.mjs';
4
- import { allLiveRuns } from '../src/hooks/live-runs.mjs';
5
- import { runsRoot } from '../src/runner/runs-root.mjs';
6
- import { STOP_COMMAND_TEMPLATE } from '../src/stop-contract.mjs';
4
+ import { allLiveRuns } from '../src/home/hooks/live-runs.mjs';
5
+ import { runsRoot } from '../src/home/lib/runner/runs-root.mjs';
6
+ import { STOP_COMMAND_TEMPLATE } from '../src/home/lib/stop-contract.mjs';
7
7
 
8
8
  // The table renders whatever it is given; how a byte count or a timestamp reads is this command's
9
9
  // business. Raw bytes and ISO milliseconds are technically exact and practically unreadable — an
@@ -3,8 +3,8 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { listProjectRuns, listProjects, recursiveSize } from './runs-inventory.mjs';
5
5
  import { parseOlderThan } from './prune-args.mjs';
6
- import { runsRoot } from '../src/runner/runs-root.mjs';
7
- import { runIsOlderThan, TRANSPORT_FILES } from '../src/retention.mjs';
6
+ import { runsRoot } from '../src/home/lib/runner/runs-root.mjs';
7
+ import { runIsOlderThan, TRANSPORT_FILES } from '../src/home/lib/retention.mjs';
8
8
 
9
9
  export { runIsOlderThan, TRANSPORT_FILES };
10
10
 
package/cli/prune.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import readline from 'node:readline/promises';
5
- import { normalizePath } from '../src/hooks/live-runs.mjs';
5
+ import { normalizePath } from '../src/home/hooks/live-runs.mjs';
6
6
  import { parsePruneArgs } from './prune-args.mjs';
7
7
  import { prunePlan } from './prune-plan.mjs';
8
8
  import { recursiveSize } from './runs-inventory.mjs';
package/cli/read.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Renders a run's structured Codex transport as a readable event-by-event report. */
2
- import { readEvents } from '../src/meta/events.mjs';
3
- import { runsRoot } from '../src/runner/runs-root.mjs';
2
+ import { readEvents } from '../src/home/lib/meta/events.mjs';
3
+ import { runsRoot } from '../src/home/lib/runner/runs-root.mjs';
4
4
  import { resolveRunFolder } from './run-lookup.mjs';
5
5
 
6
6
  const result = (exitCode, output) => ({ exitCode, output });
@@ -2,8 +2,8 @@
2
2
  import fs from 'node:fs/promises';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import path from 'node:path';
5
- import { normalizeRepoPath } from '../src/runner/project-dir.mjs';
6
- import { readJsonFile } from '../src/json-file.mjs';
5
+ import { normalizeRepoPath } from '../src/home/lib/runner/project-dir.mjs';
6
+ import { readJsonFile } from '../src/home/lib/json-file.mjs';
7
7
 
8
8
  export const RULES_REGISTRY_NAME = '.codex-bridge-rules.json';
9
9
  export const RULES_REGISTRY_VERSION = 1;
@@ -99,19 +99,41 @@ async function dropStaleLock(lockPath) {
99
99
  }
100
100
  }
101
101
 
102
+ /**
103
+ * Windows answers a taken lock with three different codes, and only one of them is EEXIST. A file
104
+ * whose last handle closed while a delete was pending stays visible but refuses to be opened:
105
+ * `open(..., 'wx')` comes back EPERM, and a file another process still holds comes back EBUSY.
106
+ * Treating those as fatal is what made `tests/cli/rules-owners.test.mjs` fail twice on 2026-08-11
107
+ * with EPERM on the lock file — the suite went red over a lock that was simply busy for one more
108
+ * millisecond. All three mean the same thing to a caller waiting for a lock: not yours yet.
109
+ */
110
+ const LOCK_TAKEN_CODES = new Set(['EEXIST', 'EPERM', 'EBUSY']);
111
+
112
+ /** Exported so the retry contract is asserted directly; the race itself reproduces only by luck. */
113
+ export function isLockTaken(err) {
114
+ return LOCK_TAKEN_CODES.has(err?.code);
115
+ }
116
+
102
117
  async function acquireRegistryLock(host) {
103
118
  await fs.mkdir(host.codexRulesDir, { recursive: true });
104
119
  const lockPath = registryLockPath(host);
120
+ let lastTaken;
105
121
  for (let attempt = 0; attempt < REGISTRY_LOCK_RETRIES; attempt += 1) {
106
122
  try {
107
123
  const handle = await fs.open(lockPath, 'wx');
108
124
  return { handle, lockPath };
109
125
  } catch (err) {
110
- if (err.code !== 'EEXIST') throw err;
126
+ if (!isLockTaken(err)) throw err;
127
+ lastTaken = err;
111
128
  if (!(await dropStaleLock(lockPath))) await waitForRegistryLock();
112
129
  }
113
130
  }
114
- throw new Error(`timed out waiting for rules ownership registry lock: ${lockPath}`);
131
+ // The code is part of the message: a timeout on EPERM points at a delete that never completed,
132
+ // a timeout on EEXIST at an owner that never released.
133
+ throw new Error(
134
+ `timed out waiting for rules ownership registry lock: ${lockPath} (last attempt: ${lastTaken.code})`,
135
+ { cause: lastTaken },
136
+ );
115
137
  }
116
138
 
117
139
  async function withRegistryLock(host, action) {
@@ -1,9 +1,9 @@
1
1
  /** Resolves a run argument and confirms that it names an existing run directory. */
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import { git } from '../src/runner/git-state.mjs';
5
- import { resolveProjectRunsDir } from '../src/runner/project-dir.mjs';
6
- import { runsRoot } from '../src/runner/runs-root.mjs';
4
+ import { git } from '../src/home/lib/runner/git-state.mjs';
5
+ import { resolveProjectRunsDir } from '../src/home/lib/runner/project-dir.mjs';
6
+ import { runsRoot } from '../src/home/lib/runner/runs-root.mjs';
7
7
 
8
8
  function projectRoot(cwd) {
9
9
  const result = git(cwd, ['rev-parse', '--show-toplevel']);
@@ -1,9 +1,9 @@
1
1
  /** Reads run-store directories into inventory data. */
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import { liveRuns } from '../src/hooks/live-runs.mjs';
5
- import { readJsonFileSync } from '../src/json-file.mjs';
6
- import { runsRoot } from '../src/runner/runs-root.mjs';
4
+ import { liveRuns } from '../src/home/hooks/live-runs.mjs';
5
+ import { readJsonFileSync } from '../src/home/lib/json-file.mjs';
6
+ import { runsRoot } from '../src/home/lib/runner/runs-root.mjs';
7
7
 
8
8
  const record = (value) => value && typeof value === 'object' && !Array.isArray(value);
9
9
 
@@ -5,7 +5,7 @@ import { existsSync, statSync } from 'node:fs';
5
5
  import fs from 'node:fs/promises';
6
6
  import path from 'node:path';
7
7
  import { randomUUID } from 'node:crypto';
8
- import { readJsonFileWithRaw } from '../src/json-file.mjs';
8
+ import { readJsonFileWithRaw } from '../src/home/lib/json-file.mjs';
9
9
 
10
10
  const LEGACY_SPEC = { event: 'SubagentStop', matcher: '*' };
11
11
  const settingsRuns = new AsyncLocalStorage();
package/cli/stop.mjs CHANGED
@@ -1,16 +1,16 @@
1
1
  /** Stops a live run and records the same abandoned FAIL artifacts as the normal unlock. */
2
2
  import path from 'node:path';
3
- import { stopCodex } from '../src/runner/codex-cmd.mjs';
4
- import { git, worktreeSnapshot } from '../src/runner/git-state.mjs';
3
+ import { stopCodex } from '../src/home/lib/runner/codex-cmd.mjs';
4
+ import { git, worktreeSnapshot } from '../src/home/lib/runner/git-state.mjs';
5
5
  import {
6
6
  IDENTITY_ALIVE,
7
7
  IDENTITY_FOREIGN,
8
8
  IDENTITY_UNVERIFIED,
9
9
  processAlive,
10
10
  processIdentity,
11
- } from '../src/process-identity.mjs';
12
- import { markAbandoned, readJson } from '../src/write-meta.mjs';
13
- import { runsRoot } from '../src/runner/runs-root.mjs';
11
+ } from '../src/home/lib/process-identity.mjs';
12
+ import { markAbandoned, readJson } from '../src/home/lib/write-meta.mjs';
13
+ import { runsRoot } from '../src/home/lib/runner/runs-root.mjs';
14
14
  import { resolveRunFolder } from './run-lookup.mjs';
15
15
 
16
16
  const EXIT_POLL_MS = 25;
package/cli/unlock.mjs CHANGED
@@ -2,17 +2,17 @@
2
2
  /** Closes dead-pid running records without deleting any run artifacts. */
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
- import { formatSilence, heartbeatAge, HEARTBEAT_STALE_MS } from '../src/heartbeat.mjs';
6
- import { markAbandoned } from '../src/meta/run-state.mjs';
5
+ import { formatSilence, heartbeatAge, HEARTBEAT_STALE_MS } from '../src/home/lib/heartbeat.mjs';
6
+ import { markAbandoned } from '../src/home/lib/meta/run-state.mjs';
7
7
  import {
8
8
  IDENTITY_ALIVE,
9
9
  IDENTITY_UNVERIFIED,
10
10
  processIdentity,
11
- } from '../src/process-identity.mjs';
12
- import { git } from '../src/runner/git-state.mjs';
13
- import { resolveProjectRunsDir } from '../src/runner/project-dir.mjs';
14
- import { runsRoot } from '../src/runner/runs-root.mjs';
15
- import { readJson } from '../src/write-meta.mjs';
11
+ } from '../src/home/lib/process-identity.mjs';
12
+ import { git } from '../src/home/lib/runner/git-state.mjs';
13
+ import { resolveProjectRunsDir } from '../src/home/lib/runner/project-dir.mjs';
14
+ import { runsRoot } from '../src/home/lib/runner/runs-root.mjs';
15
+ import { readJson } from '../src/home/lib/write-meta.mjs';
16
16
  import { renderTable } from './table.mjs';
17
17
 
18
18
  const CLOSED_COLUMNS = [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lyupro/codex-bridge",
3
- "version": "0.5.0",
4
- "description": "Claude Code-to-Codex delegation with 5 guards, 603 tests, zero runtime dependencies, and zero build steps.",
3
+ "version": "0.5.2",
4
+ "description": "Claude Code-to-Codex delegation with 5 guards, 604 tests, zero runtime dependencies, and zero build steps.",
5
5
  "keywords": [
6
6
  "claude-code",
7
7
  "codex",
@@ -21,7 +21,7 @@
21
21
  import fs from 'node:fs/promises';
22
22
  import path from 'node:path';
23
23
  import { fileURLToPath } from 'node:url';
24
- import { readJsonFile } from '../src/json-file.mjs';
24
+ import { readJsonFile } from '../src/home/lib/json-file.mjs';
25
25
 
26
26
  const HERE = path.dirname(fileURLToPath(import.meta.url));
27
27
  export const REPO_ROOT = path.resolve(HERE, '..');