@hecer/yoke 1.18.0 → 1.20.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 (66) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +46 -0
  4. package/README.md +86 -910
  5. package/bench/RESULTS.md +30 -0
  6. package/bench/analyze-codex-comparison.mjs +22 -0
  7. package/bench/compare-codex.mjs +71 -0
  8. package/bench/fixtures/independent-utils/.yoke/prd.yaml +30 -0
  9. package/bench/fixtures/independent-utils/bench-verify.mjs +4 -0
  10. package/bench/fixtures/independent-utils/package.json +1 -0
  11. package/bench/fixtures/independent-utils/src/chunk.mjs +1 -0
  12. package/bench/fixtures/independent-utils/src/range.mjs +1 -0
  13. package/bench/fixtures/independent-utils/src/unique.mjs +1 -0
  14. package/bench/fixtures/independent-utils/tests/STORY-1.test.mjs +8 -0
  15. package/bench/fixtures/independent-utils/tests/STORY-2.test.mjs +8 -0
  16. package/bench/fixtures/independent-utils/tests/STORY-3.test.mjs +8 -0
  17. package/bench/probe-goal-integration.mjs +93 -0
  18. package/bench/probe-parallel-gate-context.mjs +29 -0
  19. package/bench/results/codex-comparison-2026-09-29.json +305 -0
  20. package/bench/results/goal-integration-probes-2026-09-29.json +107 -0
  21. package/bench/results/native-goal-validation-2026-09-30.json +122 -0
  22. package/bench/results/parallel-validation-2026-09-30.json +86 -0
  23. package/canon/manifest.yaml +1 -1
  24. package/dist/agents/process-record-identity.js +33 -0
  25. package/dist/agents/process.js +42 -11
  26. package/dist/agents/providers.js +11 -2
  27. package/dist/agents/sol-pi-runtime.js +76 -0
  28. package/dist/check/command.js +218 -0
  29. package/dist/cli.js +11 -7
  30. package/dist/code-intelligence/adapters/graphify.js +1 -1
  31. package/dist/code-intelligence/adapters/mcp.js +25 -4
  32. package/dist/code-intelligence/adapters/serena.js +1 -1
  33. package/dist/code-intelligence/coordinator.js +2 -1
  34. package/dist/code-intelligence/evidence.js +12 -1
  35. package/dist/code-intelligence/mcp-client.js +2 -2
  36. package/dist/dashboard/page.js +1 -1
  37. package/dist/dashboard/panels.js +25 -3
  38. package/dist/dashboard/server.js +88 -7
  39. package/dist/goals/codex-native.js +340 -0
  40. package/dist/goals/command.js +230 -29
  41. package/dist/loop/cleanup.js +10 -1
  42. package/dist/loop/dispatcher.js +82 -10
  43. package/dist/loop/git.js +1 -1
  44. package/dist/loop/parallel-adapters.js +53 -7
  45. package/dist/loop/parallel-command.js +8 -1
  46. package/dist/loop/recovery.js +81 -2
  47. package/dist/loop/reporter.js +3 -0
  48. package/dist/loop/resource-pool.js +21 -15
  49. package/dist/loop/run-command.js +91 -25
  50. package/dist/loop/run-state.js +116 -0
  51. package/dist/prd/explore.js +12 -4
  52. package/dist/retrofit/config.js +29 -0
  53. package/dist/retrofit/gitignore.js +3 -0
  54. package/docs/CODEX-COMPARISON-2026-09-29.md +27 -0
  55. package/docs/CONTINUOUS-EXPLORATION.md +6 -2
  56. package/docs/GOALS-RESOURCE-AUDIT-2026-09-29.md +92 -0
  57. package/docs/GOALS.md +109 -0
  58. package/docs/HARNESSES.md +1 -1
  59. package/docs/RELEASE-VALIDATION-1.20.0.md +100 -0
  60. package/docs/SOL-PI.md +41 -0
  61. package/docs/VERIFIED-PROJECTS.md +4 -4
  62. package/docs/code-intelligence-stdio-fix-2026-09-27.md +8 -0
  63. package/docs/parallel-execution.md +15 -0
  64. package/docs/superpowers/plans/2026-09-30-goals-resources-release.md +78 -0
  65. package/gemini-extension.json +1 -1
  66. package/package.json +5 -1
package/bench/RESULTS.md CHANGED
@@ -1,11 +1,41 @@
1
1
  # Benchmark results
2
2
 
3
+ The [1.20.0 parallel acceptance smoke](results/parallel-validation-2026-09-30.json)
4
+ accepted all three independent stories and passed all 15 immutable replay tests after
5
+ the runtime claim exclusion fix. Its single corrected run took 112,949 ms. This is
6
+ validation of the corrected fixture, not a new A/B efficiency comparison; the dated
7
+ 1.19.0 comparisons below remain historical. See the
8
+ [release validation report](../docs/RELEASE-VALIDATION-1.20.0.md) for limits.
9
+
3
10
  Result schema v1 records fixture version, sample label, permission profile, telemetry/model
4
11
  availability, verdict/blocker, conflicts, wall time, iterations, and final fixture-test status.
5
12
 
6
13
  Fixture `string-kit` (3 stories, 16 pre-written assertions). Methodology and caveats:
7
14
  [README.md](README.md). One row per run — raw JSON in [`results/`](results/).
8
15
 
16
+ ## Direct Codex comparison and integration audit (2026-09-29)
17
+
18
+ Authenticated comparison of Yoke 1.19.0 against direct Codex, requesting the same
19
+ `gpt-6.1-sol / low` model and effort with routing and native delegation disabled.
20
+ Two alternating queue pairs passed the same ten original tests: median elapsed time
21
+ was **118.3 seconds for direct Codex** and **274.9 seconds for Yoke serial**.
22
+ Yoke used **91.9% more fresh input tokens** in this small sample.
23
+
24
+ One corrected independent-task run per arm passed all fifteen original tests for
25
+ direct Codex (71.3 seconds) and Yoke serial (442.2 seconds). Yoke parallel used three
26
+ workers but accepted **0/3 stories**: integration gates lost the worker's
27
+ `YOKE_STORY` context. Its 117.1-second failure is **not** a speedup.
28
+ The deterministic control probe reproduced this integration defect separately
29
+ from provider calls. Initial fixture setup/path failures are retained as diagnostics.
30
+
31
+ These measurements do not establish general superiority, USD cost, CPU/RAM savings,
32
+ or routing benefits. Host skills/plugins were not fully audited and background load
33
+ was uncontrolled. See [the complete comparison](../docs/CODEX-COMPARISON-2026-09-29.md),
34
+ [Goals/resource findings](../docs/GOALS-RESOURCE-AUDIT-2026-09-29.md), and the separately
35
+ formatted [comparison summary](results/codex-comparison-2026-09-29.json) and
36
+ [integration diagnostics](results/goal-integration-probes-2026-09-29.json).
37
+ These two aggregate audit artifacts are not individual result-schema-v1 run records.
38
+
9
39
  ## Synthetic dispatcher matrix (2026-09-27)
10
40
 
11
41
  One local run of `node bench/run-parallel-matrix.mjs` after the shared integration pipeline was
@@ -0,0 +1,22 @@
1
+ // node bench/analyze-codex-comparison.mjs <results.json> ... > summary.json
2
+ import { readFileSync } from 'node:fs'
3
+ const sources = process.argv.slice(2).map(path => JSON.parse(readFileSync(path, 'utf8')))
4
+ if (!sources.length) throw new Error('Supply comparison results.json files')
5
+ const rows = sources.flatMap(s => s.results).map(({ dir, ...row }) => ({
6
+ ...row,
7
+ freshInputTokens: Number.isFinite(row.usage?.inputTokens) && Number.isFinite(row.usage?.cachedInputTokens)
8
+ ? row.usage.inputTokens - row.usage.cachedInputTokens : null,
9
+ }))
10
+ if (!rows.length) throw new Error('No completed runs')
11
+ for (const row of rows) if (!Number.isFinite(row.wallMs) || row.wallMs <= 0 || (row.freshInputTokens !== null && row.freshInputTokens < 0)) throw new Error('Invalid measurement')
12
+ const median = values => { const sorted = [...values].sort((a,b) => a-b); const n = sorted.length; return n ? (sorted[Math.floor((n-1)/2)] + sorted[Math.floor(n/2)]) / 2 : null }
13
+ const groups = [...new Set(rows.map(r => `${r.fixture}:${r.arm}`))].map(key => {
14
+ const runs = rows.filter(r => `${r.fixture}:${r.arm}` === key)
15
+ if (new Set(runs.map(r => `${r.model}:${r.effort}:${r.routing}`)).size !== 1) throw new Error('Incompatible execution policies in one comparison group')
16
+ return { fixture: runs[0].fixture, arm: runs[0].arm, runs: runs.length, accepted: runs.filter(r => r.accepted).length,
17
+ medianWallMs: median(runs.map(r => r.wallMs)),
18
+ medianFreshInputTokens: runs.every(r => r.freshInputTokens !== null) ? median(runs.map(r => r.freshInputTokens)) : null,
19
+ medianOutputTokens: runs.every(r => Number.isFinite(r.usage?.outputTokens)) ? median(runs.map(r => r.usage.outputTokens)) : null,
20
+ medianAllInputTokens: runs.every(r => Number.isFinite(r.usage?.inputTokens)) ? median(runs.map(r => r.usage.inputTokens)) : null }
21
+ })
22
+ console.log(JSON.stringify({ auditedVersion: '1.19.0', auditedCommit: '05fac3db1a51412563005e07f80e6e5a9aab8e06', models: [...new Set(rows.map(r => r.model))], effort: 'low', groups, runs: rows, limitations: [...new Set(sources.flatMap(s => s.limits))], pricesMeasured: false, cpuMeasured: false, memoryMeasured: false }, null, 2))
@@ -0,0 +1,71 @@
1
+ // Controlled live comparison. Run after npm run build.
2
+ // node bench/compare-codex.mjs --repeats=2 --fixture=routing-queue
3
+ import { cpSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs'
4
+ import { createHash } from 'node:crypto'
5
+ import { spawn, spawnSync } from 'node:child_process'
6
+ import { dirname, join, resolve } from 'node:path'
7
+ import { fileURLToPath } from 'node:url'
8
+ import { parse, stringify } from 'yaml'
9
+ import { buildProviderInvocation, startProviderProcess } from '../dist/agents/providers.js'
10
+ const repo = dirname(dirname(fileURLToPath(import.meta.url)))
11
+ const version = JSON.parse(readFileSync(join(repo, 'package.json'), 'utf8')).version
12
+ const opts = Object.fromEntries(process.argv.slice(2).map(x => x.replace(/^--/, '').split('=')))
13
+ const fixture = opts.fixture ?? 'routing-queue'
14
+ if (!['routing-queue', 'string-kit', 'independent-utils'].includes(fixture)) throw new Error('Unsupported fixture')
15
+ const repeats = Number(opts.repeats ?? 2)
16
+ if (!Number.isInteger(repeats) || repeats < 1 || repeats > 10) throw new Error('repeats must be 1..10')
17
+ const model = opts.model ?? 'gpt-6.1-sol'
18
+ const root = resolve(opts.root ?? `G:/NN-Developed/Yoke-Testground/b${Date.now().toString(36)}`)
19
+ mkdirSync(root, { recursive: true })
20
+ const seed = join(repo, 'bench', 'fixtures', fixture)
21
+ const stories = parse(readFileSync(join(seed, '.yoke/prd.yaml'), 'utf8'))
22
+ const originalTests = readdirSync(join(seed, 'tests')).filter(f => f.endsWith('.test.mjs')).sort()
23
+ const protectedFiles = new Map(['bench-verify.mjs', ...originalTests.map(f => join('tests', f))].map(file => [file, readFileSync(join(seed, file))]))
24
+ const acceptanceDigest = createHash('sha256').update(Buffer.concat([...protectedFiles].flatMap(([file, bytes]) => [Buffer.from(file + '\0'), bytes]))).digest('hex')
25
+ const prompt = 'Implement all requirements below. Run node bench-verify.mjs. Preserve tests and the verification script. Do not commit.\n' + stories.map(s => `${s.id}: ${s.title}\n${s.acceptance.join('\n')}`).join('\n\n')
26
+ const results = []
27
+ const requestedArms = opts.arms?.split(',')
28
+ if (requestedArms && (!requestedArms.length || requestedArms.some(arm => !['codex', 'yoke-serial', 'yoke-parallel'].includes(arm)))) throw new Error('arms must name codex,yoke-serial,yoke-parallel')
29
+ for (let repeat = 0; repeat < repeats; repeat++) {
30
+ const baseArms = fixture === 'independent-utils' ? ['codex', 'yoke-serial', 'yoke-parallel'] : ['codex', 'yoke-serial']
31
+ const selectedArms = requestedArms ? baseArms.filter(arm => requestedArms.includes(arm)) : baseArms
32
+ if (!selectedArms.length) throw new Error('No requested arms apply to this fixture')
33
+ const arms = repeat % 2 ? [...selectedArms].reverse() : selectedArms
34
+ for (const arm of arms) {
35
+ const dir = join(root, `${repeat + 1}-${arm}`)
36
+ mkdirSync(dir); cpSync(seed, dir, { recursive: true })
37
+ const parallel = arm === 'yoke-parallel' ? 3 : 1
38
+ writeFileSync(join(dir, '.yoke/config.yaml'), stringify({ canonVersion: '1.2.0', agents: ['codex'], loop: { enabled: true, parallel, isolate: true }, runner: { agent: 'codex', model, reasoningEffort: 'low', bare: true }, routing: { enabled: false }, verify: { command: 'node bench-verify.mjs' } }))
39
+ for (const args of [['init', '-q'], ['add', '-A'], ['-c', 'user.name=benchmark', '-c', 'user.email=benchmark@yoke.local', 'commit', '-qm', 'Immutable fixture seed']]) {
40
+ const r = spawnSync('git', args, { cwd: dir, encoding: 'utf8' }); if (r.status !== 0) throw new Error(r.stderr)
41
+ }
42
+ console.log(JSON.stringify({ type: 'start', fixture, repeat: repeat + 1, arm, model, dir }))
43
+ const start = Date.now(); let outcome; let events = []; let usage
44
+ if (arm === 'codex') {
45
+ const inv = buildProviderInvocation('codex', prompt, dir, 'unsafe', { model, reasoningEffort: 'low', bare: true, nativeMultiAgent: false })
46
+ inv.args.push('--ignore-rules')
47
+ const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 600_000)
48
+ try {
49
+ const r = await startProviderProcess('codex', inv, { signal: controller.signal, idleTimeoutMs: 180_000 }).completion
50
+ outcome = r.kind; usage = r.telemetry.tokens
51
+ writeFileSync(join(root, `${fixture}-${repeat + 1}-${arm}.log`), JSON.stringify(r, null, 2))
52
+ } finally { clearTimeout(timer) }
53
+ } else {
54
+ const child = spawn(process.execPath, [join(repo, 'dist/cli.js'), 'loop', 'run', dir, '--json', '--runner=codex', '--no-routing', `--parallel=${parallel}`, '--max=3', '--timeout=10', '--unsafe'], { cwd: dir, env: { ...process.env, LOCALAPPDATA: join(root, 'state') }, stdio: ['ignore', 'pipe', 'pipe'] })
55
+ let output = '', errors = ''; child.stdout.on('data', d => { output += d }); child.stderr.on('data', d => { errors += d })
56
+ outcome = await new Promise((res, rej) => { child.on('error', rej); child.on('close', res) })
57
+ events = output.split('\n').flatMap(line => { try { return [JSON.parse(line)] } catch { return [] } })
58
+ writeFileSync(join(root, `${fixture}-${repeat + 1}-${arm}.log`), output + '\nSTDERR\n' + errors)
59
+ usage = events.filter(e => e.type === 'status' && e.tokens).at(-1)?.tokens
60
+ }
61
+ const wallMs = Date.now() - start
62
+ // Replay immutable originals after the agent exits. Agent-written tests never determine quality.
63
+ mkdirSync(join(dir, 'tests'), { recursive: true })
64
+ for (const [file, bytes] of protectedFiles) writeFileSync(join(dir, file), bytes)
65
+ const testFiles = originalTests.map(f => join('tests', f))
66
+ const checkStart = Date.now(); const check = spawnSync(process.execPath, ['--test', ...testFiles], { cwd: dir, encoding: 'utf8', timeout: 60_000 })
67
+ writeFileSync(join(root, `${fixture}-${repeat + 1}-${arm}.acceptance.log`), check.stdout + check.stderr)
68
+ const result = { fixture, repeat: repeat + 1, arm, model, effort: 'low', routing: false, nativeMultiAgent: false, parallel, acceptanceDigest, wallMs, acceptanceMs: Date.now() - checkStart, accepted: check.status === 0, outcome, usage: usage ?? null, events: events.length, dir }
69
+ results.push(result); writeFileSync(join(root, 'results.json'), JSON.stringify({ version, root, results, limits: ['Small fixture sample; not representative of production projects', 'CPU and RAM not measured', 'Routing disabled; fixed model and effort', 'Immutable tests replayed after execution; not hidden from agents', 'User config disabled; not an audit of every installed plugin'] }, null, 2)); console.log(JSON.stringify({ type: 'result', ...result }))
70
+ }
71
+ }
@@ -0,0 +1,30 @@
1
+ - id: STORY-1
2
+ title: Stable unique values
3
+ priority: 1
4
+ writes: [src/unique.mjs]
5
+ needs: []
6
+ acceptance:
7
+ - Export unique(values) from src/unique.mjs, returning first occurrences in input order using SameValueZero equality.
8
+ - Never mutate the input. Non-array input throws TypeError. Preserve object identity.
9
+ - node bench-verify.mjs exits 0
10
+ passes: false
11
+ - id: STORY-2
12
+ title: Array chunking
13
+ priority: 2
14
+ writes: [src/chunk.mjs]
15
+ needs: []
16
+ acceptance:
17
+ - Export chunk(values,size) from src/chunk.mjs. Split arrays into consecutive new arrays of at most size elements.
18
+ - An empty input returns an empty array. Never mutate the input. Non-array input throws TypeError; size must be a positive integer or throw RangeError.
19
+ - node bench-verify.mjs exits 0
20
+ passes: false
21
+ - id: STORY-3
22
+ title: Numeric range
23
+ priority: 3
24
+ writes: [src/range.mjs]
25
+ needs: []
26
+ acceptance:
27
+ - Export range(start,end,step=1) from src/range.mjs. Generate start-inclusive, end-exclusive numbers with a positive or negative step.
28
+ - A step pointing away from end returns an empty array. Zero step or any non-finite argument throws RangeError. Equal endpoints return an empty array.
29
+ - node bench-verify.mjs exits 0
30
+ passes: false
@@ -0,0 +1,4 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ const ids = process.env.YOKE_STORY ? [process.env.YOKE_STORY] : ['STORY-1', 'STORY-2', 'STORY-3']
3
+ const r = spawnSync(process.execPath, ['--test', ...ids.map(id => `tests/${id}.test.mjs`)], { stdio: 'inherit' })
4
+ process.exit(r.status ?? 1)
@@ -0,0 +1 @@
1
+ {"name":"yoke-independent-utils-benchmark","private":true,"type":"module","scripts":{"test":"node bench-verify.mjs"}}
@@ -0,0 +1 @@
1
+ export function chunk() { throw new Error('Not implemented') }
@@ -0,0 +1 @@
1
+ export function range() { throw new Error('Not implemented') }
@@ -0,0 +1 @@
1
+ export function unique() { throw new Error('Not implemented') }
@@ -0,0 +1,8 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { unique } from '../src/unique.mjs'
4
+ test('deduplicates in order', () => assert.deepEqual(unique([2,1,2,3,1]), [2,1,3]))
5
+ test('empty', () => assert.deepEqual(unique([]), []))
6
+ test('SameValueZero', () => assert.deepEqual(unique([NaN,NaN,0,-0]), [NaN,0]))
7
+ test('identity and immutable input', () => { const a = {}; const b = {}; const input = Object.freeze([a,b,a]); assert.deepEqual(unique(input), [a,b]) })
8
+ test('invalid input', () => assert.throws(() => unique('abc'), TypeError))
@@ -0,0 +1,8 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { chunk } from '../src/chunk.mjs'
4
+ test('consecutive chunks', () => assert.deepEqual(chunk([1,2,3,4,5],2), [[1,2],[3,4],[5]]))
5
+ test('empty', () => assert.deepEqual(chunk([],2), []))
6
+ test('large size', () => assert.deepEqual(chunk([1,2],9), [[1,2]]))
7
+ test('immutable input and new arrays', () => { const input = Object.freeze([1,2]); assert.notEqual(chunk(input,2)[0], input) })
8
+ test('invalid input and size', () => { assert.throws(() => chunk('abc',2), TypeError); for (const size of [0,-1,1.5,NaN,Infinity]) assert.throws(() => chunk([],size), RangeError) })
@@ -0,0 +1,8 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { range } from '../src/range.mjs'
4
+ test('ascending', () => assert.deepEqual(range(1,5), [1,2,3,4]))
5
+ test('descending', () => assert.deepEqual(range(5,0,-2), [5,3,1]))
6
+ test('fractional', () => assert.deepEqual(range(0,1,0.25), [0,0.25,0.5,0.75]))
7
+ test('empty', () => { assert.deepEqual(range(1,1), []); assert.deepEqual(range(1,5,-1), []); assert.deepEqual(range(5,1,1), []) })
8
+ test('invalid numbers', () => { for (const args of [[0,1,0],[NaN,1],[0,Infinity],[0,1,Infinity]]) assert.throws(() => range(...args), RangeError) })
@@ -0,0 +1,93 @@
1
+ // Read-only protocol capability probe plus disposable local goal resource test.
2
+ import { spawn } from 'node:child_process'
3
+ import { join, resolve } from 'node:path'
4
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
5
+ import { createProjectGoal, runProjectGoal } from '../dist/goals/command.js'
6
+ import { acquireSharedWorker, sharedPoolStatus } from '../dist/loop/resource-pool.js'
7
+ const root = resolve(process.argv[2] ?? `G:/NN-Developed/Yoke-Testground/goal-probe-${Date.now()}`)
8
+ mkdirSync(join(root, '.yoke'), { recursive: true })
9
+ process.env.LOCALAPPDATA = join(root, 'isolated-state')
10
+ process.env.YOKE_MAX_PARALLEL_WORKERS = '1'
11
+ process.env.YOKE_STATE_DIR = join(root, 'yoke-state')
12
+ writeFileSync(join(root, '.yoke/acceptance.yaml'), 'version: 1\nprotected: [test.mjs]\ncriteria:\n- id: outcome\n text: Expected outcome\n commands: [node test.mjs]\n')
13
+ writeFileSync(join(root, 'test.mjs'), 'import {existsSync} from "node:fs"; process.exit(existsSync("implemented.txt") ? 0 : 1)')
14
+ createProjectGoal(root, 'Disposable test goal', { acceptanceIds: ['outcome'] })
15
+ const lease = await acquireSharedWorker({ targetDir: root, storyId: 'occupied', provider: 'codex', role: 'implementation' })
16
+ let executedWhilePoolFull = false, occupiedPermitHeld = true
17
+ let goal, queuedWhilePoolFull = false
18
+ try {
19
+ const running = runProjectGoal(root, { execute: async () => {
20
+ const pool = sharedPoolStatus(); executedWhilePoolFull = occupiedPermitHeld && pool.activeUnits === pool.limit
21
+ writeFileSync(join(root, 'implemented.txt'), 'done')
22
+ return { success: true, summary: 'Fake executor for mechanical pool test', inputTokens: 1, outputTokens: 1 }
23
+ } })
24
+ // Admission is asynchronous. Release the occupied provider permit only after
25
+ // the goal has completed its independent check and queued for implementation.
26
+ const deadline = Date.now() + 15_000
27
+ while (Date.now() < deadline) {
28
+ if (sharedPoolStatus().waitingWorkers > 0) { queuedWhilePoolFull = true; break }
29
+ await new Promise(resolve => setTimeout(resolve, 100))
30
+ }
31
+ occupiedPermitHeld = false
32
+ await lease.release()
33
+ goal = await running
34
+ } finally { await lease.release() }
35
+ const findings = { poolProbe: { fakeExecutor: true, limit: 1, queuedWhilePoolFull, executedWhilePoolFull, goalStatus: goal.status }, protocol: [] }
36
+ // A successful implementation that overshoots budget must remain blocked.
37
+ const budgetRoot = join(root, 'budget-probe'); mkdirSync(join(budgetRoot, '.yoke'), { recursive: true })
38
+ writeFileSync(join(budgetRoot, '.yoke/acceptance.yaml'), 'version: 1\nprotected: [test.mjs]\ncriteria:\n- id: outcome\n text: Expected outcome\n commands: [node test.mjs]\n')
39
+ writeFileSync(join(budgetRoot, 'test.mjs'), 'import {existsSync} from "node:fs"; await new Promise(r=>setTimeout(r,120)); process.exit(existsSync("implemented.txt") ? 0 : 1)')
40
+ createProjectGoal(budgetRoot, 'Disposable budget test', { acceptanceIds: ['outcome'], tokenBudget: 1, maxMinutes: 0.001 })
41
+ const budgetStart = Date.now()
42
+ const budgetGoal = await runProjectGoal(budgetRoot, { execute: async () => {
43
+ writeFileSync(join(budgetRoot, 'implemented.txt'), 'done')
44
+ return { success: true, summary: 'Fake executor reporting 100 tokens', inputTokens: 90, outputTokens: 10 }
45
+ } })
46
+ findings.budgetProbe = { fakeExecutor: true, tokenBudget: 1, measuredTokens: 100, timeBudgetMs: 60, totalWallMs: Date.now() - budgetStart, recordedAgentMs: budgetGoal.attempts[0]?.durationMs, status: budgetGoal.status }
47
+ const objectiveRoot = join(root, 'objective-probe'); mkdirSync(join(objectiveRoot, '.yoke'), { recursive: true })
48
+ writeFileSync(join(objectiveRoot, '.yoke/acceptance.yaml'), 'version: 1\nprotected: [test.mjs]\ncriteria:\n- id: old-feature\n text: Existing feature still works\n commands: [node test.mjs]\n')
49
+ writeFileSync(join(objectiveRoot, 'test.mjs'), 'import {existsSync} from "node:fs"; process.exit(existsSync("old-feature.txt") ? 0 : 1)')
50
+ writeFileSync(join(objectiveRoot, 'old-feature.txt'), 'already implemented')
51
+ createProjectGoal(objectiveRoot, 'Implement the new feature by creating new-feature.txt')
52
+ let objectiveExecutorCalled = false
53
+ const objectiveGoal = await runProjectGoal(objectiveRoot, { execute: async () => { objectiveExecutorCalled = true; return { success: false, summary: 'Should need implementation' } } })
54
+ findings.objectiveProbe = { requestedArtifact: 'new-feature.txt', requestedArtifactExists: existsSync(join(objectiveRoot, 'new-feature.txt')), acceptance: 'only checks old-feature.txt; intentionally unbound objective', executorCalled: objectiveExecutorCalled, attempts: objectiveGoal.attempts.length, status: objectiveGoal.status }
55
+ const cli = join(process.env.APPDATA, 'npm/node_modules/@openai/codex/bin/codex.js')
56
+ const child = spawn(process.execPath, [cli, 'app-server', '--stdio', '--enable', 'goals'], { cwd: root, stdio: ['pipe', 'pipe', 'pipe'] })
57
+ let next = 1, buffer = '', errorText = ''; const pending = new Map()
58
+ child.stderr.on('data', d => { errorText += d })
59
+ child.stdout.on('data', data => {
60
+ buffer += data
61
+ while (buffer.includes('\n')) {
62
+ const i = buffer.indexOf('\n'), line = buffer.slice(0, i); buffer = buffer.slice(i + 1)
63
+ try { const message = JSON.parse(line); if (pending.has(message.id)) { const p = pending.get(message.id); pending.delete(message.id); message.error ? p.reject(new Error(JSON.stringify(message.error))) : p.resolve(message.result) } } catch {}
64
+ }
65
+ })
66
+ function rpc(method, params) {
67
+ return new Promise((resolve, reject) => {
68
+ const id = next++; const timer = setTimeout(() => { pending.delete(id); reject(new Error(`Timeout: ${method}`)) }, 30_000)
69
+ pending.set(id, { resolve: result => { clearTimeout(timer); resolve(result) }, reject: error => { clearTimeout(timer); reject(error) } })
70
+ child.stdin.write(JSON.stringify({ id, method, params }) + '\n')
71
+ })
72
+ }
73
+ try {
74
+ await rpc('initialize', { clientInfo: { name: 'yoke-goals-probe', version: '1.0.0' }, capabilities: { experimentalApi: true } })
75
+ child.stdin.write(JSON.stringify({ method: 'initialized' }) + '\n')
76
+ const start = await rpc('thread/start', { cwd: root, model: 'gpt-6.1-sol', approvalPolicy: 'never', sandbox: 'read-only' })
77
+ const threadId = start.thread.id
78
+ for (const [method, params] of [
79
+ ['thread/goal/set', { threadId, objective: 'Protocol-only goal: no development turn requested', tokenBudget: 1000 }],
80
+ ['thread/goal/get', { threadId }],
81
+ ['thread/goal/set', { threadId, status: 'paused' }],
82
+ ['thread/goal/get', { threadId }],
83
+ ['thread/goal/clear', { threadId }],
84
+ ['thread/goal/get', { threadId }],
85
+ ]) {
86
+ const result = await rpc(method, params); findings.protocol.push({ method, requestedStatus: params.status ?? null, result })
87
+ }
88
+ await rpc('thread/archive', { threadId })
89
+ } catch (error) { findings.protocolError = error.message; findings.stderrTail = errorText.slice(-2000) }
90
+ finally { child.stdin.end(); child.kill() }
91
+ writeFileSync(join(root, 'findings.json'), JSON.stringify(findings, null, 2))
92
+ console.log(JSON.stringify(findings, null, 2))
93
+ if (findings.protocolError) process.exitCode = 1
@@ -0,0 +1,29 @@
1
+ // Diagnostic using fake agents/Git adapters, exercising the real loop and dispatcher.
2
+ import { cpSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
3
+ import { join, resolve } from 'node:path'
4
+ import { runLoopCommand } from '../dist/loop/run-command.js'
5
+ import { saveConfig } from '../dist/retrofit/config.js'
6
+ import { loadPrd, savePrd } from '../dist/loop/prd.js'
7
+ const base = resolve(process.argv[2] ?? `G:/NN-Developed/Yoke-Testground/p${Date.now().toString(36)}`)
8
+ process.env.LOCALAPPDATA = join(base, 'state')
9
+ const findings = []
10
+ for (const requiresStoryContext of [false, true]) {
11
+ const root = join(base, requiresStoryContext ? 'scoped' : 'control'); mkdirSync(join(root, '.yoke'), { recursive: true })
12
+ saveConfig(root, { canonVersion: 'test', agents: ['codex'], loop: { enabled: true }, verify: { command: 'node test.mjs' } })
13
+ savePrd(join(root, '.yoke/prd.yaml'), ['A','B','C'].map((id, index) => ({ id, title: id, priority: index + 1, acceptance: ['Expected result'], writes: [`src/${id}`], passes: false })))
14
+ const observedContexts = []
15
+ const code = await runLoopCommand(root, { parallel: 3, maxIterations: 3, quiet: true,
16
+ runner: () => ({ success: true, summary: 'Fake agent completed' }), isAvailable: () => true,
17
+ verify: () => { const context = process.env.YOKE_STORY ?? null; observedContexts.push(context); return { passed: !requiresStoryContext || ['A','B','C'].includes(context), summary: context ? 'Story-scoped verification passed' : 'Missing YOKE_STORY context' } },
18
+ git: { isClean: () => true, commitAll: () => undefined,
19
+ addWorktree: (_target, worktree) => { mkdirSync(join(worktree, '.yoke'), { recursive: true }); cpSync(join(root, '.yoke/prd.yaml'), join(worktree, '.yoke/prd.yaml')) },
20
+ // Retain disposable directories for inspection; no recursive deletion.
21
+ removeWorktree: () => undefined,
22
+ integrate: (_target, worktree) => savePrd(join(root, '.yoke/prd.yaml'), loadPrd(join(worktree, '.yoke/prd.yaml'))),
23
+ },
24
+ })
25
+ findings.push({ fakeAgentsAndGit: true, requiresStoryContext, exitCode: code, observedContexts, acceptedStories: loadPrd(join(root, '.yoke/prd.yaml')).filter(s => s.passes).length })
26
+ }
27
+ writeFileSync(join(base, 'findings.json'), JSON.stringify(findings, null, 2))
28
+ console.log(JSON.stringify(findings, null, 2))
29
+ if (findings.some(row => row.exitCode !== 0 || row.acceptedStories !== 3 || row.observedContexts.includes(null))) process.exitCode = 1
@@ -0,0 +1,305 @@
1
+ {
2
+ "auditedVersion": "1.19.0",
3
+ "auditedCommit": "05fac3db1a51412563005e07f80e6e5a9aab8e06",
4
+ "models": [
5
+ "gpt-6.1-sol"
6
+ ],
7
+ "effort": "low",
8
+ "groups": [
9
+ {
10
+ "fixture": "routing-queue",
11
+ "arm": "codex",
12
+ "runs": 2,
13
+ "accepted": 2,
14
+ "medianWallMs": 118278.5,
15
+ "medianFreshInputTokens": 25292,
16
+ "medianOutputTokens": 2162,
17
+ "medianAllInputTokens": 137548
18
+ },
19
+ {
20
+ "fixture": "routing-queue",
21
+ "arm": "yoke-serial",
22
+ "runs": 2,
23
+ "accepted": 2,
24
+ "medianWallMs": 274943.5,
25
+ "medianFreshInputTokens": 48524,
26
+ "medianOutputTokens": 4822,
27
+ "medianAllInputTokens": 396300
28
+ },
29
+ {
30
+ "fixture": "independent-utils",
31
+ "arm": "codex",
32
+ "runs": 1,
33
+ "accepted": 1,
34
+ "medianWallMs": 71283,
35
+ "medianFreshInputTokens": 10263,
36
+ "medianOutputTokens": 797,
37
+ "medianAllInputTokens": 84503
38
+ },
39
+ {
40
+ "fixture": "independent-utils",
41
+ "arm": "yoke-serial",
42
+ "runs": 1,
43
+ "accepted": 1,
44
+ "medianWallMs": 442189,
45
+ "medianFreshInputTokens": 92330,
46
+ "medianOutputTokens": 3905,
47
+ "medianAllInputTokens": 626986
48
+ },
49
+ {
50
+ "fixture": "independent-utils",
51
+ "arm": "yoke-parallel",
52
+ "runs": 1,
53
+ "accepted": 0,
54
+ "medianWallMs": 117101,
55
+ "medianFreshInputTokens": 81497,
56
+ "medianOutputTokens": 3487,
57
+ "medianAllInputTokens": 481753
58
+ }
59
+ ],
60
+ "runs": [
61
+ {
62
+ "fixture": "routing-queue",
63
+ "repeat": 1,
64
+ "arm": "codex",
65
+ "model": "gpt-6.1-sol",
66
+ "effort": "low",
67
+ "routing": false,
68
+ "nativeMultiAgent": false,
69
+ "wallMs": 112482,
70
+ "acceptanceMs": 175,
71
+ "accepted": true,
72
+ "outcome": "succeeded",
73
+ "usage": {
74
+ "inputTokens": 158620,
75
+ "cachedInputTokens": 121984,
76
+ "cacheWriteInputTokens": 0,
77
+ "outputTokens": 2190,
78
+ "reasoningOutputTokens": 18
79
+ },
80
+ "events": 0,
81
+ "freshInputTokens": 36636
82
+ },
83
+ {
84
+ "fixture": "routing-queue",
85
+ "repeat": 1,
86
+ "arm": "yoke-serial",
87
+ "model": "gpt-6.1-sol",
88
+ "effort": "low",
89
+ "routing": false,
90
+ "nativeMultiAgent": false,
91
+ "wallMs": 273000,
92
+ "acceptanceMs": 173,
93
+ "accepted": true,
94
+ "outcome": 0,
95
+ "usage": {
96
+ "inputTokens": 383543,
97
+ "cachedInputTokens": 334720,
98
+ "cacheWriteInputTokens": 0,
99
+ "outputTokens": 4924,
100
+ "reasoningOutputTokens": 104
101
+ },
102
+ "events": 11,
103
+ "freshInputTokens": 48823
104
+ },
105
+ {
106
+ "fixture": "routing-queue",
107
+ "repeat": 2,
108
+ "arm": "yoke-serial",
109
+ "model": "gpt-6.1-sol",
110
+ "effort": "low",
111
+ "routing": false,
112
+ "nativeMultiAgent": false,
113
+ "wallMs": 276887,
114
+ "acceptanceMs": 169,
115
+ "accepted": true,
116
+ "outcome": 0,
117
+ "usage": {
118
+ "inputTokens": 409057,
119
+ "cachedInputTokens": 360832,
120
+ "cacheWriteInputTokens": 0,
121
+ "outputTokens": 4720,
122
+ "reasoningOutputTokens": 81
123
+ },
124
+ "events": 11,
125
+ "freshInputTokens": 48225
126
+ },
127
+ {
128
+ "fixture": "routing-queue",
129
+ "repeat": 2,
130
+ "arm": "codex",
131
+ "model": "gpt-6.1-sol",
132
+ "effort": "low",
133
+ "routing": false,
134
+ "nativeMultiAgent": false,
135
+ "wallMs": 124075,
136
+ "acceptanceMs": 409,
137
+ "accepted": true,
138
+ "outcome": "succeeded",
139
+ "usage": {
140
+ "inputTokens": 116476,
141
+ "cachedInputTokens": 102528,
142
+ "cacheWriteInputTokens": 0,
143
+ "outputTokens": 2134,
144
+ "reasoningOutputTokens": 28
145
+ },
146
+ "events": 0,
147
+ "freshInputTokens": 13948
148
+ },
149
+ {
150
+ "fixture": "independent-utils",
151
+ "repeat": 1,
152
+ "arm": "codex",
153
+ "model": "gpt-6.1-sol",
154
+ "effort": "low",
155
+ "routing": false,
156
+ "nativeMultiAgent": false,
157
+ "parallel": 1,
158
+ "acceptanceDigest": "3e9734e6d286b7c9d19e6ce473eea17e1adf9e30a6ee8927c7240170602f14f7",
159
+ "wallMs": 71283,
160
+ "acceptanceMs": 354,
161
+ "accepted": true,
162
+ "outcome": "succeeded",
163
+ "usage": {
164
+ "inputTokens": 84503,
165
+ "cachedInputTokens": 74240,
166
+ "cacheWriteInputTokens": 0,
167
+ "outputTokens": 797,
168
+ "reasoningOutputTokens": 7
169
+ },
170
+ "events": 0,
171
+ "freshInputTokens": 10263
172
+ },
173
+ {
174
+ "fixture": "independent-utils",
175
+ "repeat": 1,
176
+ "arm": "yoke-serial",
177
+ "model": "gpt-6.1-sol",
178
+ "effort": "low",
179
+ "routing": false,
180
+ "nativeMultiAgent": false,
181
+ "parallel": 1,
182
+ "acceptanceDigest": "3e9734e6d286b7c9d19e6ce473eea17e1adf9e30a6ee8927c7240170602f14f7",
183
+ "wallMs": 442189,
184
+ "acceptanceMs": 454,
185
+ "accepted": true,
186
+ "outcome": 0,
187
+ "usage": {
188
+ "inputTokens": 626986,
189
+ "cachedInputTokens": 534656,
190
+ "cacheWriteInputTokens": 0,
191
+ "outputTokens": 3905,
192
+ "reasoningOutputTokens": 60
193
+ },
194
+ "events": 16,
195
+ "freshInputTokens": 92330
196
+ },
197
+ {
198
+ "fixture": "independent-utils",
199
+ "repeat": 1,
200
+ "arm": "yoke-parallel",
201
+ "model": "gpt-6.1-sol",
202
+ "effort": "low",
203
+ "routing": false,
204
+ "nativeMultiAgent": false,
205
+ "parallel": 3,
206
+ "acceptanceDigest": "3e9734e6d286b7c9d19e6ce473eea17e1adf9e30a6ee8927c7240170602f14f7",
207
+ "wallMs": 117101,
208
+ "acceptanceMs": 306,
209
+ "accepted": false,
210
+ "outcome": 1,
211
+ "usage": {
212
+ "inputTokens": 481753,
213
+ "cachedInputTokens": 400256,
214
+ "cacheWriteInputTokens": 0,
215
+ "outputTokens": 3487,
216
+ "reasoningOutputTokens": 72
217
+ },
218
+ "events": 22,
219
+ "freshInputTokens": 81497
220
+ }
221
+ ],
222
+ "limitations": [
223
+ "Small fixture sample; not representative of production projects",
224
+ "CPU and RAM not measured",
225
+ "Immutable tests replayed after execution; not hidden from agents",
226
+ "User config disabled; not an audit of every installed plugin",
227
+ "Routing disabled; fixed model and effort",
228
+ "Queue fixture: two alternating pairs; independent fixture: one sample per arm",
229
+ "Single Windows machine; uncontrolled background load",
230
+ "Failed parallel run is not an accepted speedup",
231
+ "No causal A/B test isolating context startup, worktree or verification overhead"
232
+ ],
233
+ "pricesMeasured": false,
234
+ "cpuMeasured": false,
235
+ "memoryMeasured": false,
236
+ "date": "2026-09-29",
237
+ "setupDiagnostics": [
238
+ {
239
+ "fixture": "independent-utils",
240
+ "repeat": 1,
241
+ "arm": "codex",
242
+ "model": "gpt-6.1-sol",
243
+ "effort": "low",
244
+ "routing": false,
245
+ "nativeMultiAgent": false,
246
+ "parallel": 1,
247
+ "wallMs": 75037,
248
+ "acceptanceMs": 352,
249
+ "accepted": true,
250
+ "outcome": "succeeded",
251
+ "usage": {
252
+ "inputTokens": 86851,
253
+ "cachedInputTokens": 75520,
254
+ "cacheWriteInputTokens": 0,
255
+ "outputTokens": 1013,
256
+ "reasoningOutputTokens": 37
257
+ },
258
+ "events": 0,
259
+ "diagnosticOnly": true,
260
+ "exclusionReason": "Initial fixture lacked runtime ignore rules; not grouped with corrected fixture"
261
+ },
262
+ {
263
+ "fixture": "independent-utils",
264
+ "repeat": 1,
265
+ "arm": "yoke-serial",
266
+ "model": "gpt-6.1-sol",
267
+ "effort": "low",
268
+ "routing": false,
269
+ "nativeMultiAgent": false,
270
+ "parallel": 1,
271
+ "wallMs": 116055,
272
+ "acceptanceMs": 627,
273
+ "accepted": false,
274
+ "outcome": 1,
275
+ "usage": {
276
+ "inputTokens": 176111,
277
+ "cachedInputTokens": 147072,
278
+ "cacheWriteInputTokens": 0,
279
+ "outputTokens": 1317,
280
+ "reasoningOutputTokens": 26
281
+ },
282
+ "events": 6,
283
+ "diagnosticOnly": true,
284
+ "exclusionReason": "Initial fixture lacked runtime ignore rules; not grouped with corrected fixture"
285
+ },
286
+ {
287
+ "fixture": "independent-utils",
288
+ "repeat": 1,
289
+ "arm": "yoke-parallel",
290
+ "model": "gpt-6.1-sol",
291
+ "effort": "low",
292
+ "routing": false,
293
+ "nativeMultiAgent": false,
294
+ "parallel": 3,
295
+ "wallMs": 3555,
296
+ "acceptanceMs": 678,
297
+ "accepted": false,
298
+ "outcome": 1,
299
+ "usage": null,
300
+ "events": 0,
301
+ "diagnosticOnly": true,
302
+ "exclusionReason": "Git worktree path exceeded Windows limit before provider startup"
303
+ }
304
+ ]
305
+ }