devmethod-ai 0.1.0-rc.2 → 0.2.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 (228) hide show
  1. package/.agents/skills/decision-architecture/SKILL.md +15 -13
  2. package/.agents/skills/decision-architecture/assets/ADR.md +18 -17
  3. package/.agents/skills/decision-architecture/references/api-contracts.md +21 -16
  4. package/.agents/skills/decision-architecture/references/backend-boundaries.md +21 -18
  5. package/.agents/skills/decision-architecture/references/product-decisions.md +14 -17
  6. package/.agents/skills/design-to-code/SKILL.md +18 -11
  7. package/.agents/skills/design-to-code/assets/UI_ACCEPTANCE.md +13 -12
  8. package/.agents/skills/design-to-code/references/ux-contract.md +20 -15
  9. package/.agents/skills/design-to-code/references/visual-creation.md +37 -0
  10. package/.agents/skills/project-foundation/SKILL.md +39 -30
  11. package/.agents/skills/project-foundation/assets/AGENTS.foundation.md +16 -15
  12. package/.agents/skills/project-foundation/assets/PROJECT_PROFILE.md +24 -23
  13. package/.agents/skills/project-foundation/assets/START_HERE.md +17 -14
  14. package/.agents/skills/project-foundation/references/mission-context.md +30 -0
  15. package/.agents/skills/project-foundation/references/operating-commands.md +49 -34
  16. package/.agents/skills/project-foundation/references/profiles/README.md +18 -0
  17. package/.agents/skills/project-foundation/references/profiles/ci.md +11 -0
  18. package/.agents/skills/project-foundation/references/profiles/cloud-delivery.md +13 -0
  19. package/.agents/skills/project-foundation/references/profiles/messaging.md +11 -0
  20. package/.agents/skills/project-foundation/references/profiles/mongodb.md +11 -0
  21. package/.agents/skills/project-foundation/references/profiles/node-nest.md +11 -0
  22. package/.agents/skills/project-foundation/references/profiles/postgres-drizzle.md +11 -0
  23. package/.agents/skills/project-foundation/references/profiles/react-next.md +11 -0
  24. package/.agents/skills/project-foundation/references/profiles/typescript.md +11 -0
  25. package/.agents/skills/project-foundation/references/work-sizing.md +31 -0
  26. package/.agents/skills/react-feature-engineering/SKILL.md +26 -22
  27. package/.agents/skills/react-feature-engineering/references/review-and-sources.md +41 -37
  28. package/.agents/skills/reliable-ai-integration/SKILL.md +13 -10
  29. package/.agents/skills/reliable-ai-integration/assets/AI_EVALUATION.md +19 -18
  30. package/.agents/skills/reliable-ai-integration/references/evidence-and-media.md +22 -18
  31. package/.agents/skills/reliable-ai-integration/references/jobs-and-costs.md +20 -15
  32. package/.agents/skills/scoped-delivery/SKILL.md +25 -18
  33. package/.agents/skills/scoped-delivery/assets/CHECKPOINT.md +22 -11
  34. package/.agents/skills/scoped-delivery/assets/MISSION.md +35 -0
  35. package/.agents/skills/scoped-delivery/assets/SLICE.md +15 -14
  36. package/.agents/skills/scoped-delivery/assets/VERIFICATION.md +19 -0
  37. package/.agents/skills/scoped-delivery/references/verification-and-cost.md +21 -18
  38. package/COMPATIBILITY.md +3 -3
  39. package/CONTRIBUTING.md +13 -0
  40. package/README.md +94 -9
  41. package/START_HERE.md +24 -0
  42. package/VALIDATION.md +37 -0
  43. package/dist/checkpoint.js +171 -0
  44. package/dist/cli.js +118 -17
  45. package/dist/doctor.js +113 -0
  46. package/dist/filesystem.js +32 -0
  47. package/dist/init.js +38 -34
  48. package/dist/mission.js +85 -0
  49. package/dist/planner.js +65 -0
  50. package/dist/records.js +76 -0
  51. package/dist/update.js +57 -0
  52. package/docs/ADR-001-distribution.md +13 -0
  53. package/docs/ADR-002-read-only-diagnostics.md +25 -0
  54. package/docs/ADR-003-installation-provenance.md +15 -0
  55. package/docs/ADR-004-checkpoint-resumption.md +23 -0
  56. package/docs/ADR-005-mission-context.md +11 -0
  57. package/docs/ADR-006-opt-in-native-pilot.md +15 -0
  58. package/docs/BMAD-COMPARISON.md +34 -0
  59. package/docs/CODEX-ADAPTER-VALIDATION.md +20 -0
  60. package/docs/COMPARISON-ITERATION.md +25 -0
  61. package/docs/DOCTOR.md +23 -0
  62. package/docs/EVALUATION.md +39 -0
  63. package/docs/EVOLUTION.md +34 -0
  64. package/docs/GREENFIELD-VALIDATION.md +36 -0
  65. package/docs/MILESTONES.md +39 -0
  66. package/docs/MISSIONS.md +47 -0
  67. package/docs/NATIVE-PILOT-RESULTS.md +22 -0
  68. package/docs/NATIVE-PILOT.md +17 -0
  69. package/docs/ORCHESTRATION.md +11 -0
  70. package/docs/RC2-VALIDATION.md +50 -0
  71. package/docs/RELEASE-0.1.0.md +60 -0
  72. package/docs/RELEASE-0.2.0.md +37 -0
  73. package/docs/RELEASE-CHECKLIST.md +13 -0
  74. package/docs/RELEASE-NOTES-0.1.0-rc.2.md +32 -0
  75. package/docs/RELEASE-NOTES-0.1.0.md +11 -0
  76. package/docs/RESUMPTION.md +87 -0
  77. package/docs/ROADMAP.md +39 -0
  78. package/docs/STACK-PROFILES.md +9 -0
  79. package/docs/TROUBLESHOOTING.md +16 -0
  80. package/docs/UPDATES.md +30 -0
  81. package/docs/VISUAL-CREATION-PROPOSAL.md +21 -0
  82. package/docs/VISUAL-WORKFLOW.md +75 -0
  83. package/docs/images/devmethod-delivery.svg +10 -0
  84. package/docs/images/devmethod-flow.svg +13 -0
  85. package/docs/media/from-zero/README.md +17 -0
  86. package/docs/media/from-zero/devmethod-demo.fr.srt +59 -0
  87. package/docs/media/from-zero/scenes.json +50 -0
  88. package/docs/media/full-chain-4k/README.md +37 -0
  89. package/docs/media/full-chain-4k/devmethod-chaine-complete.fr.srt +435 -0
  90. package/docs/media/full-chain-4k/scenes.json +173 -0
  91. package/docs/media/full-chain-4k/transcripts.fr.md +315 -0
  92. package/docs/media/visual-chain/README.md +44 -0
  93. package/docs/media/visual-chain/assets/add-book-v1.png +0 -0
  94. package/docs/media/visual-chain/assets/completed-v1.png +0 -0
  95. package/docs/media/visual-chain/devmethod-du-besoin-au-produit.fr.srt +147 -0
  96. package/docs/media/visual-chain/execution.fr.md +30 -0
  97. package/docs/media/visual-chain/image-prompts.json +9 -0
  98. package/docs/media/visual-chain/reference-hashes.json +5 -0
  99. package/docs/media/visual-chain/scenes.json +232 -0
  100. package/docs/media/visual-chain/video-preview.jpg +0 -0
  101. package/docs/missions/visual-workflow.md +46 -0
  102. package/evaluation/COMPARISONS.md +33 -0
  103. package/evaluation/README.md +35 -0
  104. package/evaluation/candidate-setup.json +33 -0
  105. package/evaluation/comparison-v2-readiness.json +18 -0
  106. package/evaluation/comparison-v2-results.json +58 -0
  107. package/evaluation/fixtures/dependency/CONTRIBUTING.md +3 -0
  108. package/evaluation/fixtures/dependency/TICKET.md +3 -0
  109. package/evaluation/fixtures/dependency/acceptance.test.mjs +4 -0
  110. package/evaluation/fixtures/dependency/api.mjs +1 -0
  111. package/evaluation/fixtures/failing-verification/CONTRIBUTING.md +3 -0
  112. package/evaluation/fixtures/failing-verification/acceptance.test.mjs +4 -0
  113. package/evaluation/fixtures/failing-verification/total.mjs +1 -0
  114. package/evaluation/fixtures/stale-handoff/CHECKPOINT.md +3 -0
  115. package/evaluation/fixtures/stale-handoff/CONTRACT.md +3 -0
  116. package/evaluation/fixtures/stale-handoff/CONTRIBUTING.md +3 -0
  117. package/evaluation/fixtures/stale-handoff/acceptance.test.mjs +4 -0
  118. package/evaluation/fixtures/stale-handoff/label.mjs +1 -0
  119. package/evaluation/fixtures/update-customization/README.md +13 -0
  120. package/evaluation/fixtures.json +85 -0
  121. package/evaluation/greenfield/acceptance.test.mjs +57 -0
  122. package/evaluation/greenfield/security.test.mjs +28 -0
  123. package/evaluation/native-pilot-results.json +155 -0
  124. package/examples/README.md +36 -0
  125. package/examples/bugfix/acceptance.test.mjs +17 -0
  126. package/examples/bugfix/page-size.mjs +4 -0
  127. package/examples/clair-from-zero/AGENT-EVALUATION.md +15 -0
  128. package/examples/clair-from-zero/MISSION.md +41 -0
  129. package/examples/clair-from-zero/README.md +24 -0
  130. package/examples/clair-from-zero/app/app.mjs +69 -0
  131. package/examples/clair-from-zero/app/domain.mjs +30 -0
  132. package/examples/clair-from-zero/app/index.html +20 -0
  133. package/examples/clair-from-zero/app/storage.mjs +4 -0
  134. package/examples/clair-from-zero/app/styles.css +128 -0
  135. package/examples/clair-from-zero/browser-check.cjs +2 -0
  136. package/examples/clair-from-zero/tests/domain.test.mjs +37 -0
  137. package/examples/fullstack/CONTRACT.md +23 -0
  138. package/examples/fullstack/DEPENDENCIES.md +18 -0
  139. package/examples/fullstack/README.md +100 -0
  140. package/examples/fullstack/api/application/tasks.ts +13 -0
  141. package/examples/fullstack/api/domain/task.ts +12 -0
  142. package/examples/fullstack/api/http.ts +26 -0
  143. package/examples/fullstack/api/infrastructure/migrate.ts +15 -0
  144. package/examples/fullstack/api/infrastructure/postgres.ts +18 -0
  145. package/examples/fullstack/api/infrastructure/schema.ts +6 -0
  146. package/examples/fullstack/api/main.ts +17 -0
  147. package/examples/fullstack/api/tsconfig.json +17 -0
  148. package/examples/fullstack/compose.yaml +14 -0
  149. package/examples/fullstack/migrations/0000_tasks.sql +5 -0
  150. package/examples/fullstack/migrations/meta/_journal.json +13 -0
  151. package/examples/fullstack/mission.json +34 -0
  152. package/examples/fullstack/package-lock.json +2661 -0
  153. package/examples/fullstack/package.json +45 -0
  154. package/examples/fullstack/tests/e2e.test.cjs +54 -0
  155. package/examples/fullstack/tests/http.test.cjs +33 -0
  156. package/examples/fullstack/tests/postgres.test.cjs +29 -0
  157. package/examples/fullstack/tests/unit.test.cjs +20 -0
  158. package/examples/fullstack/tests/web-model.test.cjs +23 -0
  159. package/examples/fullstack/web/app/layout.tsx +4 -0
  160. package/examples/fullstack/web/app/page.tsx +11 -0
  161. package/examples/fullstack/web/features/tasks/components/task-list.tsx +11 -0
  162. package/examples/fullstack/web/features/tasks/hooks/use-task-filter.ts +7 -0
  163. package/examples/fullstack/web/features/tasks/model/tasks.ts +12 -0
  164. package/examples/fullstack/web/features/tasks/server/list-tasks.ts +8 -0
  165. package/examples/fullstack/web/next.config.mjs +2 -0
  166. package/examples/fullstack/web/tsconfig.json +36 -0
  167. package/examples/mission/CONTRACT.md +3 -0
  168. package/examples/mission/mission.json +18 -0
  169. package/examples/mission/plan.json +11 -0
  170. package/examples/pocket-tasks/BRIEF.md +21 -0
  171. package/examples/pocket-tasks/PROJECT_PROFILE.md +19 -0
  172. package/examples/pocket-tasks/README.md +43 -0
  173. package/examples/pocket-tasks/docs/ADR-001.md +7 -0
  174. package/examples/pocket-tasks/docs/HANDOFF.md +16 -0
  175. package/examples/pocket-tasks/docs/MISSION.md +15 -0
  176. package/examples/pocket-tasks/docs/VERIFICATION.md +58 -0
  177. package/examples/pocket-tasks/package.json +1 -0
  178. package/examples/pocket-tasks/public/app.js +84 -0
  179. package/examples/pocket-tasks/public/index.html +12 -0
  180. package/examples/pocket-tasks/public/styles.css +1 -0
  181. package/examples/pocket-tasks/server.mjs +79 -0
  182. package/examples/pocket-tasks/src/domain.mjs +31 -0
  183. package/examples/pocket-tasks/src/store.mjs +49 -0
  184. package/examples/pocket-tasks/tests/app.test.mjs +132 -0
  185. package/examples/pocket-tasks/tests/ui-errors.test.mjs +49 -0
  186. package/examples/visual-pilot/README.md +11 -0
  187. package/examples/visual-pilot/app/app.js +10 -0
  188. package/examples/visual-pilot/app/index.html +1 -0
  189. package/examples/visual-pilot/app/reference.png +0 -0
  190. package/examples/visual-pilot/app/style.css +3 -0
  191. package/examples/visual-pilot/browser-check.cjs +3 -0
  192. package/examples/visual-pilot/desktop-actual.png +0 -0
  193. package/examples/visual-pilot/directions-prompt.txt +1 -0
  194. package/examples/visual-pilot/directions-v1.png +0 -0
  195. package/examples/visual-pilot/editorial-mockup-prompt.txt +1 -0
  196. package/examples/visual-pilot/editorial-mockup-v1.png +0 -0
  197. package/examples/visual-pilot/mismatch-probe.png +0 -0
  198. package/examples/visual-pilot/mobile-actual.png +0 -0
  199. package/examples/visual-pilot/quick-filter/AGENT-RESULT.md +25 -0
  200. package/examples/visual-pilot/quick-filter/baseline.log +42 -0
  201. package/examples/visual-pilot/quick-filter/filter.mjs +3 -0
  202. package/examples/visual-pilot/quick-filter/filter.test.mjs +9 -0
  203. package/package.json +15 -3
  204. package/scripts/check-codex-git.mjs +20 -0
  205. package/scripts/check-docs.mjs +14 -0
  206. package/scripts/check-native-sandbox.mjs +11 -0
  207. package/scripts/comparison-readiness.mjs +26 -0
  208. package/scripts/comparison.mjs +42 -0
  209. package/scripts/evaluation.mjs +64 -0
  210. package/scripts/fullstack-mission.mjs +30 -0
  211. package/scripts/hosts/checks.mjs +13 -0
  212. package/scripts/hosts/codex-meter.mjs +17 -0
  213. package/scripts/hosts/codex-rpc.mjs +44 -0
  214. package/scripts/hosts/codex-task.mjs +29 -0
  215. package/scripts/hosts/codex.mjs +28 -0
  216. package/scripts/media/demo-actions.cjs +16 -0
  217. package/scripts/media/encode-demo.py +22 -0
  218. package/scripts/media/record-demo.cjs +30 -0
  219. package/scripts/media/visual-short/check.cjs +1 -0
  220. package/scripts/media/visual-short/encode.py +16 -0
  221. package/scripts/media/visual-short/record.cjs +4 -0
  222. package/scripts/media/visual-short/story.py +34 -0
  223. package/scripts/native-host.mjs +55 -0
  224. package/scripts/package-smoke.mjs +64 -0
  225. package/scripts/probe-codex-children.mjs +41 -0
  226. package/scripts/run-comparison-v2.mjs +45 -0
  227. package/scripts/run-native-pilot.mjs +76 -0
  228. package/scripts/summarize-native-pilot.mjs +7 -0
@@ -0,0 +1,55 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+ import { checkPath } from '../dist/filesystem.js';
5
+
6
+ /** Bounded process supervision. Success means process exit, not task acceptance. */
7
+ export async function supervise({ command, args, cwd, prompt, timeoutMs, signal, env, maxBytes = 2 * 1024 * 1024 }) {
8
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 120000) throw new Error('Timeout must be 1–120000 ms.');
9
+ if (signal?.aborted) return { status: 'cancelled', exit: null, stdout: '', stderr: '', elapsedSeconds: 0 };
10
+ const start = Date.now();
11
+ return await new Promise(resolve => {
12
+ const child = spawn(command, args, { cwd, env, detached: process.platform !== 'win32', stdio: ['pipe', 'pipe', 'pipe'] });
13
+ let stdout = '', stderr = '', bytes = 0, stopped, killTimer, settled = false;
14
+ const kill = name => { try { if (process.platform !== 'win32') process.kill(-child.pid, name); else child.kill(name); } catch {} };
15
+ const stop = reason => { if (stopped) return; stopped = reason; kill('SIGTERM'); killTimer = setTimeout(() => kill('SIGKILL'), 1500); };
16
+ const timer = setTimeout(() => stop('timeout'), timeoutMs);
17
+ const cancel = () => stop('cancelled'); signal?.addEventListener('abort', cancel, { once: true });
18
+ const finish = (exit, error) => {
19
+ if (settled) return; settled = true; clearTimeout(timer); clearTimeout(killTimer); signal?.removeEventListener('abort', cancel);
20
+ // Terminate remaining descendants even if the direct child already exited.
21
+ if (process.platform !== 'win32') kill('SIGKILL');
22
+ resolve({ status: stopped ?? (error ? 'unavailable' : exit === 0 ? 'exited' : 'failed'), exit, error: error?.message,
23
+ stdout, stderr, elapsedSeconds: (Date.now()-start)/1000 });
24
+ };
25
+ const read = (chunk, kind) => { bytes += Buffer.byteLength(chunk); if (bytes > maxBytes) { stop('output-limit'); return; }
26
+ if (kind === 'out') stdout += chunk.toString(); else stderr += chunk.toString(); };
27
+ child.stdout.setEncoding('utf8'); child.stderr.setEncoding('utf8');
28
+ child.stdout.on('data', c => read(c,'out')); child.stderr.on('data', c => read(c,'err'));
29
+ child.once('error', e => finish(null,e)); child.once('close', code => finish(code));
30
+ child.stdin.on('error', () => {}); child.stdin.end(prompt);
31
+ });
32
+ }
33
+ /** Immutable per-run slots avoid blind retries. Ledger lives outside worker writable roots. */
34
+ export function reserveRun(ledgerRoot, id, { maxRuns = 12, observedTokenStop = 500000 } = {}) {
35
+ if (!Number.isInteger(maxRuns) || maxRuns < 1 || maxRuns > 12 || !Number.isSafeInteger(observedTokenStop) || observedTokenStop < 1 || observedTokenStop > 500000) throw new Error('Invalid bounded pilot limits.');
36
+ if (!/^[a-zA-Z0-9-]{1,64}$/.test(id)) throw new Error('Invalid run ID.');
37
+ checkPath(path.resolve(ledgerRoot)); fs.mkdirSync(ledgerRoot, { recursive: true });
38
+ const lock = path.join(ledgerRoot, '.lock'); const fd = fs.openSync(lock, 'wx');
39
+ try {
40
+ const records = fs.readdirSync(ledgerRoot).filter(n=>n.endsWith('.json')).map(n=>JSON.parse(fs.readFileSync(path.join(ledgerRoot,n),'utf8')));
41
+ if (records.length >= maxRuns) throw new Error('Run limit reached.');
42
+ if (records.some(r=>!['exited','failed'].includes(r.status) || r.usage === null)) throw new Error('Interrupted, cancelled, unavailable or unmetered run requires reconciliation; no automatic retry.');
43
+ const used = records.reduce((n,r)=>n+r.usage.inputTokens+r.usage.outputTokens,0);
44
+ if (used >= observedTokenStop) throw new Error('Observed token stop reached. This is an inter-run stop, not a per-request cap.');
45
+ const file = path.join(ledgerRoot, `${id}.json`);
46
+ fs.writeFileSync(file, JSON.stringify({ id, status: 'running', usage: null }), { flag: 'wx' }); return file;
47
+ } finally { fs.closeSync(fd); fs.unlinkSync(lock); }
48
+ }
49
+ export function recordRun(file, result) {
50
+ checkPath(path.resolve(file));
51
+ const previous = JSON.parse(fs.readFileSync(file));
52
+ if (previous.status !== 'running') throw new Error('Run already finalized.');
53
+ const temporary = `${file}.tmp`;
54
+ fs.writeFileSync(temporary, JSON.stringify({ ...result, id: previous.id }, null, 2), { flag: 'wx' }); fs.renameSync(temporary,file);
55
+ }
@@ -0,0 +1,64 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import assert from 'node:assert/strict';
5
+ import { spawnSync } from 'node:child_process';
6
+ const archive = process.argv[2];
7
+ if (!archive) throw new Error('Usage: node scripts/package-smoke.mjs PACKAGE_TGZ');
8
+ const root = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'devmethod-package-'));
9
+ const run = (command, args, cwd = root, expected = 0, input) => {
10
+ const r = spawnSync(command, args, { cwd, encoding: 'utf8', timeout: 30000, input });
11
+ assert.equal(r.status, expected, `${command} ${args.join(' ')}\n${r.stdout}\n${r.stderr}`); return r.stdout;
12
+ };
13
+ try {
14
+ // stdin avoids GNU tar treating a Windows drive letter as a remote host.
15
+ run('tar', ['-xzf', '-'], root, 0, fs.readFileSync(path.resolve(archive)));
16
+ const pkg = path.join(root, 'package'); const cli = path.join(pkg, 'dist/cli.js');
17
+ const call = (args, expected = 0) => run(process.execPath, [cli, ...args], root, expected);
18
+ assert.equal(JSON.parse(fs.readFileSync(path.join(pkg, 'package.json'))).version, '0.2.0');
19
+ run(process.execPath, ['scripts/check-docs.mjs'], pkg);
20
+ for (const testFile of [...fs.readdirSync(path.join(pkg, 'examples/pocket-tasks/tests')).filter(f => f.endsWith('.test.mjs')).map(f => `examples/pocket-tasks/tests/${f}`), 'evaluation/greenfield/acceptance.test.mjs', 'evaluation/greenfield/security.test.mjs']) {
21
+ run(process.execPath, ['--test', testFile], pkg);
22
+ }
23
+ for (const host of ['codex', 'claude', 'cursor']) {
24
+ const project = path.join(root, host);
25
+ call(['init', '--tool', host, '--dest', project]);
26
+ assert.equal(JSON.parse(call(['doctor', '--dest', project, '--json'])).status, 'ok');
27
+ const profile = path.join(project, 'PROJECT_PROFILE.md'); fs.appendFileSync(profile, '\nFictional local customization.\n');
28
+ const before = fs.readFileSync(profile);
29
+ const preview = JSON.parse(call(['update-preview', '--dest', project, '--json']));
30
+ assert.equal(preview.entries.find(e => e.path === 'PROJECT_PROFILE.md').classification, 'customized');
31
+ call(['init', '--tool', host, '--dest', project], 2); assert.deepEqual(fs.readFileSync(profile), before);
32
+ }
33
+ call(['init', '--tool', 'claude', '--modules', 'scoped-delivery', '--dest', path.join(root, 'subset')]);
34
+ const project = path.join(root, 'mission'); fs.mkdirSync(path.join(project, 'examples'), { recursive: true });
35
+ fs.cpSync(path.join(pkg, 'examples/mission'), path.join(project, 'examples/mission'), { recursive: true });
36
+ fs.writeFileSync(path.join(project, '.gitignore'), 'evidence/\n');
37
+ run('git', ['init'], project); run('git', ['add', '.'], project);
38
+ run('git', ['-c', 'user.name=Fixture', '-c', 'user.email=fixture@example.invalid', 'commit', '-m', 'fixture'], project);
39
+ const args = ['--dest', project, '--json'];
40
+ assert.equal(JSON.parse(call(['mission', '--mission', 'examples/mission/mission.json', ...args])).status, 'ready');
41
+ const context = call(['context', '--mission', 'examples/mission/mission.json', ...args]);
42
+ fs.mkdirSync(path.join(project, 'evidence')); fs.writeFileSync(path.join(project, 'evidence/context.json'), context);
43
+ assert.equal(JSON.parse(call(['context-check', '--context', 'evidence/context.json', ...args])).status, 'ready');
44
+ fs.appendFileSync(path.join(project, 'examples/mission/CONTRACT.md'), '\nChanged contract.\n');
45
+ assert.equal(JSON.parse(call(['context-check', '--context', 'evidence/context.json', ...args], 1)).status, 'reverify');
46
+ assert.deepEqual(JSON.parse(call(['plan', '--plan', 'examples/mission/plan.json', ...args])).candidates, ['api']);
47
+ if (process.argv[3]) {
48
+ const legacy = path.join(root, 'legacy-package'); fs.mkdirSync(legacy);
49
+ run('tar', ['-xzf', '-', '--strip-components=1'], legacy, 0, fs.readFileSync(path.resolve(process.argv[3])));
50
+ const adopted = path.join(root, 'legacy-adopted');
51
+ run(process.execPath, [path.join(legacy, 'dist/cli.js'), 'init', '--tool', 'codex', '--dest', adopted]);
52
+ const customized = path.join(adopted, '.agents/skills/project-foundation/SKILL.md');
53
+ const profile = path.join(adopted, 'PROJECT_PROFILE.md');
54
+ fs.appendFileSync(customized, '\nLocal workflow policy.\n'); fs.appendFileSync(profile, '\nFilled project context.\n');
55
+ const before = [fs.readFileSync(customized), fs.readFileSync(profile)];
56
+ const preview = JSON.parse(call(['update-preview', '--dest', adopted, '--json']));
57
+ assert.equal(preview.entries.find(e => e.path === '.agents/skills/project-foundation/SKILL.md').classification, 'conflict');
58
+ assert.equal(preview.provenance, 'unknown');
59
+ call(['init', '--tool', 'codex', '--dest', adopted], 2);
60
+ assert.deepEqual([fs.readFileSync(customized), fs.readFileSync(profile)], before);
61
+ console.log('Actual legacy tarball: local/upstream conflict detected and filled profile/custom skill preserved.');
62
+ }
63
+ console.log('Packed 0.2.0: three host installs, subset, customization preservation, mission/context/staleness/planning and documentation links passed. No native host execution.');
64
+ } finally { fs.rmSync(root, { recursive: true, force: true }); }
@@ -0,0 +1,41 @@
1
+ // One explicitly bounded infrastructure probe, not a comparative result.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { meterCodexTree } from './hosts/codex-meter.mjs';
5
+ import { startCodexRpc } from './hosts/codex-rpc.mjs';
6
+ import { reserveRun, recordRun } from './native-host.mjs';
7
+ const cancelling=process.argv.includes('--cancel');
8
+ const runId=cancelling?'children-cancel-preflight':'children-preflight';
9
+ const evidence=path.resolve('evaluation-private/comparison-v2'); fs.mkdirSync(evidence,{recursive:true});
10
+ const slot=reserveRun(path.join(evidence,'ledger'),runId);
11
+ const cwd=fs.mkdtempSync('/private/tmp/devmethod-child-probe-');
12
+ const log=fs.openSync(path.join(evidence,runId+'.jsonl'),'wx');
13
+ const events=[];const turns=new Map();let cancellation;
14
+ let root, done; const finished=new Promise(resolve=>{done=resolve;});
15
+ const totals=new Map(), children=new Set(), completed=new Set();
16
+ const rpc=startCodexRpc({cwd,onEvent(event){
17
+ events.push(event);
18
+ if(event.method==='account/rateLimits/updated')return;
19
+ fs.writeSync(log,JSON.stringify(event)+'\n'); const p=event.params;
20
+ if(p.item?.type==='subAgentActivity')children.add(p.item.agentThreadId);
21
+ if(event.method==='thread/started' && p.thread.parentThreadId)children.add(p.thread.id);
22
+ if(event.method==='thread/tokenUsage/updated')totals.set(p.threadId,p.tokenUsage.total);
23
+ if(event.method==='turn/started')turns.set(p.threadId,p.turn.id);
24
+ if(cancelling && !cancellation && event.method==='item/started' && p.item?.type==='commandExecution' && p.threadId!==root) {
25
+ cancellation=Promise.all([...turns].map(([threadId,turnId])=>rpc.call('turn/interrupt',{threadId,turnId})));
26
+ cancellation.catch(e=>done('interrupt-error: '+e.message));
27
+ }
28
+ if(event.method==='turn/completed') {completed.add(p.threadId); if(p.threadId===root)done(p.turn.status);}
29
+ }});
30
+ const abort=()=>{rpc.close();done('cancelled');};process.on('SIGINT',abort);process.on('SIGTERM',abort);
31
+ let status='failed',error;
32
+ try {
33
+ await rpc.call('initialize',{clientInfo:{name:'devmethod_probe',version:'0.1.0'},capabilities:{experimentalApi:true}});rpc.notify({method:'initialized'});
34
+ const result=await rpc.call('thread/start',{cwd,model:'gpt-5.6-sol',ephemeral:true,approvalPolicy:'never',sandbox:'read-only',config:{'model_reasoning_effort':'low','features.multi_agent':true,'agents.max_threads':2,'agents.max_depth':1,'features.apps':false,'features.plugins':false,'features.remote_plugin':false,'web_search':'disabled'},developerInstructions:'Infrastructure probe in a fictional empty directory. No file writes, network tools, external actions or purchases. Only the single requested child and its explicitly requested sleep command are authorized.'});
35
+ root=result.thread.id;
36
+ await rpc.call('turn/start',{threadId:root,effort:'low',input:[{type:'text',text:cancelling?'Spawn exactly one child to run the shell command sleep 30 and then answer CHILD_OK. Wait for the child and answer PARENT_OK. No other work or tools. This bounded delegation is authorized.':'Spawn exactly one child agent to answer CHILD_OK without using tools or spawning agents. Wait for that child to finish, then answer PARENT_OK. This bounded delegation is explicitly authorized.',text_elements:[]}]});
37
+ let deadline; try {status=await Promise.race([finished,new Promise((_,reject)=>{deadline=setTimeout(()=>reject(new Error('Probe deadline')),110000);})]);}finally{clearTimeout(deadline);}
38
+ if(cancelling && cancellation)await cancellation;
39
+ }catch(e){error=e.message;}finally{process.removeListener('SIGINT',abort);process.removeListener('SIGTERM',abort);rpc.close();fs.closeSync(log);}
40
+ const {usage}=meterCodexTree(events,root);
41
+ const record={status:status==='completed'||(cancelling && status==='interrupted')?'exited':'failed',probeStatus:status,cancellationRequested:Boolean(cancellation),usage,error,root,children:[...children],completed:[...completed],totals:Object.fromEntries(totals),note:'Observed per-thread totals; native accounting semantics still require review.'};recordRun(slot,record);console.log(JSON.stringify(record,null,2));
@@ -0,0 +1,45 @@
1
+ // Explicit bounded series. Source checkout and reviewed BMAD export required.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import {execFileSync} from 'node:child_process';
5
+ import {spawnSync} from 'node:child_process';
6
+ import {codexEnvironment,codexVersion} from './hosts/codex.mjs';
7
+ import {createHash} from 'node:crypto';
8
+ import {initialize} from '../dist/init.js';
9
+ import {prepare,snapshot,collect} from './evaluation.mjs';
10
+ import {verifyFixture} from './hosts/checks.mjs';
11
+ import {runCodexTask} from './hosts/codex-task.mjs';
12
+ import {reserveRun,recordRun} from './native-host.mjs';
13
+ const count=Number(process.argv[2]), bmad=process.argv[3];
14
+ if(process.platform!=='darwin'||!Number.isInteger(count)||count<1||count>6||!bmad||!path.isAbsolute(bmad))throw Error('Usage: node scripts/run-comparison-v2.mjs COUNT_1_TO_6 ABSOLUTE_BMAD_EXPORT (macOS)');
15
+ if(execFileSync('codex',['--version'],{encoding:'utf8',env:codexEnvironment()}).trim()!==codexVersion)throw Error('Codex version mismatch');
16
+ const auth=spawnSync('codex',['login','status'],{encoding:'utf8',env:codexEnvironment(),timeout:10000});
17
+ if(auth.status!==0||!`${auth.stdout}${auth.stderr}`.includes('Logged in using ChatGPT'))throw Error('ChatGPT authentication required');
18
+ const evidence=path.resolve('evaluation-private/comparison-v2');fs.mkdirSync(evidence,{recursive:true});
19
+ const git=(cwd,args)=>execFileSync('git',['-C',cwd,...args],{encoding:'utf8'}).trim();
20
+ const revision=git(process.cwd(),['rev-parse','HEAD']);
21
+ const schedule=['bmad','devmethod','devmethod','bmad','bmad','devmethod'];
22
+ for(const [index,arm] of schedule.slice(0,count).entries()){
23
+ const id=`b1-${index+1}-${arm}`,file=path.join(evidence,'ledger',id+'.json');
24
+ if(fs.existsSync(file)){const previous=JSON.parse(fs.readFileSync(file));if(previous.status==='exited'&&previous.usage)continue;throw Error('Unsuccessful slot requires reconciliation');}
25
+ const slot=reserveRun(path.join(evidence,'ledger'),id);
26
+ const cwd=fs.mkdtempSync('/private/tmp/devmethod-comparison-'),fixture=path.join(cwd,'task');prepare('B1',fixture);
27
+ if(arm==='bmad')fs.cpSync(bmad,fixture,{recursive:true});else initialize({destination:fixture,tool:'codex'});
28
+ // Export rendering belongs to each invocation, not the preparation directory.
29
+ if(arm==='bmad')fs.rmSync(path.join(fixture,'_bmad/render'),{recursive:true,force:true});
30
+ git(fixture,['init','-b','fix-page-size']);
31
+ fs.writeFileSync(path.join(fixture,'.git/info/exclude'),'/.runtime/\n/_bmad/render/\n/_bmad-output/\n');
32
+ git(fixture,['add','.']);git(fixture,['-c','user.name=Evaluator','-c','user.email=evaluator@example.invalid','commit','-m','Pinned fictional baseline']);
33
+ const baseline=snapshot(fixture);
34
+ const prompt=(arm==='bmad'?'Use the installed bmad-build-auto skill. ':'Use the installed project-foundation skill and relevant references. ')+
35
+ 'Fix page-size.mjs so all supplied acceptance tests pass. Preserve acceptance.test.mjs and change no unrelated source files. You may create method artifacts under _bmad-output/, runtime renderings under _bmad/render/, and temporary files under .runtime/. Deliver the local fix and HANDOFF.md. Routine planning and implementation are authorized. Synchronous subagents are authorized, up to three child agents concurrently, depth one; no detached work. Git staging in this isolated repository is authorized; no commits, external writes, network tools, purchases or publication. Wait for all children before finishing.';
36
+ fs.writeFileSync(path.join(evidence,id+'-setup.json'),JSON.stringify({arm,revision,cwd:fixture,prompt,baseline,model:'gpt-5.6-sol',effort:'low',bmadVersion:'6.12.0',driverPins:Object.fromEntries(['run-comparison-v2.mjs','hosts/codex-task.mjs','hosts/codex-rpc.mjs','hosts/codex-meter.mjs','hosts/checks.mjs','hosts/codex.mjs','evaluation.mjs','native-host.mjs'].map(file=>[file,createHash('sha256').update(fs.readFileSync(new URL(file,import.meta.url))).digest('hex')]))},null,2));
37
+ console.log(JSON.stringify({event:'started',id}));
38
+ const result=await runCodexTask({cwd:fixture,prompt,logFile:path.join(evidence,id+'.jsonl')});
39
+ let checks;try{checks=collect('B1',fixture,baseline,verifyFixture);}catch(e){checks={error:e.message};}
40
+ const disallowed=checks.unauthorizedChanges?.filter(name=>baseline[name]!==undefined||!['_bmad-output/','_bmad/render/','.runtime/'].some(prefix=>name.startsWith(prefix)));
41
+ const handoffPresent=fs.existsSync(path.join(fixture,'HANDOFF.md'))&&fs.statSync(path.join(fixture,'HANDOFF.md')).isFile()&&fs.statSync(path.join(fixture,'HANDOFF.md')).size>0;
42
+ const acceptance=Boolean(handoffPresent&&checks.testsIntact&&checks.check.exit===0&&disallowed?.length===0);
43
+ recordRun(slot,{...result,arm,acceptance,handoffPresent,checks,disallowed});console.log(JSON.stringify({event:'finished',id,status:result.status,usage:result.usage,acceptance}));
44
+ if(result.status!=='exited'||!result.usage)break;
45
+ }
@@ -0,0 +1,76 @@
1
+ // Explicit opt-in fixture campaign. Usage: node scripts/run-native-pilot.mjs ABSOLUTE_ROOT COUNT
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { execFileSync } from 'node:child_process';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { createHash } from 'node:crypto';
7
+ import { fixtures, prepare, snapshot, collect } from './evaluation.mjs';
8
+ import { verifyFixture } from './hosts/checks.mjs';
9
+ import { initialize } from '../dist/init.js';
10
+ import { supervise, reserveRun, recordRun } from './native-host.mjs';
11
+ import { codexArguments, codexEnvironment, codexVersion, codexUsage } from './hosts/codex.mjs';
12
+ const source=fileURLToPath(new URL('../',import.meta.url));
13
+ const root=process.argv[2]; const count=Number(process.argv[3]);
14
+ if (!root || !path.isAbsolute(root) || !Number.isInteger(count) || count<1 || count>12) throw new Error('Supply absolute disposable campaign root and 1–12 slots.');
15
+ if (process.platform !== 'darwin') throw new Error('This native fixture pilot is validated on macOS only.');
16
+ const env=codexEnvironment();
17
+ const probe=args=>execFileSync('codex',args,{encoding:'utf8',env,timeout:10000,stdio:['ignore','pipe','pipe']}).trim();
18
+ if (probe(['--version'])!==codexVersion) throw new Error('Installed Codex version differs from pinned pilot.');
19
+ // login status may write its human-readable result to stderr.
20
+ const {spawnSync}=await import('node:child_process');
21
+ const auth=spawnSync('codex',['login','status'],{encoding:'utf8',env,timeout:10000});
22
+ if(auth.status!==0 || !`${auth.stdout}${auth.stderr}`.includes('Logged in using ChatGPT')) throw new Error('ChatGPT CLI authentication required; no API fallback.');
23
+ fs.mkdirSync(root,{recursive:true});
24
+ const ledger=path.join(root,'ledger'), evidence=path.join(root,'evidence'), worktrees=path.join(root,'worktrees'), base=path.join(root,'base');
25
+ for(const dir of [evidence,worktrees]) fs.mkdirSync(dir,{recursive:true});
26
+ const git=(dir,args)=>execFileSync('git',['-c','core.fsmonitor=false','-C',dir,...args],{encoding:'utf8',timeout:10000,stdio:['ignore','pipe','pipe']}).trim();
27
+ if(!fs.existsSync(base)) {fs.mkdirSync(base);git(base,['init']);fs.writeFileSync(path.join(base,'WORKSPACE.md'),'Fictional evaluation workspace. Local task scope only. No external actions.\n');git(base,['add','.']);git(base,['-c','user.name=Evaluator','-c','user.email=evaluator@example.invalid','commit','-m','Fictional baseline']);}
28
+ const hash=value=>createHash('sha256').update(value).digest('hex');
29
+ const revision=git(source,['rev-parse','HEAD']);
30
+ const schedule=[{id:'b1-1-none',caseId:'B1',arm:'none',repetition:1,purpose:'calibration-only'}];
31
+ for(let rep=1;rep<=3;rep++) for(const arm of [['none','devmethod','bmad'],['devmethod','bmad','none'],['bmad','none','devmethod']][rep-1]) schedule.push({id:`matched-b1-${rep}-${arm}`,caseId:'B1',arm,repetition:rep});
32
+ for(const caseId of ['B3','B4'])schedule.push({id:`smoke-${caseId.toLowerCase()}`,caseId,arm:'devmethod',repetition:1});
33
+ // Prioritize complete three-arm coverage and failure/resumption before repetitions.
34
+ const probes=schedule.splice(10,2);schedule.splice(4,0,...probes);
35
+ const controller=new AbortController(); const stop=()=>controller.abort();process.on('SIGINT',stop);process.on('SIGTERM',stop);
36
+ try {
37
+ for(const entry of schedule.slice(0,count)) {
38
+ const prior=path.join(ledger,entry.id+'.json');
39
+ if(fs.existsSync(prior)) {
40
+ const record=JSON.parse(fs.readFileSync(prior));
41
+ if(record.status==='exited' && record.usage)continue;
42
+ throw new Error('Existing unsuccessful/interrupted slot requires explicit reconciliation; no automatic retry.');
43
+ }
44
+ if(controller.signal.aborted)break;
45
+ // Reserve before setup; a setup failure leaves a slot requiring reconciliation.
46
+ const slot=reserveRun(ledger,entry.id);
47
+ const dir=path.join(worktrees,entry.id), staging=path.join(root,'staging-'+entry.id);
48
+ git(base,['worktree','add','-b',entry.id,dir]);
49
+ const prepared=prepare(entry.caseId,staging);fs.cpSync(staging,dir,{recursive:true});fs.rmSync(staging,{recursive:true});
50
+ let wrapper='';
51
+ if(entry.arm==='devmethod') {initialize({destination:dir,tool:'codex'});wrapper='Use the installed project-foundation skill and only its relevant references. ';}
52
+ if(entry.arm==='bmad') {
53
+ const bmad=path.join(root,'bmad-export');
54
+ if(!fs.existsSync(path.join(bmad,'.agents/skills')))throw new Error('Place the reviewed pinned BMAD 6.12.0 export in campaign-root/bmad-export before admission.');
55
+ fs.cpSync(bmad,dir,{recursive:true});wrapper='Use the installed BMAD method and the relevant skill for this bounded task. ';
56
+ }
57
+ const baseline=snapshot(dir); git(dir,['add','.']);git(dir,['-c','user.name=Evaluator','-c','user.email=evaluator@example.invalid','commit','-m','Pinned arm setup']);
58
+ const fixture=fixtures.find(f=>f.id===entry.caseId);
59
+ const prompt=wrapper+(entry.caseId==='B4'?'Run the initial tests and explicitly report their real failure before correcting and resuming. ':'')+fixture.prompt+'\nRespond in English. Stay in this worktree. No network, purchases, external writes, nested agents, commits or publication. Record a concise HANDOFF.md. Stop at the stated task scope.';
60
+ const args=codexArguments({directory:dir,model:'gpt-5.6-sol'});
61
+ const meta={...entry,driverPins:Object.fromEntries(['scripts/run-native-pilot.mjs','scripts/native-host.mjs','scripts/hosts/codex.mjs','scripts/hosts/checks.mjs'].map(p=>[p,hash(fs.readFileSync(path.join(source,p)))])),methodRevision:entry.arm==='none'?'none':entry.arm==='bmad'?'bmad-method@6.12.0':revision,hostVersion:codexVersion,model:'gpt-5.6-sol',effort:'low',prompt,fixtureDigest:hash(JSON.stringify(fixture)),promptDigest:hash(fixture.prompt),baseline,args,timeoutSeconds:120};
62
+ fs.writeFileSync(path.join(evidence,entry.id+'-setup.json'),JSON.stringify(meta,null,2));
63
+ console.log(JSON.stringify({event:'started',id:entry.id}));
64
+ const result=await supervise({command:'codex',args,cwd:dir,prompt,timeoutMs:120000,signal:controller.signal,env});
65
+ fs.writeFileSync(path.join(evidence,entry.id+'.jsonl'),result.stdout);fs.writeFileSync(path.join(evidence,entry.id+'.stderr'),result.stderr);
66
+ let events;try {events=result.stdout.trim().split('\n').filter(Boolean).map(line=>JSON.parse(line));}catch{events=[];}
67
+ const usage=codexUsage(events);let checks;
68
+ try { checks=collect(entry.caseId,dir,baseline,verifyFixture); } catch(error) { checks={error:error.message,testsIntact:false}; }
69
+ const acceptance=Boolean(checks.testsIntact && checks.unauthorizedChanges.length===0 && checks.check.exit===(entry.caseId==='B5'?1:0));
70
+ const record={...entry,status:result.status,exit:result.exit,error:result.error,usage,elapsedSeconds:result.elapsedSeconds,acceptance,checks,
71
+ transcriptSha256:hash(result.stdout),review:'pending; process exit and objective checks do not certify instruction adherence'};
72
+ recordRun(slot,record);
73
+ console.log(JSON.stringify({event:'finished',id:entry.id,status:result.status,acceptance,usage,elapsedSeconds:result.elapsedSeconds}));
74
+ if(result.status!=='exited'||!usage)break;
75
+ }
76
+ }finally{process.removeListener('SIGINT',stop);process.removeListener('SIGTERM',stop);}
@@ -0,0 +1,7 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ const root=process.argv[2];if(!root)throw new Error('Usage: node scripts/summarize-native-pilot.mjs CAMPAIGN_ROOT');
4
+ const runs=fs.readdirSync(path.join(root,'ledger')).filter(n=>n.endsWith('.json')).sort().map(n=>JSON.parse(fs.readFileSync(path.join(root,'ledger',n))));
5
+ const totalTokens=runs.every(r=>r.usage)?runs.reduce((n,r)=>n+r.usage.inputTokens+r.usage.outputTokens,0):null;
6
+ const cells=['none','devmethod','bmad'].map(arm=>{const rs=runs.filter(r=>r.id.startsWith('matched-')&&r.arm===arm);return {arm,planned:3,executed:rs.length,notRun:3-rs.length,objectivePassed:rs.filter(r=>r.acceptance===true).length,objectiveFailed:rs.filter(r=>r.acceptance===false).length};});
7
+ console.log(JSON.stringify({format:1,kind:'bounded-native-pilot',runs:runs.map(({id,status,acceptance,usage,elapsedSeconds,review})=>({id,status,objectiveChecksPassed:acceptance,usage,elapsedSeconds,review})),cells,totalTokens,costUSD:null,limits:{maxRuns:12,timeoutSeconds:120,observedTokenStop:500000},limitation:'Calibration excluded from matched cells. Objective success is not behavioral review or proof of superiority. Unknown cost remains unavailable.'},null,2));