@ikon85/agent-workflow-kit 0.43.0 → 0.44.1

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 (127) hide show
  1. package/.agents/skills/grill-me/SKILL.md +3 -1
  2. package/.agents/skills/grill-with-docs/SKILL.md +3 -1
  3. package/.agents/skills/kit-update/SKILL.md +9 -11
  4. package/.agents/skills/orchestrate-wave/SKILL.md +8 -8
  5. package/.agents/skills/setup-workflow/SKILL.md +20 -18
  6. package/.agents/skills/setup-workflow/board-sync.md +4 -1
  7. package/.agents/skills/setup-workflow/workflow-advisories.md +3 -2
  8. package/.agents/skills/setup-workflow/worktree-lifecycle.md +36 -31
  9. package/.agents/skills/to-issues/SKILL.md +43 -0
  10. package/.agents/skills/wrapup/SKILL.md +98 -54
  11. package/.claude/skills/grill-me/SKILL.md +3 -1
  12. package/.claude/skills/grill-me-codex/SKILL.md +3 -1
  13. package/.claude/skills/grill-with-docs/SKILL.md +3 -1
  14. package/.claude/skills/grill-with-docs-codex/SKILL.md +3 -1
  15. package/.claude/skills/kit-update/SKILL.md +9 -11
  16. package/.claude/skills/orchestrate-wave/SKILL.md +8 -8
  17. package/.claude/skills/setup-workflow/SKILL.md +20 -18
  18. package/.claude/skills/setup-workflow/board-sync.md +4 -1
  19. package/.claude/skills/setup-workflow/workflow-advisories.md +3 -2
  20. package/.claude/skills/setup-workflow/worktree-lifecycle.md +36 -31
  21. package/.claude/skills/skill-manifest.json +1 -1
  22. package/.claude/skills/to-issues/SKILL.md +43 -0
  23. package/.claude/skills/wrapup/SKILL.md +98 -54
  24. package/README.md +254 -7
  25. package/agent-workflow-kit.package.json +43 -43
  26. package/package.json +16 -12
  27. package/scripts/marker_lib.py +3 -0
  28. package/scripts/profile_globs.py +26 -45
  29. package/scripts/release-delta-guard.mjs +26 -9
  30. package/scripts/worktree-lifecycle/README.md +57 -160
  31. package/scripts/worktree-lifecycle/capabilities.json +2 -3
  32. package/scripts/worktree-lifecycle/classify.py +480 -0
  33. package/scripts/worktree-lifecycle/cleanup.py +9 -29
  34. package/scripts/worktree-lifecycle/core.py +68 -1429
  35. package/scripts/worktree-lifecycle/ignore_seed.py +1 -1
  36. package/scripts/worktree-lifecycle/profile.py +46 -29
  37. package/scripts/worktree-lifecycle/setup.py +7 -19
  38. package/scripts/wrapup-land.py +1292 -545
  39. package/src/consumer-migrations.json +1 -16
  40. package/src/lib/bundle.mjs +31 -1
  41. package/docs/adr/0001-consumer-divergence-policy.md +0 -53
  42. package/docs/adr/0002-capability-gated-orchestration.md +0 -70
  43. package/docs/adr/0003-kit-core-and-project-extension-lifecycle.md +0 -63
  44. package/docs/adr/0004-release-intent-is-a-version-tag.md +0 -64
  45. package/docs/adr/0005-to-issues-is-the-planning-facade.md +0 -42
  46. package/docs/adr/0006-routing-knowledge-access-and-policy-are-separate.md +0 -95
  47. package/docs/adr/0007-session-teardown-requires-provenance-bound-ownership.md +0 -88
  48. package/docs/adr/0008-planning-ignore-rules-are-offered-never-installed.md +0 -84
  49. package/docs/adr/0009-teardown-authority-is-stateless-repository-classification.md +0 -101
  50. package/docs/adr/0010-model-roster-replaces-the-optimization-dial.md +0 -87
  51. package/docs/agents/board-sync.md +0 -74
  52. package/docs/agents/code-review.md +0 -14
  53. package/docs/agents/workflow-capabilities.json +0 -28
  54. package/docs/research/agent-task-taxonomy-benchmark-coverage.md +0 -451
  55. package/docs/research/benchlm-routing-source.md +0 -198
  56. package/docs/research/consumer-owned-protocol-files.md +0 -238
  57. package/docs/research/frontend-agent-benchmarks.md +0 -282
  58. package/docs/research/model-effort-routing-benchmarks.md +0 -261
  59. package/docs/research/provider-neutral-agent-routing.md +0 -207
  60. package/docs/research/wave-152-consumer-acceptance.md +0 -98
  61. package/docs/research/wave-43-script-hook-census.md +0 -128
  62. package/scripts/build-kit.mjs +0 -117
  63. package/scripts/build-kit.test.mjs +0 -207
  64. package/scripts/census/census.test.mjs +0 -252
  65. package/scripts/census/state.test.mjs +0 -15
  66. package/scripts/census/transaction.test.mjs +0 -103
  67. package/scripts/census-contract.test.mjs +0 -227
  68. package/scripts/check-kit-staleness.mjs +0 -45
  69. package/scripts/check-kit-staleness.test.mjs +0 -19
  70. package/scripts/codex-exec-scenarios/fake-codex.mjs +0 -165
  71. package/scripts/codex-exec.test.mjs +0 -871
  72. package/scripts/grill-census-wiring-guard.mjs +0 -87
  73. package/scripts/grill-census-wiring-guard.test.mjs +0 -107
  74. package/scripts/kit-release.test.mjs +0 -273
  75. package/scripts/kit-update-pr.test.mjs +0 -151
  76. package/scripts/lib/audit-refs.mjs +0 -146
  77. package/scripts/lib/scrub.mjs +0 -113
  78. package/scripts/lib/scrub.test.mjs +0 -187
  79. package/scripts/memory-lifecycle/memory-lifecycle.test.mjs +0 -170
  80. package/scripts/portability_profile_scan.py +0 -53
  81. package/scripts/release-delta-guard.test.mjs +0 -174
  82. package/scripts/release-parity.test.mjs +0 -90
  83. package/scripts/release-state.test.mjs +0 -360
  84. package/scripts/test_anchor_table.py +0 -69
  85. package/scripts/test_board_bootstrap.py +0 -348
  86. package/scripts/test_board_sync.py +0 -208
  87. package/scripts/test_board_sync_create_idempotency.py +0 -117
  88. package/scripts/test_board_sync_wave_title.py +0 -120
  89. package/scripts/test_census_backstop.py +0 -712
  90. package/scripts/test_census_forward_contract.py +0 -91
  91. package/scripts/test_census_update_contract.test.mjs +0 -454
  92. package/scripts/test_codex_adapter_sync_contract.py +0 -169
  93. package/scripts/test_dist_kit_smoke.py +0 -220
  94. package/scripts/test_drift_guard_diagnostics.py +0 -295
  95. package/scripts/test_issue_claim_contract.py +0 -169
  96. package/scripts/test_kit_docs_language_census.py +0 -178
  97. package/scripts/test_marker_lib.py +0 -162
  98. package/scripts/test_orchestrate_wave_contract.py +0 -381
  99. package/scripts/test_pr_body_check.py +0 -264
  100. package/scripts/test_profile_globs.py +0 -280
  101. package/scripts/test_program_planning_contract.py +0 -133
  102. package/scripts/test_release_authorization_contract.py +0 -101
  103. package/scripts/test_render_anchor.py +0 -267
  104. package/scripts/test_retro_wrapup_contract.py +0 -153
  105. package/scripts/test_skill_code_review_seed.py +0 -89
  106. package/scripts/test_skill_codex_exec_lifecycle.py +0 -123
  107. package/scripts/test_skill_frontmatter_lint.py +0 -234
  108. package/scripts/test_skill_gh_lint.py +0 -124
  109. package/scripts/test_skill_language_census.py +0 -254
  110. package/scripts/test_skill_optional_readiness.py +0 -171
  111. package/scripts/test_skill_portability_lint.py +0 -866
  112. package/scripts/test_skill_precommit_template.py +0 -30
  113. package/scripts/test_skill_publish_audit.py +0 -358
  114. package/scripts/test_skill_readiness_contract.py +0 -264
  115. package/scripts/test_skill_readiness_preflight.py +0 -180
  116. package/scripts/test_skill_required_readiness.py +0 -233
  117. package/scripts/test_skill_selfcontainment_lint.py +0 -255
  118. package/scripts/test_skill_setup_workflow_seeds.py +0 -691
  119. package/scripts/test_skill_stale_name_lint.py +0 -126
  120. package/scripts/test_skill_surface_refs.py +0 -300
  121. package/scripts/test_skill_trailing_artifact_lint.py +0 -71
  122. package/scripts/test_tdd_contract.py +0 -78
  123. package/scripts/test_worktree_ignore_seed.py +0 -320
  124. package/scripts/test_worktree_setup_base_guard.py +0 -140
  125. package/scripts/test_worktree_wrapup_contract.py +0 -1737
  126. package/scripts/test_wrapup_land.py +0 -428
  127. package/scripts/worktree-lifecycle/session.py +0 -1857
@@ -1,227 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { execFile } from 'node:child_process';
4
- import { promisify } from 'node:util';
5
- import { cp, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
6
- import { tmpdir } from 'node:os';
7
- import { join } from 'node:path';
8
- import { fileURLToPath, pathToFileURL } from 'node:url';
9
- import {
10
- CENSUS_BUILDER_VERSION,
11
- activateCensus,
12
- diffCensus,
13
- scanCensus,
14
- } from './census/index.mjs';
15
-
16
- const exec = promisify(execFile);
17
- const FIXTURES = new URL('../test/fixtures/census-consumers/', import.meta.url);
18
- const REPO = fileURLToPath(new URL('../', import.meta.url));
19
-
20
- async function consumer(name, tracked = ['.']) {
21
- const root = await mkdtemp(join(tmpdir(), `awk-census-${name}-`));
22
- await cp(new URL(`${name}/`, FIXTURES), root, { recursive: true });
23
- await exec('git', ['init', '--quiet'], { cwd: root });
24
- await exec('git', ['add', ...tracked], { cwd: root });
25
- return root;
26
- }
27
-
28
- async function cleanup(root) {
29
- await rm(root, { recursive: true, force: true });
30
- }
31
-
32
- async function json(path) {
33
- return JSON.parse(await readFile(path, 'utf8'));
34
- }
35
-
36
- async function backstopStatus(root) {
37
- const { stdout } = await exec('python3', [
38
- join(REPO, '.claude/hooks/drift-guard.py'), '--census-status',
39
- ], { cwd: root });
40
- return JSON.parse(stdout);
41
- }
42
-
43
- test('brownfield reports real X of Y surfaces separately from behaviors', async () => {
44
- const root = await consumer('brownfield');
45
- try {
46
- const profile = await json(join(root, '.census/profile.json'));
47
- const census = await scanCensus({
48
- repoRoot: root,
49
- enabled: profile.enabled,
50
- hasActive: true,
51
- behaviorFamilies: profile.decisions.map(({ family, status }) => ({ name: family, status })),
52
- });
53
- const covered = census.families.surfaces.filter(({ status }) => status === 'abgedeckt').length;
54
-
55
- assert.equal(census.state, 'current');
56
- assert.equal(`${covered} of ${census.families.surfaces.length}`, '3 of 3');
57
- assert.deepEqual(census.families.behaviors, [
58
- { name: 'authentication', status: 'abgedeckt', type: 'behavior' },
59
- { name: 'email-delivery', status: 'nicht relevant', type: 'behavior' },
60
- ]);
61
- assert.ok(census.families.surfaces.every(({ type }) => type === 'surface'));
62
- } finally { await cleanup(root); }
63
- });
64
-
65
- test('greenfield without an active snapshot remains honest bootstrap', async () => {
66
- const root = await consumer('greenfield');
67
- try {
68
- const census = await scanCensus({ repoRoot: root, enabled: true, hasActive: false });
69
- assert.equal(census.state, 'bootstrap');
70
- assert.ok(census.denominator.length > 0, 'bootstrap still reports discovered facts');
71
- } finally { await cleanup(root); }
72
- });
73
-
74
- test('unknown pattern stays open until its repository-local scanner test passes', async () => {
75
- const root = await consumer('unknown-pattern', ['package.json', 'src', '.census']);
76
- try {
77
- const activePath = join(root, '.census/active.json');
78
- const profilePath = join(root, '.census/profile.json');
79
- const surface = 'services/payments/src';
80
- const scannerRecord = {
81
- surface,
82
- module: 'scripts/census-local/scan-services.mjs',
83
- export: 'scanServices',
84
- test: 'test/census-services.test.mjs',
85
- };
86
-
87
- const untracked = await scanCensus({ repoRoot: root, enabled: true, hasActive: true });
88
- assert.equal(untracked.state, 'refresh_required');
89
- assert.deepEqual(diffCensus(untracked, untracked).open, [surface]);
90
-
91
- // Establish real active history before the repository grows the unknown surface.
92
- await rm(join(root, 'services'), { recursive: true });
93
- await rm(join(root, 'scripts'), { recursive: true });
94
- await rm(join(root, 'test'), { recursive: true });
95
- const known = await scanCensus({ repoRoot: root, enabled: true, hasActive: true });
96
- await writeFile(activePath, `${JSON.stringify({
97
- ...known,
98
- profileReport: { decisions: [], localScanners: [], overrides: [] },
99
- })}\n`);
100
-
101
- await cp(new URL('unknown-pattern/services/', FIXTURES), join(root, 'services'), { recursive: true });
102
- await exec('git', ['add', 'services'], { cwd: root });
103
- const trackedOnly = await backstopStatus(root);
104
- assert.equal(trackedOnly.state, 'refresh_required');
105
- assert.ok(trackedOnly.reasons.includes('topology'));
106
-
107
- const profile = await json(profilePath);
108
- profile.localScanners.push(scannerRecord);
109
- await writeFile(profilePath, `${JSON.stringify(profile)}\n`);
110
- const missingProof = await backstopStatus(root);
111
- assert.equal(missingProof.state, 'refresh_required');
112
- assert.ok(missingProof.reasons.includes(`proof:${surface}`));
113
-
114
- await cp(new URL('unknown-pattern/scripts/', FIXTURES), join(root, 'scripts'), { recursive: true });
115
- await cp(new URL('unknown-pattern/test/', FIXTURES), join(root, 'test'), { recursive: true });
116
- await writeFile(
117
- join(root, scannerRecord.test),
118
- "import { test } from 'node:test'; test('failing local proof', () => { throw new Error('no'); });\n",
119
- );
120
- await exec('git', ['add', 'scripts', 'test', '.census/profile.json'], { cwd: root });
121
- const failedProof = await backstopStatus(root);
122
- assert.equal(failedProof.state, 'refresh_required');
123
- assert.ok(failedProof.reasons.includes(`proof:${surface}`));
124
-
125
- await cp(
126
- new URL('unknown-pattern/test/census-services.test.mjs', FIXTURES),
127
- join(root, scannerRecord.test),
128
- );
129
- const candidate = await scanCensus({ repoRoot: root, enabled: true, hasActive: true });
130
- assert.equal(candidate.state, 'current');
131
- await activateCensus({
132
- activePath,
133
- candidate: {
134
- ...candidate,
135
- profileReport: { decisions: [], localScanners: [scannerRecord], overrides: [] },
136
- },
137
- verify: async () => {
138
- await exec('node', ['--test', scannerRecord.test], { cwd: root });
139
- const scanner = await import(`${pathToFileURL(join(root, scannerRecord.module)).href}?proof=${Date.now()}`);
140
- return scanner[scannerRecord.export]().includes(surface);
141
- },
142
- });
143
-
144
- const proven = await backstopStatus(root);
145
- assert.equal(proven.state, 'current');
146
- assert.deepEqual(proven.reasons, []);
147
- const active = await json(activePath);
148
- assert.equal(active.profileReport.localScanners[0].test, scannerRecord.test);
149
- } finally { await cleanup(root); }
150
- });
151
-
152
- test('scan and compact delta do not overwrite a consumer-local modification', async () => {
153
- const root = await consumer('local-modified');
154
- try {
155
- const sourcePath = join(root, 'src/index.mjs');
156
- const activePath = join(root, '.census/active.json');
157
- const sourceBefore = await readFile(sourcePath);
158
- const activeBefore = await readFile(activePath);
159
- const previous = { denominator: [], families: { surfaces: [], behaviors: [] } };
160
- const current = await scanCensus({ repoRoot: root, enabled: true, hasActive: true });
161
- const delta = diffCensus(previous, current);
162
-
163
- assert.deepEqual(Object.keys(delta), ['added', 'changed', 'open', 'removed']);
164
- assert.ok(JSON.stringify(delta).length < JSON.stringify(current).length);
165
- assert.deepEqual(await readFile(sourcePath), sourceBefore);
166
- assert.deepEqual(await readFile(activePath), activeBefore);
167
- } finally { await cleanup(root); }
168
- });
169
-
170
- test('secret canary is absent from scan, delta, and file reads', async () => {
171
- const root = await consumer('secret-canary');
172
- try {
173
- const reads = [];
174
- const scan = await scanCensus({
175
- repoRoot: root,
176
- readText: async (path) => {
177
- reads.push(path);
178
- return readFile(path, 'utf8');
179
- },
180
- });
181
- const output = JSON.stringify({ scan, delta: diffCensus({ denominator: [] }, scan) });
182
- assert.doesNotMatch(output, /CENSUS_SECRET_CANARY/);
183
- assert.ok(reads.every((path) => !path.includes('credentials')));
184
- } finally { await cleanup(root); }
185
- });
186
-
187
- test('interrupted activation preserves the active census byte-for-byte', async () => {
188
- const root = await consumer('interrupted');
189
- try {
190
- const activePath = join(root, '.census/active.json');
191
- const before = await readFile(activePath);
192
- const candidate = await scanCensus({ repoRoot: root, enabled: true, hasActive: true });
193
- await assert.rejects(activateCensus({
194
- activePath,
195
- candidate,
196
- verify: async () => true,
197
- renameCandidate: async () => { throw new Error('fixture interruption'); },
198
- }), (error) => error.state === 'failed');
199
- assert.deepEqual(await readFile(activePath), before);
200
- } finally { await cleanup(root); }
201
- });
202
-
203
- test('consumer update reports a newer builder without silently mutating its census', async () => {
204
- const root = await consumer('consumer-update');
205
- try {
206
- const activePath = join(root, '.census/active.json');
207
- const profilePath = join(root, '.census/profile.json');
208
- const fresh = await scanCensus({ repoRoot: root, enabled: true, hasActive: true });
209
- await writeFile(activePath, `${JSON.stringify({
210
- ...fresh,
211
- fingerprints: { ...fresh.fingerprints, builder: 'older-builder-fingerprint' },
212
- profileReport: { decisions: [], localScanners: [], overrides: [] },
213
- })}\n`);
214
- const activeBefore = await readFile(activePath);
215
- const profileBefore = await readFile(profilePath);
216
- const { stdout } = await exec('python3', [
217
- join(REPO, '.claude/hooks/drift-guard.py'), '--census-status',
218
- ], { cwd: root });
219
- const status = JSON.parse(stdout);
220
-
221
- assert.equal(status.state, 'refresh_required');
222
- assert.equal(status.detail, `builder ${CENSUS_BUILDER_VERSION}`);
223
- assert.ok(status.reasons.includes('builder'));
224
- assert.deepEqual(await readFile(activePath), activeBefore);
225
- assert.deepEqual(await readFile(profilePath), profileBefore);
226
- } finally { await cleanup(root); }
227
- });
@@ -1,45 +0,0 @@
1
- /** Verify that the checked-in install manifest matches a fresh public-SSOT build. */
2
- import { mkdtemp, readFile, rm } from 'node:fs/promises';
3
- import { tmpdir } from 'node:os';
4
- import { dirname, join } from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
- import { buildKit } from './build-kit.mjs';
7
-
8
- const index = (manifest) => new Map(manifest.files.map((file) => [file.path, file.sha256]));
9
-
10
- export function diffManifests(checked, built) {
11
- const before = index(checked);
12
- const after = index(built);
13
- const added = [...after.keys()].filter((path) => !before.has(path)).sort();
14
- const removed = [...before.keys()].filter((path) => !after.has(path)).sort();
15
- const changed = [...after.keys()].filter(
16
- (path) => before.has(path) && before.get(path) !== after.get(path),
17
- ).sort();
18
- return { added, removed, changed };
19
- }
20
-
21
- export async function checkKitStaleness({ repoRoot } = {}) {
22
- repoRoot ??= join(dirname(fileURLToPath(import.meta.url)), '..');
23
- const distDir = await mkdtemp(join(tmpdir(), 'awkit-staleness-'));
24
- try {
25
- await buildKit({ repoRoot, distDir });
26
- const checked = JSON.parse(await readFile(join(repoRoot, 'agent-workflow-kit.package.json'), 'utf8'));
27
- const built = JSON.parse(await readFile(join(distDir, 'agent-workflow-kit.package.json'), 'utf8'));
28
- const diff = diffManifests(checked, built);
29
- const drift = Object.values(diff).some((paths) => paths.length);
30
- return { status: drift ? 'drift' : 'ok', ...diff };
31
- } finally {
32
- await rm(distDir, { recursive: true, force: true });
33
- }
34
- }
35
-
36
- if (import.meta.url === `file://${process.argv[1]}`) {
37
- checkKitStaleness().then((result) => {
38
- if (result.status === 'ok') return console.log('kit:staleness — OK');
39
- console.error('kit:staleness — DRIFT');
40
- for (const key of ['added', 'removed', 'changed']) {
41
- if (result[key].length) console.error(` ${key}: ${result[key].join(', ')}`);
42
- }
43
- process.exitCode = 1;
44
- }).catch((error) => { console.error(error.message); process.exitCode = 1; });
45
- }
@@ -1,19 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtemp, writeFile, rm } from 'node:fs/promises';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import { diffManifests } from './check-kit-staleness.mjs';
7
-
8
- test('manifest parity reports no drift for identical path hashes', () => {
9
- const manifest = { files: [{ path: 'a', sha256: 'one' }] };
10
- assert.deepEqual(diffManifests(manifest, manifest), { added: [], removed: [], changed: [] });
11
- });
12
-
13
- test('manifest parity counts added, removed, and changed files', () => {
14
- const checked = { files: [{ path: 'gone', sha256: '1' }, { path: 'same', sha256: '1' }, { path: 'changed', sha256: '1' }] };
15
- const built = { files: [{ path: 'new', sha256: '1' }, { path: 'same', sha256: '1' }, { path: 'changed', sha256: '2' }] };
16
- assert.deepEqual(diffManifests(checked, built), {
17
- added: ['new'], removed: ['gone'], changed: ['changed'],
18
- });
19
- });
@@ -1,165 +0,0 @@
1
- #!/usr/bin/env node
2
- import { appendFileSync, writeFileSync } from 'node:fs';
3
- import { spawn } from 'node:child_process';
4
-
5
- const args = process.argv.slice(2);
6
- const scenario = process.env.FAKE_CODEX_SCENARIO ?? 'ok';
7
- const pause = Number(process.env.FAKE_CODEX_PAUSE_MS ?? 1_000);
8
-
9
- if (args.includes('--version') || args[0] === '--version') {
10
- console.log(`codex-cli ${process.env.FAKE_CODEX_VERSION ?? '0.144.6'}`);
11
- process.exit(0);
12
- }
13
- if (args[0] === 'login' && args[1] === 'status') {
14
- if (scenario === 'auth-fail') {
15
- console.error('Not logged in');
16
- process.exit(1);
17
- }
18
- console.log('Logged in using fake credentials');
19
- process.exit(0);
20
- }
21
- if (args.includes('--help')) {
22
- const resumeHelp = args[0] === 'exec' && args[1] === 'resume';
23
- if (process.env.FAKE_CODEX_MISSING_CAPABILITY === '1'
24
- || (resumeHelp && process.env.FAKE_CODEX_RESUME_MISSING_CAPABILITY === '1')) {
25
- console.log('Usage: codex exec [PROMPT]');
26
- } else if (resumeHelp) {
27
- console.log('Usage: codex exec resume [--config key=value] [--json] THREAD PROMPT');
28
- } else {
29
- console.log('Usage: codex exec [--json] [--sandbox MODE] resume THREAD');
30
- }
31
- process.exit(0);
32
- }
33
-
34
- if (process.env.FAKE_CODEX_LAUNCH_LOG) {
35
- appendFileSync(process.env.FAKE_CODEX_LAUNCH_LOG, `${JSON.stringify(args)}\n`);
36
- }
37
-
38
- if (args[0] === 'exec' && args[1] === 'resume') {
39
- const configIndex = args.indexOf('-c');
40
- const expected = `sandbox_mode=${process.env.FAKE_EXPECTED_SANDBOX ?? 'read-only'}`;
41
- if (args.includes('--sandbox') || configIndex < 0 || args[configIndex + 1] !== expected) {
42
- console.error('invalid resume sandbox contract');
43
- process.exit(64);
44
- }
45
- }
46
-
47
- const emit = (value) => console.log(JSON.stringify(value));
48
- const thread = process.env.FAKE_CODEX_THREAD ?? 'fake-thread-1';
49
- const started = () => emit({ type: 'thread.started', thread_id: thread });
50
- const verdict = () => emit({
51
- type: 'item.completed',
52
- item: { id: 'fake-item', type: 'agent_message', text: 'fake verdict' },
53
- });
54
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
55
- const waitForSignal = () => new Promise((resolve) => {
56
- const keepAlive = setInterval(() => {}, 2_147_483_647);
57
- const done = () => {
58
- clearInterval(keepAlive);
59
- resolve();
60
- };
61
- process.once('SIGTERM', done);
62
- process.once('SIGINT', done);
63
- process.once('SIGHUP', done);
64
- });
65
-
66
- switch (scenario) {
67
- case 'silent':
68
- await sleep(pause);
69
- break;
70
- case 'startup-byte-silence':
71
- process.stderr.write('x');
72
- await sleep(pause);
73
- break;
74
- case 'quiet-post-thread':
75
- started();
76
- await sleep(pause);
77
- verdict();
78
- break;
79
- case 'malformed':
80
- console.log('{definitely-not-json');
81
- break;
82
- case 'json-null':
83
- emit(null);
84
- break;
85
- case 'json-array':
86
- emit([]);
87
- break;
88
- case 'json-scalar':
89
- emit('scalar');
90
- break;
91
- case 'item-null':
92
- emit({ type: 'item.completed', item: null });
93
- break;
94
- case 'item-array':
95
- emit({ type: 'item.completed', item: [] });
96
- break;
97
- case 'event-type-null':
98
- emit({ type: null });
99
- break;
100
- case 'event-type-array':
101
- emit({ type: [] });
102
- break;
103
- case 'item-type-null':
104
- emit({ type: 'item.completed', item: { type: null } });
105
- break;
106
- case 'item-type-array':
107
- emit({ type: 'item.completed', item: { type: [] } });
108
- break;
109
- case 'thread-non-string':
110
- emit({ type: 'thread.started', thread_id: 42 });
111
- break;
112
- case 'verdict-non-string':
113
- started();
114
- emit({ type: 'item.completed', item: { type: 'agent_message', text: 42 } });
115
- break;
116
- case 'missing-thread':
117
- verdict();
118
- break;
119
- case 'missing-verdict':
120
- started();
121
- emit({ type: 'turn.completed' });
122
- break;
123
- case 'exec-fail':
124
- started();
125
- console.error('fake execution failed token=super-secret');
126
- process.exit(23);
127
- break;
128
- case 'split-secret':
129
- started();
130
- writeFileSync(2, `${'x'.repeat(65_530)}token=super-secret`);
131
- process.exit(24);
132
- break;
133
- case 'signal':
134
- process.kill(process.pid, 'SIGTERM');
135
- break;
136
- case 'group-hang': {
137
- started();
138
- const child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)']);
139
- if (process.env.FAKE_CODEX_CHILD_PID) {
140
- writeFileSync(process.env.FAKE_CODEX_CHILD_PID, String(child.pid));
141
- }
142
- if (process.env.FAKE_CODEX_GROUP_HANG_READY) {
143
- writeFileSync(process.env.FAKE_CODEX_GROUP_HANG_READY, 'ready\n');
144
- }
145
- await waitForSignal();
146
- break;
147
- }
148
- case 'orphan-group': {
149
- started();
150
- const child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], { stdio: 'inherit' });
151
- if (process.env.FAKE_CODEX_CHILD_PID) {
152
- writeFileSync(process.env.FAKE_CODEX_CHILD_PID, String(child.pid));
153
- }
154
- child.unref();
155
- break;
156
- }
157
- case 'pipe-burst':
158
- process.stderr.write('x'.repeat(2 * 1024 * 1024));
159
- started();
160
- verdict();
161
- break;
162
- default:
163
- started();
164
- verdict();
165
- }