carthagent 0.0.0-stage → 1.0.7

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 (379) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -2
  3. package/bin/ctg.mjs +736 -0
  4. package/docs/budgets.md +72 -0
  5. package/docs/compaction.md +34 -0
  6. package/docs/context-efficiency-plan.md +24 -0
  7. package/docs/context-efficiency-review.md +27 -0
  8. package/docs/context-efficiency.md +49 -0
  9. package/docs/deepseek-jev-benchmark-plan.md +70 -0
  10. package/docs/deepseek-jev-benchmark-repair-plan.md +20 -0
  11. package/docs/deepseek-jev-benchmark-restart.md +9 -0
  12. package/docs/deepseek-jev-benchmark-results.md +77 -0
  13. package/docs/deepseek-jev-benchmark-running.md +60 -0
  14. package/docs/deepseek-jev-benchmark-v3-results.md +41 -0
  15. package/docs/deepseek-jev-benchmark-v4-protocol.md +15 -0
  16. package/docs/deepseek-jev-benchmark-v4-results.md +119 -0
  17. package/docs/deepseek-jev-http400-diagnosis.md +45 -0
  18. package/docs/delivery-review-evidence.md +53 -0
  19. package/docs/delivery-timeout-repair-plan.md +51 -0
  20. package/docs/evaluation-readiness.md +31 -0
  21. package/docs/evaluation.md +70 -0
  22. package/docs/extending.md +354 -0
  23. package/docs/glm-pilot-execution.md +9 -0
  24. package/docs/glm-pilot-plan.md +15 -0
  25. package/docs/glm-pilot-results.md +71 -0
  26. package/docs/graph-context-optimization.md +15 -0
  27. package/docs/jev-repository-pilot-plan.md +25 -0
  28. package/docs/jev-repository-pilot-protocol.md +39 -0
  29. package/docs/jev-repository-pilot-restart.md +7 -0
  30. package/docs/jev-repository-pilot-results.md +79 -0
  31. package/docs/jev.md +32 -0
  32. package/docs/model-registry.md +25 -0
  33. package/docs/native-compatibility.md +117 -0
  34. package/docs/opencode-archive.md +9 -0
  35. package/docs/opencode-migration.md +115 -0
  36. package/docs/opencode-runtime.md +94 -0
  37. package/docs/opencode-workspace.md +31 -0
  38. package/docs/owned-runtime-workflow-registration.md +17 -0
  39. package/docs/plan-advisor-evaluation.md +38 -0
  40. package/docs/planning-startup-fix-plan.md +23 -0
  41. package/docs/planning-startup-fix-review.md +24 -0
  42. package/docs/planning.md +118 -0
  43. package/docs/production-merge-review.md +23 -0
  44. package/docs/quality.md +35 -0
  45. package/docs/repository-benchmark-design.md +111 -0
  46. package/docs/review-assurance.md +9 -0
  47. package/docs/review-experiment-results.md +59 -0
  48. package/docs/tested-planning-roadmap.md +58 -0
  49. package/docs/tested-planning.md +58 -0
  50. package/docs/tui-pickers.md +9 -0
  51. package/docs/webdriver.md +83 -0
  52. package/docs/workflow-modes.md +26 -0
  53. package/docs/workflow.d2 +17 -0
  54. package/docs/workflow.svg +149 -0
  55. package/docs/workspace-coordination.md +9 -0
  56. package/evaluate.mjs +249 -0
  57. package/extensions/codegraph.ts +36 -0
  58. package/extensions/delivery.ts +819 -0
  59. package/extensions/experiential.ts +21 -0
  60. package/extensions/subscriptions.ts +21 -0
  61. package/guidance/profiles/authenticated-web.json +24 -0
  62. package/guidance/profiles/data-visualization.json +25 -0
  63. package/guidance/profiles/database-change.json +24 -0
  64. package/guidance/profiles/external-api.json +24 -0
  65. package/guidance/profiles/transactional-data.json +25 -0
  66. package/guidance/profiles/web-application.json +26 -0
  67. package/lib/acceptance-review.mjs +4 -0
  68. package/lib/browser-bin.mjs +160 -0
  69. package/lib/browser-errors.mjs +54 -0
  70. package/lib/browser.mjs +219 -0
  71. package/lib/budget.mjs +37 -0
  72. package/lib/cdp.mjs +221 -0
  73. package/lib/cloud/client.mjs +275 -0
  74. package/lib/cloud/control-plane.mjs +350 -0
  75. package/lib/cloud/gateway.mjs +380 -0
  76. package/lib/cloud/http.mjs +135 -0
  77. package/lib/cloud/ledger.mjs +215 -0
  78. package/lib/codegraph-query.mjs +60 -0
  79. package/lib/codegraph.mjs +378 -0
  80. package/lib/context-archive.mjs +18 -0
  81. package/lib/delivery-context.mjs +132 -0
  82. package/lib/delivery-guidance.mjs +9 -0
  83. package/lib/delivery-review.mjs +94 -0
  84. package/lib/delivery.mjs +610 -0
  85. package/lib/engine.mjs +137 -0
  86. package/lib/guidance.mjs +71 -0
  87. package/lib/jev-advisor.mjs +47 -0
  88. package/lib/opencode/archive.mjs +122 -0
  89. package/lib/opencode/compatibility.mjs +19 -0
  90. package/lib/opencode/credentials.mjs +205 -0
  91. package/lib/opencode/evaluation-budget-plugin/index.ts +147 -0
  92. package/lib/opencode/evaluation.mjs +242 -0
  93. package/lib/opencode/file-lock.mjs +86 -0
  94. package/lib/opencode/headless.mjs +200 -0
  95. package/lib/opencode/review.mjs +213 -0
  96. package/lib/opencode/runtime.mjs +246 -0
  97. package/lib/opencode/service.mjs +249 -0
  98. package/lib/opencode/session-options.mjs +60 -0
  99. package/lib/opencode/workspace.mjs +133 -0
  100. package/lib/openrouter-jev.mjs +37 -0
  101. package/lib/outcome-contract.mjs +21 -0
  102. package/lib/plan-advisor.mjs +38 -0
  103. package/lib/planning-access.mjs +7 -0
  104. package/lib/planning-advice.mjs +28 -0
  105. package/lib/planning-discovery.mjs +14 -0
  106. package/lib/planning-guide.mjs +34 -0
  107. package/lib/planning-view.mjs +57 -0
  108. package/lib/planning.mjs +155 -0
  109. package/lib/probe-diagnostics.mjs +13 -0
  110. package/lib/providers/experiential.mjs +292 -0
  111. package/lib/providers/model-catalog.json +700 -0
  112. package/lib/providers/model-catalog.mjs +7 -0
  113. package/lib/providers/names.mjs +13 -0
  114. package/lib/providers/subscription-models.mjs +483 -0
  115. package/lib/reports.mjs +80 -0
  116. package/lib/review-state.mjs +49 -0
  117. package/lib/review.mjs +114 -0
  118. package/lib/scenarios.mjs +99 -0
  119. package/lib/ship-auth.mjs +44 -0
  120. package/lib/ship-jev.mjs +59 -0
  121. package/lib/ship-telemetry.mjs +183 -0
  122. package/lib/tui/ansi.mjs +255 -0
  123. package/lib/tui/app.mjs +2445 -0
  124. package/lib/tui/auth.mjs +249 -0
  125. package/lib/tui/carthage.mjs +109 -0
  126. package/lib/tui/clipboard.mjs +122 -0
  127. package/lib/tui/d2.mjs +431 -0
  128. package/lib/tui/feed.mjs +467 -0
  129. package/lib/tui/framing.mjs +80 -0
  130. package/lib/tui/glyphs.mjs +172 -0
  131. package/lib/tui/hash-worker.mjs +11 -0
  132. package/lib/tui/mock-engine.mjs +222 -0
  133. package/lib/tui/models.mjs +73 -0
  134. package/lib/tui/picker.mjs +18 -0
  135. package/lib/tui/theme.mjs +663 -0
  136. package/lib/tui/title.mjs +7 -0
  137. package/lib/update-channel.mjs +62 -0
  138. package/lib/update.mjs +230 -0
  139. package/lib/webdriver.mjs +82 -0
  140. package/lib/workflow.mjs +88 -0
  141. package/lib/workspace.mjs +39 -0
  142. package/metadata.json +8 -0
  143. package/package.json +94 -4
  144. package/plugins/carthagent/index.ts +3 -0
  145. package/plugins/carthagent/package.json +28 -0
  146. package/plugins/carthagent/src/archive-rpc.ts +46 -0
  147. package/plugins/carthagent/src/bootstrap.ts +38 -0
  148. package/plugins/carthagent/src/index.ts +62 -0
  149. package/plugins/carthagent/src/pull-request-rpc.ts +54 -0
  150. package/plugins/carthagent/src/rpc.ts +76 -0
  151. package/plugins/carthagent/src/server/archive-rpc.ts +327 -0
  152. package/plugins/carthagent/src/server/budgets.ts +21 -0
  153. package/plugins/carthagent/src/server/codegraph.ts +46 -0
  154. package/plugins/carthagent/src/server/delivery.ts +488 -0
  155. package/plugins/carthagent/src/server/device-auth.ts +91 -0
  156. package/plugins/carthagent/src/server/native-delivery.ts +26 -0
  157. package/plugins/carthagent/src/server/orchestration.ts +56 -0
  158. package/plugins/carthagent/src/server/policy.ts +24 -0
  159. package/plugins/carthagent/src/server/providers.ts +424 -0
  160. package/plugins/carthagent/src/server/pull-request-rpc.ts +387 -0
  161. package/plugins/carthagent/src/server/session-controls.ts +82 -0
  162. package/plugins/carthagent/src/server/telemetry.ts +92 -0
  163. package/plugins/carthagent/src/server/workspace-rpc.ts +349 -0
  164. package/plugins/carthagent/src/session-rpc.ts +31 -0
  165. package/plugins/carthagent/src/testing.ts +85 -0
  166. package/plugins/carthagent/src/workspace-rpc.ts +133 -0
  167. package/plugins/carthagent/test-plugin/index.ts +4 -0
  168. package/plugins/carthagent/test-plugin/package.json +1 -0
  169. package/plugins/carthagent/tsconfig.json +19 -0
  170. package/plugins/carthagent/tui.ts +1 -0
  171. package/prompts/delivery.md +7 -0
  172. package/public/index.html +983 -0
  173. package/scenarios/cli/grade_cli.py +69 -0
  174. package/scenarios/cli/scenario.json +21 -0
  175. package/scenarios/game/baseline/minecraft.py +193 -0
  176. package/scenarios/game/context.md +1 -0
  177. package/scenarios/game/grade_minecraft_clone.py +603 -0
  178. package/scenarios/game/requirements.txt +2 -0
  179. package/scenarios/game/scenario.json +33 -0
  180. package/server.mjs +341 -0
  181. package/skills/game-development/SKILL.md +36 -0
  182. package/skills/game-development/assets/ursina_starter.py +98 -0
  183. package/skills/game-development/scripts/verify_ursina.py +80 -0
  184. package/skills/software-delivery/SKILL.md +50 -0
  185. package/skills/typescript-delivery/SKILL.md +69 -0
  186. package/tests/acceptance-review.test.mjs +13 -0
  187. package/tests/auth.test.mjs +234 -0
  188. package/tests/bounded-summary.test.mjs +68 -0
  189. package/tests/browser-bin.test.mjs +91 -0
  190. package/tests/browser.test.mjs +96 -0
  191. package/tests/budget-engine.test.mjs +110 -0
  192. package/tests/budget.test.mjs +23 -0
  193. package/tests/cli.test.mjs +245 -0
  194. package/tests/clipboard.test.mjs +79 -0
  195. package/tests/cloud-client.test.mjs +129 -0
  196. package/tests/cloud-control-plane.test.mjs +144 -0
  197. package/tests/cloud-gateway.test.mjs +147 -0
  198. package/tests/cloud-http.test.mjs +102 -0
  199. package/tests/cloud-ledger.test.mjs +75 -0
  200. package/tests/codegraph-query.test.mjs +66 -0
  201. package/tests/codegraph.test.mjs +142 -0
  202. package/tests/delivery-context.test.mjs +66 -0
  203. package/tests/delivery-review.test.mjs +139 -0
  204. package/tests/delivery.test.mjs +343 -0
  205. package/tests/engine-api.mjs +24 -0
  206. package/tests/experiential-extension.test.mjs +37 -0
  207. package/tests/experiential.test.mjs +300 -0
  208. package/tests/extension.test.mjs +921 -0
  209. package/tests/fixtures/archive-plugin/index.ts +10 -0
  210. package/tests/guidance.test.mjs +78 -0
  211. package/tests/model-registry.test.mjs +68 -0
  212. package/tests/models.test.mjs +70 -0
  213. package/tests/openai-idempotency.test.mjs +99 -0
  214. package/tests/opencode-archive-rpc.test.mjs +82 -0
  215. package/tests/opencode-delivery.test.mjs +231 -0
  216. package/tests/opencode-providers.test.mjs +414 -0
  217. package/tests/opencode-runtime.test.mjs +455 -0
  218. package/tests/opencode-session-controls.test.mjs +115 -0
  219. package/tests/opencode-session-options.test.mjs +26 -0
  220. package/tests/opencode-workspace.test.mjs +339 -0
  221. package/tests/outcome-contract.test.mjs +17 -0
  222. package/tests/picker.test.mjs +34 -0
  223. package/tests/plan-advisor.test.mjs +88 -0
  224. package/tests/plan-review-loop.test.mjs +53 -0
  225. package/tests/planning-advice.test.mjs +27 -0
  226. package/tests/planning-contract.test.mjs +65 -0
  227. package/tests/planning-startup.test.mjs +38 -0
  228. package/tests/planning-view.test.mjs +33 -0
  229. package/tests/planning.test.mjs +223 -0
  230. package/tests/probe-diagnostics.test.mjs +21 -0
  231. package/tests/pull-request-rpc.test.mjs +366 -0
  232. package/tests/review-state.test.mjs +32 -0
  233. package/tests/review.test.mjs +127 -0
  234. package/tests/server.test.mjs +252 -0
  235. package/tests/ship-jev.test.mjs +77 -0
  236. package/tests/ship-telemetry.test.mjs +333 -0
  237. package/tests/standard-workflow.test.mjs +28 -0
  238. package/tests/subscription-models.test.mjs +416 -0
  239. package/tests/subscriptions-extension.test.mjs +27 -0
  240. package/tests/terminal-title.test.mjs +13 -0
  241. package/tests/test_grade_minecraft_clone.py +199 -0
  242. package/tests/test_starter.py +78 -0
  243. package/tests/theme.test.mjs +175 -0
  244. package/tests/tui-copy-paste.test.mjs +54 -0
  245. package/tests/tui.test.mjs +808 -0
  246. package/tests/update-channels.test.mjs +129 -0
  247. package/tests/update.test.mjs +175 -0
  248. package/tests/webdriver.test.mjs +31 -0
  249. package/tests/workflow.test.mjs +47 -0
  250. package/tests/workspace-rpc.test.mjs +236 -0
  251. package/tests/workspace.test.mjs +24 -0
  252. package/tools/benchmark/advisor.test.mjs +7 -0
  253. package/tools/benchmark/advisor.ts +27 -0
  254. package/tools/benchmark/campaign.mjs +45 -0
  255. package/tools/benchmark/diagnose-request.mjs +40 -0
  256. package/tools/benchmark/eval-common.mjs +32 -0
  257. package/tools/benchmark/eval-ledger.mjs +22 -0
  258. package/tools/benchmark/eval-shop.mjs +26 -0
  259. package/tools/benchmark/eval-voxel.mjs +20 -0
  260. package/tools/benchmark/evaluate.mjs +24 -0
  261. package/tools/benchmark/evaluators.test.mjs +13 -0
  262. package/tools/benchmark/fixtures/shop/server.mjs +18 -0
  263. package/tools/benchmark/fixtures/shop/ui.html +8 -0
  264. package/tools/benchmark/freeze.mjs +30 -0
  265. package/tools/benchmark/gateway.mjs +91 -0
  266. package/tools/benchmark/gateway.test.mjs +38 -0
  267. package/tools/benchmark/generator.mjs +11 -0
  268. package/tools/benchmark/generator.test.mjs +26 -0
  269. package/tools/benchmark/guard.test.mjs +133 -0
  270. package/tools/benchmark/guard.ts +136 -0
  271. package/tools/benchmark/native-plugin/index.ts +24 -0
  272. package/tools/benchmark/planning-startup.test.mjs +47 -0
  273. package/tools/benchmark/preflight-live.mjs +31 -0
  274. package/tools/benchmark/preflight.mjs +22 -0
  275. package/tools/benchmark/replay-diagnostic.mjs +23 -0
  276. package/tools/benchmark/runner.mjs +141 -0
  277. package/tools/benchmark/runner.test.mjs +93 -0
  278. package/tools/benchmark/runtime.mjs +23 -0
  279. package/tools/benchmark/selftest.mjs +47 -0
  280. package/tools/benchmark/smoke-jev.mjs +5 -0
  281. package/tools/benchmark/tasks.mjs +20 -0
  282. package/tools/benchmark/transport.mjs +65 -0
  283. package/tools/benchmark/transport.test.mjs +44 -0
  284. package/tools/benchmark/upstream-error.mjs +18 -0
  285. package/tools/benchmark/upstream-error.test.mjs +10 -0
  286. package/tools/browser-check.mjs +109 -0
  287. package/tools/check-syntax.mjs +27 -0
  288. package/tools/evaluate-plan-advisor.mjs +20 -0
  289. package/tools/native-fixture.mjs +194 -0
  290. package/tools/native-pty-smoke.py +108 -0
  291. package/tools/native-report-plugin/index.ts +50 -0
  292. package/tools/native-report-plugin/package.json +7 -0
  293. package/tools/patch-compaction.mjs +24 -0
  294. package/tools/patch-model-catalog.mjs +12 -0
  295. package/tools/patch-print-shutdown.mjs +39 -0
  296. package/tools/repo-pilot/evaluator.cjs +32 -0
  297. package/tools/repo-pilot/reference.mjs +43 -0
  298. package/tools/repo-pilot/runner.mjs +85 -0
  299. package/tools/repo-pilot/sandbox-entry.mjs +12 -0
  300. package/tools/repo-pilot/tasks.mjs +20 -0
  301. package/tools/repo-pilot/type-evaluator.cjs +6 -0
  302. package/tools/test-native-controls.mjs +84 -0
  303. package/tools/test-native-delivery-host.mjs +307 -0
  304. package/tools/test-native-evaluation.mjs +46 -0
  305. package/tools/test-native-host.mjs +385 -0
  306. package/tools/test-native-outcomes.mjs +73 -0
  307. package/tools/test-native-package.mjs +70 -0
  308. package/tools/test-native-pty.mjs +20 -0
  309. package/tools/test-native-report.mjs +88 -0
  310. package/tools/test-native-swe-host.mjs +132 -0
  311. package/tools/test-native-swe.mjs +122 -0
  312. package/tools/test-picker-pty.py +45 -0
  313. package/tools/test-python.mjs +29 -0
  314. package/tools/test-webdriver.mjs +54 -0
  315. package/tools/verify-release-version.mjs +37 -0
  316. package/tools/webdriver-check.mjs +52 -0
  317. package/vendor/agent/bounded-summary.mjs +88 -0
  318. package/vendor/agent/chunks/anthropic-messages-VWZZOSJQ.js +6 -0
  319. package/vendor/agent/chunks/anthropic.js +75 -0
  320. package/vendor/agent/chunks/azure-openai-responses-BJLAM6CD.js +2 -0
  321. package/vendor/agent/chunks/bedrock-converse-stream.js +74 -0
  322. package/vendor/agent/chunks/chord-context.js +96 -0
  323. package/vendor/agent/chunks/chunk-4L3WN2XY.js +2 -0
  324. package/vendor/agent/chunks/chunk-6CYCI3EJ.js +12 -0
  325. package/vendor/agent/chunks/chunk-6R7UI4IY.js +2 -0
  326. package/vendor/agent/chunks/chunk-7ZC4UXSL.js +2 -0
  327. package/vendor/agent/chunks/chunk-AXIIZGTV.js +2 -0
  328. package/vendor/agent/chunks/chunk-CLNPYIDP.js +42 -0
  329. package/vendor/agent/chunks/chunk-DRQDKNXA.js +2 -0
  330. package/vendor/agent/chunks/chunk-GMWCTUPB.js +2 -0
  331. package/vendor/agent/chunks/chunk-IDDQWTHI.js +2 -0
  332. package/vendor/agent/chunks/chunk-JVUZSMYM.js +1548 -0
  333. package/vendor/agent/chunks/chunk-L2ZPNLYD.js +2 -0
  334. package/vendor/agent/chunks/chunk-NUHFSC37.js +25 -0
  335. package/vendor/agent/chunks/chunk-OUXLLA64.js +11 -0
  336. package/vendor/agent/chunks/chunk-PGXDEOVV.js +2 -0
  337. package/vendor/agent/chunks/chunk-PJACZCIR.js +2 -0
  338. package/vendor/agent/chunks/chunk-PRRNXB7S.js +2 -0
  339. package/vendor/agent/chunks/chunk-TEPMHNKQ.js +2 -0
  340. package/vendor/agent/chunks/chunk-TRDNDS6A.js +4 -0
  341. package/vendor/agent/chunks/chunk-U6ZMQKGD.js +50 -0
  342. package/vendor/agent/chunks/chunk-UAQELI3K.js +2 -0
  343. package/vendor/agent/chunks/chunk-USJZMAIR.js +2 -0
  344. package/vendor/agent/chunks/chunk-XDUWOHPL.js +6 -0
  345. package/vendor/agent/chunks/chunk-XNGRGP62.js +2 -0
  346. package/vendor/agent/chunks/github-copilot.js +2 -0
  347. package/vendor/agent/chunks/google-generative-ai-Q2K7FBDI.js +2 -0
  348. package/vendor/agent/chunks/google-vertex-7UJBF4S7.js +2 -0
  349. package/vendor/agent/chunks/https-proxy-agent-2VXB7436.js +13 -0
  350. package/vendor/agent/chunks/image-resize-worker.js +2 -0
  351. package/vendor/agent/chunks/kimi-coding.js +2 -0
  352. package/vendor/agent/chunks/lib-HGQMNYVT.js +9 -0
  353. package/vendor/agent/chunks/mistral-conversations-4M6BZBX6.js +5 -0
  354. package/vendor/agent/chunks/multipart-parser-SQ6GDJL7.js +3 -0
  355. package/vendor/agent/chunks/node-KG362ECQ.js +16 -0
  356. package/vendor/agent/chunks/node-TWVVAGPO.js +2 -0
  357. package/vendor/agent/chunks/openai-codex-responses-R6VYZTVY.js +10 -0
  358. package/vendor/agent/chunks/openai-codex.js +75 -0
  359. package/vendor/agent/chunks/openai-completions-EKZT2IH2.js +7 -0
  360. package/vendor/agent/chunks/openai-responses-TFDINO6W.js +2 -0
  361. package/vendor/agent/chunks/openrouter-images-N4XVVFSD.js +2 -0
  362. package/vendor/agent/chunks/openrouter.js +75 -0
  363. package/vendor/agent/chunks/pi-messages-PML4SPVJ.js +8 -0
  364. package/vendor/agent/chunks/radius.js +75 -0
  365. package/vendor/agent/chunks/src-UK77SYZE.js +4 -0
  366. package/vendor/agent/chunks/xai.js +2 -0
  367. package/vendor/agent/cli.js +3 -0
  368. package/vendor/agent/dist/modes/interactive/theme/dark.json +91 -0
  369. package/vendor/agent/dist/modes/interactive/theme/light.json +90 -0
  370. package/vendor/agent/dist/modes/interactive/theme/theme-schema.json +356 -0
  371. package/vendor/agent/index.js +2 -0
  372. package/vendor/agent/modes/interactive/theme/dark.json +91 -0
  373. package/vendor/agent/modes/interactive/theme/light.json +90 -0
  374. package/vendor/agent/modes/interactive/theme/theme-schema.json +356 -0
  375. package/vendor/agent/package.json +14 -0
  376. package/vendor/agent/rpc-entry.js +3 -0
  377. package/vendor/agent/theme/dark.json +91 -0
  378. package/vendor/agent/theme/light.json +90 -0
  379. package/vendor/agent/theme/theme-schema.json +356 -0
package/server.mjs ADDED
@@ -0,0 +1,341 @@
1
+ import express from 'express';
2
+ import { planningView } from './lib/planning-view.mjs';
3
+ import { validatePlanning, inspectPlanning, recordPlanReview, approvePlanning, planningStatus, startImplementation } from './lib/planning.mjs';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
8
+ import {
9
+ fingerprint,
10
+ evidenceIdentity,
11
+ validatePlan,
12
+ planD2,
13
+ bindRequiredChecks,
14
+ pendingChecks,
15
+ runCommand,
16
+ createSerialQueue,
17
+ revisePlan,
18
+ completionIssues,
19
+ verificationMode
20
+ } from './lib/delivery.mjs';
21
+ import { captureDeliveryReview, recordDeliveryReview } from './lib/delivery-review.mjs';
22
+ import { mergePlanPatch } from './lib/delivery.mjs';
23
+ import { compareVersions, latestReleaseCached, packageVersion, updateCheckEnabled } from './lib/update.mjs';
24
+ import { lockWorkspace, selectReport } from './lib/workspace.mjs';
25
+ import { createReport, latestReport, saveReport } from './lib/reports.mjs';
26
+
27
+ const root = dirname(fileURLToPath(import.meta.url));
28
+ const cwd = resolve(process.env.CARTHAGENT_WORKSPACE || process.cwd());
29
+ const port = Number(process.env.CARTHAGENT_PORT) || 3000;
30
+ const token = process.env.CARTHAGENT_SERVER_TOKEN || randomBytes(24).toString('hex');
31
+ const app = express();
32
+ const enqueue = createSerialQueue();
33
+ // Include queued requests: their check definitions belong to the active plan.
34
+ let pendingRuns = 0;
35
+ let active = latestReport(cwd);
36
+ let currentState = active?.state || { version: 1, status: 'idle', plan: null, evidence: {}, review: '', launch: '', limitations: [] };
37
+
38
+ // Resolved once at startup (the shared cache throttles fetches to ~1/day) and
39
+ // surfaced on /api/status so the dashboard can badge an available update.
40
+ let updateState = null;
41
+ if (updateCheckEnabled()) {
42
+ latestReleaseCached().then(({ release }) => {
43
+ const installed = packageVersion();
44
+ let available = false;
45
+ try { available = Boolean(installed && release && compareVersions(installed, release.version) < 0); }
46
+ catch { /* non-semver comparison input */ }
47
+ updateState = { installed, latest: release?.version || installed, available, tag: release?.tag, note: release?.note };
48
+ }).catch(() => { /* update check is best-effort */ });
49
+ }
50
+
51
+ app.disable('x-powered-by');
52
+ app.use((req, res, next) => {
53
+ res.setHeader('content-security-policy', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'");
54
+ res.setHeader('referrer-policy', 'no-referrer');
55
+ res.setHeader('x-content-type-options', 'nosniff');
56
+ if (req.path.startsWith('/api/')) res.setHeader('cache-control', 'no-store');
57
+ next();
58
+ });
59
+ app.use(express.json({ limit: '1mb' }));
60
+ app.use('/api', (req, res, next) => {
61
+ const supplied = req.get('x-carthagent-token') || '';
62
+ const expected = Buffer.from(token);
63
+ const received = Buffer.from(supplied);
64
+ if (expected.length !== received.length || !timingSafeEqual(expected, received)) return res.status(401).json({ error: 'Unauthorized' });
65
+ const origin = req.get('origin');
66
+ if (origin && !new Set([`http://127.0.0.1:${port}`, `http://localhost:${port}`]).has(origin)) return res.status(403).json({ error: 'Origin not allowed' });
67
+ next();
68
+ });
69
+
70
+ function refresh() {
71
+ if (pendingRuns) return;
72
+ const found = latestReport(cwd);
73
+ if (found) {
74
+ active = found;
75
+ currentState = found.state;
76
+ }
77
+ }
78
+
79
+ function persist() {
80
+ if (!active) active = createReport(cwd, currentState);
81
+ else saveReport(active.path, currentState);
82
+ active = { ...active, state: currentState, mtimeMs: Date.now() };
83
+ }
84
+
85
+ app.get('/api/status', (req, res) => {
86
+ try {
87
+ refresh();
88
+ const currentFingerprint = fingerprint(cwd, ['.']);
89
+ const pending = currentState.plan ? pendingChecks(currentState, currentFingerprint) : [];
90
+ res.json({
91
+ status: currentState.status,
92
+ runId: currentState.runId,
93
+ revision: currentState.revision,
94
+ completionIssues: currentState.plan ? completionIssues(currentState, cwd, currentFingerprint) : null,
95
+ plan: currentState.plan,
96
+ evidence: currentState.evidence || {},
97
+ stepStatus: currentState.stepStatus || {},
98
+ requirementRevisions: currentState.requirementRevisions || {},
99
+ reviewEvidence: currentState.reviewEvidence || null,
100
+ review: currentState.review || currentState.handoff?.review || '',
101
+ launch: currentState.launch || currentState.handoff?.launch || '',
102
+ limitations: currentState.limitations || currentState.handoff?.limitations || [],
103
+ currentFingerprint,
104
+ planningStatus: planningStatus(currentState, currentFingerprint),
105
+ planning: currentState.planning || null,
106
+ planningView: planningView(currentState, currentFingerprint),
107
+ pendingChecks: pending,
108
+ workspace: cwd,
109
+ report: active?.path || null,
110
+ update: updateState
111
+ });
112
+ } catch (error) {
113
+ res.status(500).json({ error: error.message });
114
+ }
115
+ });
116
+
117
+ app.post('/api/plan/validate', (req, res) => {
118
+ try {
119
+ const plan = validatePlan(req.body.plan, cwd);
120
+ res.json({ valid: true, plan, d2: planD2(plan) });
121
+ } catch (error) {
122
+ res.status(400).json({ valid: false, error: error.message });
123
+ }
124
+ });
125
+
126
+ app.post('/api/plan/d2', (req, res) => {
127
+ try { res.json({ d2: planD2(req.body.plan) }); }
128
+ catch (error) { res.status(400).json({ error: error.message }); }
129
+ });
130
+
131
+ // Mutating endpoints share a cross-process lease for the entire response.
132
+ app.use(['/api/design', '/api/plan/set', '/api/plan/revise', '/api/checks/run', '/api/review', '/api/finish'], (req, res, next) => {
133
+ if (req.method !== 'POST') return next();
134
+ let release;
135
+ try { release = lockWorkspace(cwd); refresh(); }
136
+ catch (error) { release?.(); return res.status(409).json({ error: error.message }); }
137
+ let done = false;
138
+ const finish = () => { if (!done) { done = true; release(); } };
139
+ // Mutations are persisted before handlers serialize their response. Release
140
+ // at that boundary rather than waiting for the socket's `finish` event: a
141
+ // client that has received the response must not race the still-held lease.
142
+ const json = res.json.bind(res);
143
+ res.json = body => { finish(); return json(body); };
144
+ // Do not release on socket close: a disconnected request may still run checks.
145
+ res.once('finish', finish);
146
+ res.locals.releaseWorkspace = finish;
147
+ next();
148
+ });
149
+
150
+ app.post('/api/plan/set', (req, res) => {
151
+ if (pendingRuns) return res.status(409).json({ error: 'Checks are running or queued; wait before replacing the plan' });
152
+ try {
153
+ const ongoing = currentState.plan && !['verified', 'blocked'].includes(currentState.status);
154
+ const required = ongoing ? currentState.plan.checks.filter(c => c.id.startsWith('required_')) : [];
155
+ const plan = validatePlan(bindRequiredChecks({ ...req.body.plan, verification: req.body.plan?.verification ?? 'required' }, required), cwd);
156
+ if (ongoing) {
157
+ currentState = revisePlan(currentState, plan, { cwd, hash: fingerprint(cwd, ['.']), reason: 'Active plan replaced via dashboard' });
158
+ persist();
159
+ } else {
160
+ currentState = {
161
+ version: 1,
162
+ runId: randomUUID(),
163
+ revision: (currentState.revision || 0) + 1,
164
+ status: 'implementing',
165
+ plan,
166
+ evidence: {},
167
+ stepStatus: {},
168
+ createdAt: new Date().toISOString()
169
+ };
170
+ active = createReport(cwd, currentState);
171
+ currentState = active.state;
172
+ }
173
+ selectReport(cwd, active.path);
174
+ res.json({ success: true, plan, d2: planD2(plan), report: active.path });
175
+ } catch (error) {
176
+ res.status(400).json({ error: error.message });
177
+ }
178
+ });
179
+
180
+ app.post('/api/plan/revise', (req, res) => {
181
+ try {
182
+ if (!currentState.plan) throw Error('No active plan');
183
+ const required = currentState.plan.checks.filter(c => c.id.startsWith('required_'));
184
+ const plan = bindRequiredChecks(mergePlanPatch(currentState.plan, req.body.patch), required);
185
+ currentState = revisePlan(currentState, plan, { cwd, hash: fingerprint(cwd, ['.']), reason: req.body.reason });
186
+ persist();
187
+ res.json({ success: true, revision: currentState.revision, report: active.path });
188
+ } catch (error) { res.status(400).json({ error: error.message }); }
189
+ });
190
+
191
+ app.post('/api/plan/bind-required', (req, res) => {
192
+ try {
193
+ const plan = validatePlan(bindRequiredChecks(req.body.plan, req.body.required), cwd);
194
+ res.json({ success: true, plan, d2: planD2(plan) });
195
+ } catch (error) {
196
+ res.status(400).json({ error: error.message });
197
+ }
198
+ });
199
+
200
+ app.post('/api/design', (req, res) => {
201
+ try {
202
+ if (!currentState.plan) throw Error('No active plan');
203
+ const hash = fingerprint(cwd, ['.']);
204
+ let result;
205
+ if (req.body.action === 'validate') result = validatePlanning(currentState, hash);
206
+ else if (req.body.action === 'inspect') result = inspectPlanning(currentState, hash);
207
+ else if (req.body.action === 'review') {
208
+ const review = req.body.review && typeof req.body.review === 'object' ? req.body.review : {};
209
+ for (const field of ['captureId', 'challenges', 'walkthroughs', 'findings', 'limitations', 'resolutions']) {
210
+ if (review[field] == null && req.body[field] !== undefined) review[field] = req.body[field];
211
+ }
212
+ if (review.captureId == null && typeof review.id === 'string' && review.id) review.captureId = review.id;
213
+ result = recordPlanReview(currentState, hash, review);
214
+ }
215
+ else if (req.body.action === 'approve') result = approvePlanning(currentState, hash);
216
+ else throw Error('Unknown design action');
217
+ persist();
218
+ res.json({ result, planningStatus: planningStatus(currentState, hash) });
219
+ } catch (error) { res.status(400).json({ error: error.message }); }
220
+ });
221
+
222
+ app.post('/api/checks/run', async (req, res) => {
223
+ refresh();
224
+ if (!currentState.plan?.checks?.length) return res.status(400).json({ error: 'No active plan' });
225
+ const checks = req.body.id === 'all' ? currentState.plan.checks : currentState.plan.checks.filter(check => check.id === req.body.id);
226
+ if (!checks.length) return res.status(404).json({ error: `Unknown check ID: ${req.body.id}` });
227
+ pendingRuns++;
228
+ try {
229
+ startImplementation(currentState, fingerprint(cwd, ['.']));
230
+ persist();
231
+ selectReport(cwd, active.path);
232
+ const results = await enqueue(async () => {
233
+ const runResults = [];
234
+ const priorStatus = currentState.status;
235
+ currentState.status = 'verifying';
236
+ currentState.verificationStarted = true;
237
+ persist();
238
+ for (const check of checks) {
239
+ const before = fingerprint(cwd, ['.']);
240
+ const result = await runCommand(check.argv, {
241
+ cwd,
242
+ timeoutSeconds: check.timeoutSeconds,
243
+ logPath: join(active.dir, `${check.id}-${randomUUID()}.log`)
244
+ });
245
+ const after = fingerprint(cwd, ['.']);
246
+ const evidence = {
247
+ passed: result.code === 0 && !result.timedOut && !result.cancelled && !result.outputLimit && before === after,
248
+ fingerprint: after,
249
+ ...evidenceIdentity(currentState, check),
250
+ executedRevision: currentState.revision,
251
+ durationMs: result.durationMs,
252
+ code: result.code,
253
+ timedOut: result.timedOut,
254
+ cancelled: result.cancelled,
255
+ outputLimit: result.outputLimit,
256
+ changedDuringCheck: before !== after,
257
+ outputTail: result.output.slice(-1200),
258
+ logPath: result.logPath,
259
+ timestamp: new Date().toISOString()
260
+ };
261
+ currentState.evidence[check.id] = evidence;
262
+ persist();
263
+ runResults.push({ id: check.id, ...evidence, output: result.output });
264
+ }
265
+ const complete = Object.values(completionIssues(currentState, cwd, fingerprint(cwd, ['.']))).every(items => items.length === 0);
266
+ currentState.status = priorStatus === 'verified' && complete ? 'verified' : 'implementing';
267
+ persist();
268
+ return runResults;
269
+ });
270
+ const currentFingerprint = fingerprint(cwd, ['.']);
271
+ res.json({ results, pendingChecks: pendingChecks(currentState, currentFingerprint), currentFingerprint });
272
+ } catch (error) {
273
+ res.status(500).json({ error: error.message });
274
+ } finally {
275
+ pendingRuns--;
276
+ res.locals.releaseWorkspace?.();
277
+ }
278
+ });
279
+
280
+ app.post('/api/review', async (req, res) => {
281
+ try {
282
+ if (!currentState.plan) throw Error('No active plan');
283
+ let result;
284
+ if (req.body.action === 'inspect') result = await captureDeliveryReview(currentState, cwd, active.dir);
285
+ else if (req.body.action === 'record') result = recordDeliveryReview(currentState, cwd, req.body);
286
+ else throw Error('Review action must be inspect or record');
287
+ persist();
288
+ res.json({ success: true, result });
289
+ } catch (error) { res.status(400).json({ error: error.message }); }
290
+ finally { res.locals.releaseWorkspace?.(); }
291
+ });
292
+
293
+ app.post('/api/finish', (req, res) => {
294
+ if (pendingRuns) return res.status(409).json({ error: 'Checks are running or queued; wait before finishing' });
295
+ try {
296
+ refresh();
297
+ const { status, review, launch, limitations } = req.body;
298
+ if (!['verified', 'blocked'].includes(status)) return res.status(400).json({ error: 'Status must be verified or blocked' });
299
+ if (!currentState.plan) return res.status(400).json({ error: 'No active plan' });
300
+ if (status === 'verified') {
301
+ const issues = completionIssues(currentState, cwd, fingerprint(cwd, ['.']));
302
+ if (Object.values(issues).some(items => items.length)) return res.status(400).json({ error: `Cannot verify: ${JSON.stringify(issues)}` });
303
+ } else if (!Array.isArray(limitations) || !limitations.length) {
304
+ return res.status(400).json({ error: 'Blocked status requires a reason' });
305
+ }
306
+ currentState.status = status;
307
+ currentState.review = review || '';
308
+ currentState.launch = launch || '';
309
+ currentState.limitations = Array.isArray(limitations) ? limitations : [];
310
+ currentState.handoff = { status, review: currentState.review, launch: currentState.launch, limitations: currentState.limitations, verification: verificationMode(currentState.plan), fingerprint: fingerprint(cwd, ['.']), at: new Date().toISOString(), evidence: structuredClone(currentState.evidence), reviewEvidence: currentState.reviewEvidence ? structuredClone(currentState.reviewEvidence) : null };
311
+ persist();
312
+ res.json({ success: true, state: currentState });
313
+ } catch (error) {
314
+ res.status(400).json({ error: error.message });
315
+ }
316
+ });
317
+
318
+ app.get('/api/docs/:name', (req, res) => {
319
+ const paths = {
320
+ readme: join(root, 'README.md'),
321
+ evaluation: join(root, 'docs', 'evaluation.md'),
322
+ delivery: join(root, 'prompts', 'delivery.md'),
323
+ 'game-development': join(root, 'skills', 'game-development', 'SKILL.md'),
324
+ 'software-delivery': join(root, 'skills', 'software-delivery', 'SKILL.md'),
325
+ 'typescript-delivery': join(root, 'skills', 'typescript-delivery', 'SKILL.md')
326
+ };
327
+ const path = paths[req.params.name];
328
+ if (!path || !existsSync(path)) return res.status(404).json({ error: 'Document not found' });
329
+ res.json({ content: readFileSync(path, 'utf8'), path });
330
+ });
331
+
332
+ app.use(express.static(join(root, 'public')));
333
+ app.use((req, res, next) => {
334
+ if (req.method === 'GET') return res.sendFile(join(root, 'public', 'index.html'));
335
+ next();
336
+ });
337
+
338
+ app.listen(port, '127.0.0.1', () => {
339
+ console.log(`carthagent dashboard: http://127.0.0.1:${port}/?token=${token}`);
340
+ console.log(`workspace: ${cwd}`);
341
+ });
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: game-development
3
+ description: Build or repair games, simulations, and interactive visual applications through phased delivery. Covers engine API probes, asset completeness, frame budgets, deterministic logic tests, real input smoke tests, screenshots, and packaging. Read before implementing a Minecraft/voxel clone or other GUI game.
4
+ ---
5
+ # Phased game delivery
6
+
7
+ ## First 10%: discover and de-risk
8
+ - Inspect entry point, installed engine version/source, assets, platform, and run instructions. Preserve the user's original before a major rewrite; do not overwrite unrelated work.
9
+ - Translate the prompt into 5–10 observable acceptance criteria. Choose a small complete scope, not a long list of half-implemented features. State ordinary assumptions and proceed; ask only if a consequential ambiguity blocks implementation.
10
+ - Create a `delivery_plan` with source + tests + dependency files + docs in artifact roots, acceptance-to-check mappings, and a short vertical-slice sequence. Map every explicit prompt requirement to an acceptance criterion with a real check: a renderer-only contract does not verify a game whose prompt requires movement, collision, mining and saving. It emits D2. Use D2, never Mermaid, for additional flowcharts.
11
+ - Probe uncertain methods/signatures before relying on them. An import test is not a gameplay test. In Ursina, `FirstPersonController.camera_raycast()` is not a supported API; inspect installed code and use supported raycasting or tested grid traversal.
12
+ - In Ursina, callbacks nested inside `main()` are not automatically dispatched. Put `input`/`update` on a live `Entity` or at module scope, and use the same handlers for keyboard input and smoke checks. A correct dispatch shape is `class Runtime(Entity): def input(self, key): ...; def update(self): ...; runtime = Runtime()`.
13
+ - Normal-window movement, mouselook, jump/gravity, and targeting need per-frame runtime behavior, not one-time camera offsets inside `input()`. Prefer the installed `FirstPersonController` for locomotion and use supported `raycast()` from its camera. Lock and read the mouse only in normal-window mode—the offscreen smoke buffer has no pointer API. For a custom controller, consume `mouse.velocity` during `update()`. Spawn at terrain eye height with a clear forward approach and an exposed block still inside interaction reach after moving, so movement is visible and the center-screen public path can then break and place. The controller initially faces positive Z: for terrain centered around zero, start near its negative-Z edge and remain inside its bounds.
14
+
15
+ ## Implement a vertical slice, then deepen it
16
+ 1. Window, camera, one visible surface, one actual input action, bounded automated launch. For Ursina 7, read `assets/ursina_starter.py` beside this skill: it is an executable, API-verified starting slice with real offscreen rendering. Adapt its lifecycle/capture pattern before implementing more systems; do not leave the starter as the finished game. Graphics feasibility is step zero: make the adapted slice render a nonblank frame and pass the external probe **before** writing gameplay code, and re-run the probe after renderer changes — never defer render verification to the end where repair budgets run out.
17
+ 2. Keep pure world/state/physics/persistence logic importable without constructing a window. Avoid module-level `app.run()`; use a main guard and lazy graphical imports.
18
+ 3. Finish the core loop: spawn safely, move, jump, collide, select, interact, pause/resume, recover from falling, quit. Avoid placing blocks inside the player or overlapping occupied cells; limit reach; address negative coordinates and boundary hits.
19
+ 4. Budget rendering: a world dictionary for O(1) lookup; chunk/batched geometry; remove hidden faces; rebuild only affected chunks including neighbors at boundaries. Do not create thousands of UI Buttons or colliders. Measure startup and report real counters; do not claim FPS you didn't measure.
20
+ 5. Supply all assets, generate deterministic assets, or use verified engine resources. Missing texture warnings are defects. Prefer a coherent palette, readable hierarchy, clear selection/target feedback, and a pause overlay over decorative feature sprawl.
21
+ 6. Handle data honestly: atomic saves, schema/version validation, bounded input size, corrupted-save errors without destroying existing data, deterministic seed, round-trip tests.
22
+
23
+ ## Last 25%: verification and repair
24
+ - Pure tests: world invariants, seeded generation, hit/miss/reach, placement rejection, collision and jump/ceiling, persistence round-trip and malformed files, mesh face/winding/count invariants.
25
+ - Integration smoke: instantiate the REAL app, step frames, invoke the SAME input handlers used by keyboard/mouse, assert the resulting world/UI state, close automatically, return nonzero on failure. Exercise break/place, selection, pause/resume, save/load when present and print one line beginning `GAME_SMOKE_RESULT ` followed by JSON containing the observed booleans/counters. An offscreen graphics context still needs a working graphics driver. Never implement --smoke as a separate pure-logic demonstration or draw a substitute screenshot with PIL.
26
+ - A saved PNG's existence is NOT render evidence: smoke must sample the saved image's pixels and require several distinct colors (the starter's `assert_nonblank` is the tested pattern). A smoke that passes while the framebuffer is blank is a defect, not a probe limitation.
27
+ - Diagnose blank frames from evidence, not excuses. A flat window-color frame with one oversized bar means the offscreen UI lens was not sized or the camera is inside/away from geometry: compare your init order with the starter, fix, re-capture. The probe reports real framebuffer dimensions and sampled color counts; "headless environment limitations" is a rationalization those numbers already disprove.
28
+ - For Ursina, declare an additional runtime check using the bundled `scripts/verify_ursina.py` (resolve relative to this SKILL.md): `PYTHON /absolute/skill/path/scripts/verify_ursina.py minecraft.py --screenshot artifacts/probe.png`. This independently requires actual Panda3D startup and a nonblank framebuffer; it rejects fake smoke paths. It is not a full gameplay oracle.
29
+ - Ursina 7 pitfalls confirmed by executable probes: use `color.rgb32` for 0–255 channels (`color.rgb` expects normalized floats); initialize `camera.ui_lens` film size for offscreen buffers; don't set mouse lock/visibility on a GraphicsBuffer; `Entity.material` is a reserved property, not a safe name for a Text label. Ursina mesh winding is opposite mathematical right-handed outward normals. Inspect a rendered frame, not only cross-product unit tests.
30
+ - Capture a deterministic screenshot to `artifacts/`. If image-capable, read it and inspect terrain visibility, composition, text clipping, contrast, overlays, and blank/missing assets. Non-vision models must not claim visual inspection: record the image path and limitation. Screenshot existence is not evidence of good UX.
31
+ - Run checks with `delivery_check`; repair the root cause, add a regression test, and rerun all stale checks after final edits. A timeout, skipped graphics check, empty test suite, or syntax-only check is not a pass.
32
+ - Review adversarially: fresh install, default launch, alternate cwd, boundary input, unavailable assets, failure recovery, and performance. Call `delivery_finish` only with genuine evidence (or explicit blocked status).
33
+ - Deliver: what works, exact setup/run/test commands, controls, evidence paths, and remaining limitations. Never label a prototype a complete Minecraft clone. A renderer-only verified slice with unimplemented prompt gameplay is an incomplete delivery: with budget remaining, finish the required behavior and its checks before delivery_finish.
34
+
35
+ ## Port this to other large software tasks
36
+ Use the same vertical-slice/evidence approach: for web apps, browser interaction tests + screenshots + accessibility; for services, boot/readiness + API/error-path tests; for CLIs, subprocess tests + exit codes + filesystem side effects. Prefer the project's existing stack and tooling. Preserve acceptance state across compaction using the delivery tools, not a long narrative transcript.
@@ -0,0 +1,98 @@
1
+ """Known-working Ursina 7 vertical slice. Extend it; this is NOT a complete game.
2
+ Run: python ursina_starter.py --smoke --screenshot artifacts/starter.png
3
+ """
4
+ import argparse
5
+ from pathlib import Path
6
+
7
+
8
+ def assert_nonblank(image_path, min_colors=3):
9
+ """A saved PNG's existence is NOT render evidence: verify pixel variety.
10
+
11
+ Smoke paths that only assert the file exists pass while the framebuffer is
12
+ blank (flat window color, empty UI). Sample a grid and require distinct colors.
13
+ """
14
+ from PIL import Image
15
+ with Image.open(image_path) as frame:
16
+ rgb = frame.convert('RGB')
17
+ width, height = rgb.size
18
+ colors = {rgb.getpixel((x, y))
19
+ for x in range(0, width, max(1, width // 32))
20
+ for y in range(0, height, max(1, height // 32))}
21
+ if len(colors) < min_colors:
22
+ raise AssertionError(
23
+ f'Framebuffer looks blank: {len(colors)} sampled colors in {image_path} '
24
+ f'(need {min_colors}). The render is defective; do not blame the probe or environment.')
25
+ return len(colors)
26
+
27
+
28
+ def main():
29
+ parser = argparse.ArgumentParser()
30
+ parser.add_argument('--smoke', action='store_true')
31
+ parser.add_argument('--screenshot', default='artifacts/starter.png')
32
+ args = parser.parse_args()
33
+ # All graphical imports and construction are behind the main guard.
34
+ from panda3d.core import Filename, loadPrcFileData
35
+ from ursina import Ursina, Entity, Text, camera, window, color, mouse
36
+ if args.smoke:
37
+ loadPrcFileData('', 'audio-library-name null\nsync-video false')
38
+ app = Ursina(window_type='offscreen' if args.smoke else 'onscreen',
39
+ size=(960, 600), development_mode=False, editor_ui_enabled=False)
40
+ # Offscreen GraphicsBuffer has no requestProperties(): do not set mouse.locked.
41
+ if not args.smoke:
42
+ from ursina.prefabs.first_person_controller import FirstPersonController
43
+ window.fullscreen = False
44
+ mouse.locked = True
45
+ aspect = app.win.getXSize() / app.win.getYSize()
46
+ camera.ui_lens.set_film_size(camera.ui_size * .5 * aspect, camera.ui_size * .5)
47
+ camera.perspective_lens.set_aspect_ratio(aspect)
48
+ window.color = color.rgb32(174, 207, 220)
49
+ ground = Entity(
50
+ model='cube',
51
+ scale=(12, 1, 12),
52
+ y=-1,
53
+ collider='box',
54
+ color=color.rgb32(93, 141, 73),
55
+ )
56
+ Entity(
57
+ model='cube',
58
+ position=(0, 1, 2),
59
+ collider='box',
60
+ color=color.rgb32(178, 124, 76),
61
+ )
62
+ if args.smoke:
63
+ camera.position = (5, 5, -8)
64
+ camera.look_at((0, 0, 0))
65
+ else:
66
+ FirstPersonController(position=(0, 1, -4), speed=5, gravity=.5, jump_height=1.5)
67
+
68
+ class Slice(Entity):
69
+ def __init__(self):
70
+ super().__init__(model='cube', color=color.rgb32(178, 124, 76))
71
+ self.actions = 0
72
+ Text(parent=camera.ui, text='A RUNNABLE SLICE / press 1', x=-.7, y=.43, scale=1)
73
+
74
+ def input(self, key):
75
+ if key == '1':
76
+ self.actions += 1
77
+ self.color = color.rgb32(100, 160, 210)
78
+
79
+ game = Slice()
80
+ if not args.smoke:
81
+ app.run()
82
+ return
83
+ game.input('1')
84
+ assert game.actions == 1
85
+ for _ in range(4):
86
+ app.taskMgr.step()
87
+ app.graphicsEngine.renderFrame()
88
+ screenshot = Path(args.screenshot).resolve()
89
+ screenshot.parent.mkdir(parents=True, exist_ok=True)
90
+ assert app.win.saveScreenshot(Filename.fromOsSpecific(str(screenshot)))
91
+ # Verify the actual pixels, not just that a file was written.
92
+ colors = assert_nonblank(screenshot)
93
+ print(f'NONBLANK_OK {colors} sampled colors in {screenshot}')
94
+ app.destroy()
95
+
96
+
97
+ if __name__ == '__main__':
98
+ main()
@@ -0,0 +1,80 @@
1
+ """External smoke credibility check: require a real renderer and nonblank framebuffer.
2
+
3
+ Usage: python verify_ursina.py minecraft.py --screenshot artifacts/probe.png
4
+ Runs the candidate's --smoke path. This is NOT a sandbox or a gameplay/UX oracle.
5
+ """
6
+ import argparse
7
+ import json
8
+ from pathlib import Path
9
+ import runpy
10
+ import sys
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(description=__doc__)
15
+ parser.add_argument('entry')
16
+ parser.add_argument('--screenshot', default='artifacts/probe.png')
17
+ options = parser.parse_args()
18
+ entry = Path(options.entry).resolve()
19
+ screenshot = Path(options.screenshot).resolve()
20
+ from direct.showbase.ShowBase import ShowBase
21
+ from panda3d.core import PNMImage, Filename
22
+ original_init, original_destroy = ShowBase.__init__, ShowBase.destroy
23
+ instances, captures = [], []
24
+
25
+ def capture(app):
26
+ if getattr(app, 'win', None) is None:
27
+ return
28
+ app.graphicsEngine.renderFrame()
29
+ app.graphicsEngine.renderFrame()
30
+ image = PNMImage()
31
+ if not app.win.getScreenshot(image):
32
+ return
33
+ colors = set()
34
+ for x in range(0, image.getXSize(), max(1, image.getXSize() // 32)):
35
+ for y in range(0, image.getYSize(), max(1, image.getYSize() // 32)):
36
+ pixel = image.getXel(x, y)
37
+ colors.add(tuple(round(float(c) * 255) for c in pixel))
38
+ proof = screenshot.with_name(screenshot.stem + '-framebuffer.png')
39
+ proof.parent.mkdir(parents=True, exist_ok=True)
40
+ image.write(Filename.fromOsSpecific(str(proof)))
41
+ captures.append({'width': image.getXSize(), 'height': image.getYSize(), 'sampled_colors': len(colors), 'framebuffer': str(proof)})
42
+
43
+ def tracked_init(self, *args, **kwargs):
44
+ original_init(self, *args, **kwargs)
45
+ instances.append(self)
46
+
47
+ def tracked_destroy(self, *args, **kwargs):
48
+ capture(self)
49
+ return original_destroy(self, *args, **kwargs)
50
+
51
+ ShowBase.__init__, ShowBase.destroy = tracked_init, tracked_destroy
52
+ sys.path.insert(0, str(entry.parent))
53
+ old_argv = sys.argv
54
+ sys.argv = [str(entry), '--smoke', '--screenshot', str(screenshot)]
55
+ try:
56
+ try:
57
+ runpy.run_path(str(entry), run_name='__main__')
58
+ except SystemExit as error:
59
+ if error.code not in (None, 0):
60
+ raise RuntimeError(f'Candidate smoke failed: {error.code}') from error
61
+ for app in instances:
62
+ capture(app)
63
+ if not instances:
64
+ raise AssertionError('Smoke never instantiated Panda3D ShowBase: fake/headless-only smoke is not renderer evidence')
65
+ if not captures or not any(c['width'] >= 320 and c['height'] >= 200 and c['sampled_colors'] >= 3 for c in captures):
66
+ raise AssertionError(f'No sufficiently sized, nonblank real framebuffer captured. Renderers={len(instances)}, captures={captures}. Step/render frames before destroying the app; smoke must draw the actual game, not an empty window.')
67
+ if not screenshot.is_file():
68
+ raise AssertionError('Candidate did not save the requested screenshot')
69
+ print('URSINA_PROBE_RESULT ' + json.dumps({'status': 'passed', 'renderers': len(instances), 'captures': captures}))
70
+ finally:
71
+ ShowBase.__init__, ShowBase.destroy = original_init, original_destroy
72
+ sys.argv = old_argv
73
+ sys.path.pop(0)
74
+ for app in instances:
75
+ if getattr(app, 'win', None):
76
+ original_destroy(app)
77
+
78
+
79
+ if __name__ == '__main__':
80
+ main()
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: software-delivery
3
+ description: Complete substantial software implementation from one prompt: CLIs, services, web apps and multi-file refactors. Use for acceptance contracts, vertical slices, process-level integration tests, platform/encoding failures, and evidence-backed handoff.
4
+ ---
5
+ # Large-task delivery
6
+
7
+ One user prompt can require many small implementation and verification turns. Prefer complete, testable behavior over generating a large code dump.
8
+
9
+ ## Pipeline
10
+
11
+ 1. **Inspect & identify knowledge.** For structural questions, use `code_nav` definition/symbols/outline, then callers/importers, then targeted source reads. Inspect graph coverage and nextOffset; name-based call matches are candidates, not runtime binding proof. Filter by path/container or paginate. Use grep for strings, routes, SQL and unsupported languages. Inspect nearby tests and dependency manifests, read applicable domain skills, and preserve every observable requirement. Do not exhaustively read the repository before proposing a contract.
12
+
13
+ 2. **Plan.** Use `delivery_plan` before implementation: a few steps, explicit assumptions, source/test/config/documentation roots (usually `["."]`), and 2–4 check suites. Reuse a check suite across acceptance criteria. D2 is emitted automatically; use D2 for other flowcharts.
14
+
15
+ 3. **Pressure-test proportionally.** Follow the selected workflow. Standard mode permits implementation after a valid complete contract; challenge requirements and checks without mandatory architecture paperwork. Strict mode requires `delivery_design` validate → inspect → review → approve with architecture, ports and scenarios; revisions relock it. Bounded Node/npm versions are available through delivery_probe; other APIs can be read. Revisions preserve omitted fields, merge design/workflow object fields and replace supplied arrays. Changed checks require new evidence; never remove obligations to manufacture success.
16
+
17
+ 4. **Develop.** Get one vertical slice running early: CLI command -> output/file; request -> handler -> response; UI action -> state -> visible feedback. Keep logic separable from infrastructure. Mark progress with `delivery_progress` as each step finishes so the plan panel stays current. If development reveals a wrong assumption or a step can't be completed as planned, call `delivery_revise` to adjust — update steps and checks to match reality, but never silently drop original acceptance criteria. Implement remaining requirements, failure behavior, dependency pins, and README commands. Check uncertain APIs by reading installed implementations or running a tiny probe. Don't add tests requiring an uninstalled framework; use the existing framework or standard-library tools.
18
+
19
+ 5. **Verify.** Verify like a user, in a **fresh process with the normal environment**, not just by calling internal functions. Use `delivery_check` with `id="all"`. Keep generated output under `artifacts/` so it doesn't invalidate source fingerprints. Do not edit while checks run. If checks fail, read the quoted output, repair the root cause, and rerun. If a check failure reveals the plan was wrong, adjust the plan first, then re-verify.
20
+
21
+ 6. **Deliver.** Review behavior, failure paths, data integrity, usability, performance and scope. Repair root causes, add regression tests, rerun stale checks, then `delivery_finish`. A blocked report is preferable to a false verified report.
22
+
23
+ ## Adaptive planning across packages
24
+
25
+ Map affected callers, package boundaries, shared configuration and baseline test failures before changing code. Record compatibility invariants, uncertain assumptions and rollback strategy. Keep near-term slices detailed and later work coarse; expand milestones after discoveries rather than predicting every edit up front.
26
+
27
+ Use structured steps `{id,title,dependsOn:[],checks:[]}` when order or evidence matters. Independent branches can proceed separately; dependent steps cannot start before prerequisites finish. Mark steps done only after mapped checks pass on current source. Reopening a prerequisite resets downstream progress. Stable IDs preserve unambiguous progress during revisions; all structured steps must be done before finish.
28
+
29
+ For an intentionally failing regression milestone, use `kind:"regression",checks:[]`; run the focused probe via bash and record its behavioral assertion failure in progress notes. Import/syntax failures are not sufficient. Retain green suites on acceptance and the integration milestone. New/remapped acceptance needs new assertions and new check executions, not just retained old command evidence. Add a regression that fails before the fix. Run focused tests while iterating, then public-path integration tests and existing repository quality gates. `delivery_check id="all"` collects ordinary failures across all requested suites. Use `delivery_review action="inspect"` to capture the real final diff, read it and all listed new files, and inspect unrelated edits and missing consumers. Run additional boundary/metamorphic probes: vary magnitudes, signs, extremes, equivalent representations and order where relevant. After repairs and fresh checks, inspect again and `delivery_review action="record"` with the capture ID, per-criterion concrete assertions, probe commands/results, empty unresolved findings and honest limitations. Structured required plans cannot finish without this snapshot-bound receipt. Claimed review content remains model-authored, not independent proof. See [adaptive planning scenarios](../../docs/planning.md) for examples and assurance limits.
30
+
31
+ ## General pitfalls
32
+
33
+ - Reuse the installed test runner. For isolated TypeScript/framework routes, consider node:test + existing TypeScript transpilation + explicit mocked imports before inventing ESM loaders. Prove one minimal import first. After two failed infrastructure probes, inspect full stderr and change one hypothesis. Run tests directly: output pipelines must not mask the original exit code. Do not upgrade unrelated dependencies or manufacture ambient types merely to obtain a green typecheck.
34
+
35
+ - Test exit codes, stdout/stderr separation, filesystem side effects, paths with spaces, alternate cwd, and repeat invocations. Use subprocess argument arrays; shell quoting is platform-specific.
36
+ - Test non-ASCII text (not just accented Latin) through the actual command with captured stdout. On Windows, stdout may use a legacy code page even when files are UTF-8. Don't make only the test environment UTF-8 and claim the default executable works — either emit ASCII-safe output or configure the application's UTF-8 output explicitly.
37
+ - Reject malformed schema as well as malformed syntax. Avoid coercing booleans to integer IDs. Test duplicate records, missing files, invalid identifiers, whitespace-only values, and idempotency where relevant.
38
+ - Write to a temporary file in the destination directory, flush, then atomically replace. On failure leave the previous bytes untouched and clean up temporary files. Never discard corrupt user data to make a test pass.
39
+ - Import safety: a subprocess should import the module without starting a service/window, parsing unrelated argv, writing files, or printing output. Guard entry points so importing the module doesn't execute the main logic.
40
+
41
+ ## Other task types
42
+
43
+ - Games/GUI: read the game-development skill for real-renderer verification and input smoke tests. Never substitute a hand-drawn image for a renderer screenshot.
44
+ - Web: test real browser actions, empty/loading/error states, keyboard focus and screenshots; do not mistake a dev server's readiness for correct UI.
45
+ - Services: boot/readiness, schema validation, failure status codes, persistence and shutdown; avoid unauthorized live integrations.
46
+ - Refactors: preserve the public contract and existing tests, add a failing regression first, keep changes scoped, examine the final diff.
47
+
48
+ ## Evidence discipline
49
+
50
+ Checks written by the same model can share its blind spots. External behavioral tests and visual inspection are separate evidence. A passing gate proves recorded commands passed on the current project, not that all requirements are objectively satisfied. Never remove assertions, relabel runtime work as static checks, or narrow the contract to achieve a green status. If context compacts, retrieve `delivery_status` and continue from the durable contract.