@wenathlan/devthink 2.0.12 → 2.0.14

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 (444) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/accounts.ts +2087 -0
  3. package/agent.ts +2331 -0
  4. package/antigravity.ts +2139 -0
  5. package/apifreeze.ts +4146 -0
  6. package/auth.ts +642 -0
  7. package/background.ts +16837 -0
  8. package/bridge.ts +2080 -0
  9. package/build.ts +54 -0
  10. package/bun.ts +26 -0
  11. package/capture.ts +1614 -0
  12. package/checksecrets.ts +72 -0
  13. package/cjs.ts +9 -0
  14. package/cli.ts +3500 -0
  15. package/commands.ts +1101 -0
  16. package/companion.ts +450 -0
  17. package/compatibility.ts +37 -0
  18. package/config.ts +3137 -0
  19. package/constants.ts +943 -0
  20. package/core.ts +1473 -0
  21. package/crossbrowser.ts +1359 -0
  22. package/dashdone.ts +605 -0
  23. package/data.ts +943 -0
  24. package/database.ts +133 -0
  25. package/debug.ts +850 -0
  26. package/deno.ts +27 -0
  27. package/devthink.ts +707 -0
  28. package/dist/accounts.d.ts +409 -0
  29. package/dist/agent.d.ts +837 -0
  30. package/dist/antigravity.d.ts +380 -0
  31. package/dist/apifreeze.d.ts +113 -0
  32. package/dist/auth.d.ts +224 -0
  33. package/{bridge.d.ts → dist/bridge.d.ts} +1 -1
  34. package/dist/capture.d.ts +595 -0
  35. package/dist/cjs.d.ts +6 -0
  36. package/{cli.js → dist/cli.js} +2 -2
  37. package/dist/commands.d.ts +310 -0
  38. package/dist/compatibility.d.ts +17 -0
  39. package/dist/config.d.ts +1853 -0
  40. package/dist/constants.d.ts +637 -0
  41. package/dist/core.d.ts +361 -0
  42. package/dist/data.d.ts +330 -0
  43. package/dist/database.d.ts +24 -0
  44. package/dist/debug.d.ts +270 -0
  45. package/dist/engine.d.ts +74 -0
  46. package/dist/environments.d.ts +244 -0
  47. package/dist/evidence.d.ts +277 -0
  48. package/dist/export.d.ts +311 -0
  49. package/dist/fingerprint.d.ts +594 -0
  50. package/dist/flow.d.ts +300 -0
  51. package/dist/gates.d.ts +103 -0
  52. package/dist/identity.d.ts +51 -0
  53. package/dist/ids.d.ts +7 -0
  54. package/dist/llm.d.ts +652 -0
  55. package/dist/models.d.ts +1807 -0
  56. package/dist/net.d.ts +325 -0
  57. package/dist/neutral.d.ts +41 -0
  58. package/dist/oauth.d.ts +703 -0
  59. package/dist/page.d.ts +1166 -0
  60. package/dist/perf.d.ts +481 -0
  61. package/dist/plan.d.ts +134 -0
  62. package/dist/project.d.ts +395 -0
  63. package/dist/providers.d.ts +37 -0
  64. package/dist/quota.d.ts +453 -0
  65. package/dist/recovery.d.ts +621 -0
  66. package/dist/request.d.ts +1492 -0
  67. package/dist/run.d.ts +779 -0
  68. package/dist/runtime.d.ts +212 -0
  69. package/dist/search.d.ts +375 -0
  70. package/dist/security.d.ts +534 -0
  71. package/dist/serve.d.ts +136 -0
  72. package/dist/session.d.ts +296 -0
  73. package/dist/storage.d.ts +53 -0
  74. package/dist/streaming.d.ts +1119 -0
  75. package/dist/swarm.d.ts +328 -0
  76. package/dist/tools.d.ts +170 -0
  77. package/dist/types.d.ts +8357 -0
  78. package/dist/utils.d.ts +82 -0
  79. package/dist/version.d.ts +7 -0
  80. package/dist/versionregistry.d.ts +406 -0
  81. package/dist/views.d.ts +579 -0
  82. package/dist/workbenchsession.d.ts +69 -0
  83. package/dist/workflow.d.ts +649 -0
  84. package/docs/readme.md +92 -0
  85. package/docs/schemas/antigravity.json +192 -0
  86. package/engine.ts +1524 -0
  87. package/environments.ts +1005 -0
  88. package/evidence.ts +643 -0
  89. package/export.ts +889 -0
  90. package/fingerprint.ts +1458 -0
  91. package/flow.ts +1190 -0
  92. package/gates.ts +307 -0
  93. package/gateway.ts +34 -0
  94. package/hardening.ts +191 -0
  95. package/headless.ts +369 -0
  96. package/http.ts +2110 -0
  97. package/identity.ts +246 -0
  98. package/ids.ts +26 -0
  99. package/index.ts +369 -0
  100. package/llm.ts +2682 -0
  101. package/mcp.ts +1835 -0
  102. package/{memory.js → memory.ts} +3834 -2310
  103. package/models.ts +3805 -0
  104. package/modes.ts +43 -0
  105. package/net.ts +1214 -0
  106. package/neutral.ts +362 -0
  107. package/node.ts +26 -0
  108. package/oauth.ts +2216 -0
  109. package/offscreen.ts +170 -0
  110. package/pack.ts +956 -0
  111. package/package.json +175 -82
  112. package/page.ts +7824 -0
  113. package/pagebridge.ts +1116 -0
  114. package/perf.ts +957 -0
  115. package/plan.ts +672 -0
  116. package/plugin.ts +76 -0
  117. package/{policy.js → policy.ts} +5073 -4679
  118. package/prisma.config.ts +27 -0
  119. package/progress.ts +1308 -0
  120. package/project.ts +1291 -0
  121. package/protocol.ts +4187 -0
  122. package/providers.ts +246 -0
  123. package/quota.ts +1887 -0
  124. package/recovery.ts +2433 -0
  125. package/request.ts +4482 -0
  126. package/run.ts +2135 -0
  127. package/runtime.ts +526 -0
  128. package/search.ts +1760 -0
  129. package/security.ts +1673 -0
  130. package/serve.ts +419 -0
  131. package/server.ts +5538 -0
  132. package/session.ts +1162 -0
  133. package/storage.ts +182 -0
  134. package/streaming.ts +4101 -0
  135. package/swarm.ts +884 -0
  136. package/sync.ts +56 -0
  137. package/tests/agentcert.test.ts +128 -0
  138. package/tests/agentfleet.test.ts +214 -0
  139. package/tests/agentreview.test.ts +241 -0
  140. package/tests/agentstream.test.ts +335 -0
  141. package/tests/agentwork.test.ts +356 -0
  142. package/tests/apifreeze.test.ts +290 -0
  143. package/tests/apimap.test.ts +112 -0
  144. package/tests/approvalgate.test.ts +175 -0
  145. package/tests/artifactmanifest.test.ts +204 -0
  146. package/tests/attentionfeed.test.ts +163 -0
  147. package/tests/backgroundruns.test.ts +100 -0
  148. package/tests/batchscheduling.test.ts +131 -0
  149. package/tests/blackboard.test.ts +238 -0
  150. package/tests/bridge.test.ts +1088 -0
  151. package/tests/browsercoverage.test.ts +134 -0
  152. package/tests/browserpolyfills.test.ts +124 -0
  153. package/tests/build.mjs +1260 -0
  154. package/tests/clientauth.test.ts +223 -0
  155. package/tests/clitools.test.ts +578 -0
  156. package/tests/confirmgates.test.ts +190 -0
  157. package/tests/containerpack.test.ts +143 -0
  158. package/tests/controlflow.test.ts +2120 -0
  159. package/tests/coordination.test.ts +345 -0
  160. package/tests/copyscreen.test.ts +139 -0
  161. package/tests/core.test.ts +417 -0
  162. package/tests/costcert.test.ts +95 -0
  163. package/tests/crossbrowser.test.ts +115 -0
  164. package/tests/cspaudit.test.ts +79 -0
  165. package/tests/dashdone.test.ts +615 -0
  166. package/tests/datagrid.test.ts +137 -0
  167. package/tests/doccheck.test.ts +107 -0
  168. package/tests/dombatch.test.ts +109 -0
  169. package/tests/emulation.test.ts +282 -0
  170. package/tests/environments.test.ts +259 -0
  171. package/tests/evidenceviews.test.ts +88 -0
  172. package/tests/exporttools.test.ts +185 -0
  173. package/tests/firefoxprep.test.ts +134 -0
  174. package/tests/flowlibrary.test.ts +242 -0
  175. package/tests/flowrun.test.ts +303 -0
  176. package/tests/forensics.test.ts +439 -0
  177. package/tests/gateway.test.ts +939 -0
  178. package/tests/headlesslib.test.ts +171 -0
  179. package/tests/httpstream.test.ts +277 -0
  180. package/tests/immutablelog.test.ts +157 -0
  181. package/tests/inboundguard.test.ts +177 -0
  182. package/tests/lazyload.test.ts +72 -0
  183. package/tests/librarymodes.test.ts +623 -0
  184. package/tests/llm.test.ts +696 -0
  185. package/tests/manifest.test.ts +226 -0
  186. package/tests/maskinputs.test.ts +150 -0
  187. package/tests/matrixverify.test.ts +111 -0
  188. package/tests/mavenpack.test.ts +75 -0
  189. package/tests/mcpserver.test.ts +704 -0
  190. package/tests/memory.test.ts +4155 -0
  191. package/tests/memorycare.test.ts +230 -0
  192. package/tests/migrateplan.test.ts +349 -0
  193. package/tests/minimization.test.ts +414 -0
  194. package/tests/modelroute.test.ts +112 -0
  195. package/tests/multiagent.test.ts +440 -0
  196. package/tests/nativebridge.test.ts +807 -0
  197. package/tests/navigation.test.ts +284 -0
  198. package/tests/nugetpack.test.ts +83 -0
  199. package/tests/orchestration.test.ts +449 -0
  200. package/tests/originpolicy.test.ts +405 -0
  201. package/tests/outputcompare.test.ts +97 -0
  202. package/tests/package.test.ts +91 -0
  203. package/tests/pagebridge.test.ts +5909 -0
  204. package/tests/pentest.test.ts +75 -0
  205. package/tests/perfrecords.test.ts +257 -0
  206. package/tests/permdiff.test.ts +85 -0
  207. package/tests/phishguard.test.ts +128 -0
  208. package/tests/pickerviews.test.ts +155 -0
  209. package/tests/pipelines.test.ts +381 -0
  210. package/tests/planlint.test.ts +240 -0
  211. package/tests/planreview.test.ts +383 -0
  212. package/tests/platformtargets.test.ts +56 -0
  213. package/tests/policy.test.ts +8284 -0
  214. package/tests/portability.test.ts +96 -0
  215. package/tests/progress.test.ts +1036 -0
  216. package/tests/promptlibrary.test.ts +116 -0
  217. package/tests/protocol.test.ts +4793 -0
  218. package/tests/publishingpipeline.test.ts +314 -0
  219. package/tests/quickactions.test.ts +164 -0
  220. package/tests/readiness.test.ts +78 -0
  221. package/tests/recipes.test.ts +163 -0
  222. package/tests/redactshots.test.ts +216 -0
  223. package/tests/registernvidiakeys.ts +51 -0
  224. package/tests/relayserve.test.ts +304 -0
  225. package/tests/releasenotes.test.ts +276 -0
  226. package/tests/resilience.test.ts +467 -0
  227. package/tests/resourceaware.test.ts +187 -0
  228. package/tests/runbudget.test.ts +125 -0
  229. package/tests/runhistory.test.ts +299 -0
  230. package/tests/runreplay.test.ts +113 -0
  231. package/tests/runresume.test.ts +184 -0
  232. package/tests/runstate.test.ts +288 -0
  233. package/tests/runtimeadapters.test.ts +251 -0
  234. package/tests/safariskeleton.test.ts +115 -0
  235. package/tests/sandboxframe.test.ts +196 -0
  236. package/tests/sbom.test.ts +105 -0
  237. package/tests/secretvault.test.ts +188 -0
  238. package/tests/selcache.test.ts +111 -0
  239. package/tests/sessioninterface.test.ts +791 -0
  240. package/tests/sessions.test.ts +591 -0
  241. package/tests/siteprefs.test.ts +228 -0
  242. package/tests/snapshotdelta.test.ts +165 -0
  243. package/tests/statusviews.test.ts +237 -0
  244. package/tests/stepmeter.test.ts +200 -0
  245. package/tests/streamparse.test.ts +163 -0
  246. package/tests/stylepruning.test.ts +90 -0
  247. package/tests/surfaces.test.ts +282 -0
  248. package/tests/sweep.test.ts +78 -0
  249. package/tests/syncbridge.test.ts +164 -0
  250. package/tests/taskqueue.test.ts +206 -0
  251. package/tests/toolcalls.test.ts +336 -0
  252. package/tests/toolcatalog.test.ts +201 -0
  253. package/tests/torture-agent.test.ts +706 -0
  254. package/tests/torture-auth.test.ts +461 -0
  255. package/tests/torture-commands.test.ts +895 -0
  256. package/tests/torture-data.test.ts +923 -0
  257. package/tests/torture-environments.test.ts +285 -0
  258. package/tests/torture-export.test.ts +263 -0
  259. package/tests/torture-http.test.ts +1379 -0
  260. package/tests/torture-llm.test.ts +808 -0
  261. package/tests/torture-memory.test.ts +1084 -0
  262. package/tests/torture-plan.test.ts +829 -0
  263. package/tests/torture-run.test.ts +626 -0
  264. package/tests/torture-session.test.ts +928 -0
  265. package/tests/torture-swarm.test.ts +1245 -0
  266. package/tests/torture-tools.test.ts +964 -0
  267. package/tests/torture-workflow.test.ts +2354 -0
  268. package/tests/tourviews.test.ts +52 -0
  269. package/tests/transparency.test.ts +147 -0
  270. package/tests/trigger.test.ts +941 -0
  271. package/tests/v1sunset.test.ts +428 -0
  272. package/tests/virtlist.test.ts +94 -0
  273. package/tests/vision.test.ts +540 -0
  274. package/tests/vsixpack.test.ts +145 -0
  275. package/tests/webapi.test.ts +523 -0
  276. package/tests/workflow.test.ts +1785 -0
  277. package/tests/workfloweditor.test.ts +759 -0
  278. package/tests/xpipack.test.ts +128 -0
  279. package/tools.ts +734 -0
  280. package/types.ts +9908 -0
  281. package/umd.ts +9 -0
  282. package/utils.ts +249 -0
  283. package/version.ts +8 -0
  284. package/versionregistry.ts +1310 -0
  285. package/views.ts +2153 -0
  286. package/vitest.config.ts +12 -0
  287. package/web/App.tsx +78 -0
  288. package/web/README.md +47 -0
  289. package/web/capacitor.config.ts +46 -0
  290. package/web/const.ts +19 -0
  291. package/web/control.shell.tsx +66 -0
  292. package/web/dashboardpage.ts +554 -0
  293. package/web/db.ts +493 -0
  294. package/web/design.html +541 -0
  295. package/web/gatewayview/Gateway.tsx +345 -0
  296. package/web/gatewayview/config.ts +1042 -0
  297. package/web/gatewayview/definition.ts +174 -0
  298. package/web/icons.ts +15 -0
  299. package/{manifest.json → web/manifest.json} +1 -1
  300. package/web/optionspage.ts +691 -0
  301. package/web/popup.ts +1651 -0
  302. package/web/schema.prisma +176 -0
  303. package/web/sidepanel.ts +7941 -0
  304. package/web/sitemanifest.json +13 -0
  305. package/web/terminal-ui.tsx +622 -0
  306. package/web/transparencypage.ts +541 -0
  307. package/web/vite.config.ts +39 -0
  308. package/web/workspace.ts +23 -0
  309. package/workbenchmemory.ts +104 -0
  310. package/workbenchsession.ts +304 -0
  311. package/workflow.ts +4495 -0
  312. package/bridge.d.ts.map +0 -1
  313. package/bridge.js +0 -1877
  314. package/bun.d.ts.map +0 -1
  315. package/bun.js +0 -52329
  316. package/caps/background.json +0 -712
  317. package/caps/cli.json +0 -360
  318. package/caps/library.json +0 -3611
  319. package/caps/mcp.json +0 -126
  320. package/caps/pagebridge.json +0 -377
  321. package/caps/popup.json +0 -51
  322. package/caps/sidepanel.json +0 -522
  323. package/checksums.txt +0 -142
  324. package/cli.d.ts.map +0 -1
  325. package/companion.d.ts.map +0 -1
  326. package/companion.js +0 -389
  327. package/crossbrowser.d.ts.map +0 -1
  328. package/crossbrowser.js +0 -1191
  329. package/dashdone.d.ts.map +0 -1
  330. package/dashdone.js +0 -368
  331. package/deno.d.ts.map +0 -1
  332. package/deno.js +0 -52325
  333. package/devthink.umd.js +0 -52346
  334. package/fixtures/bun.ts +0 -7
  335. package/fixtures/cjs.js +0 -6
  336. package/fixtures/deno.ts +0 -6
  337. package/fixtures/example-org-pagestate.json +0 -22
  338. package/fixtures/importers/automa-workflow.json +0 -18
  339. package/fixtures/importers/selenium-side.json +0 -20
  340. package/fixtures/importers/tabular-plan.csv +0 -7
  341. package/fixtures/importers/uivision-macro.json +0 -10
  342. package/fixtures/importers/v1-plan.json +0 -13
  343. package/fixtures/mcp-client.mjs +0 -14
  344. package/fixtures/pages/checkout.html +0 -192
  345. package/fixtures/pages/dashboard.html +0 -110
  346. package/fixtures/pages/feed.html +0 -113
  347. package/fixtures/pages/product-grid.html +0 -150
  348. package/fixtures/plans/form-inventory-plan.json +0 -11
  349. package/fixtures/plans/release-notes-plan.json +0 -11
  350. package/fixtures/recipes/agentscompete.json +0 -11
  351. package/fixtures/recipes/agentsforms.json +0 -11
  352. package/fixtures/recipes/agentsmonitor.json +0 -11
  353. package/fixtures/recipes/agentsreview.json +0 -29
  354. package/fixtures/recipes/agentswarm.json +0 -11
  355. package/fixtures/recipes/fillcalendar.json +0 -12
  356. package/fixtures/recipes/fillcheckout.json +0 -13
  357. package/fixtures/recipes/filldependentdropdowns.json +0 -12
  358. package/fixtures/recipes/fillfromcsv.json +0 -13
  359. package/fixtures/recipes/fillloginform.json +0 -12
  360. package/fixtures/recipes/fillmultistepwizard.json +0 -13
  361. package/fixtures/recipes/fillregistration.json +0 -12
  362. package/fixtures/recipes/fillsearchfilters.json +0 -12
  363. package/fixtures/recipes/filltypeahead.json +0 -11
  364. package/fixtures/recipes/monitoralerts.json +0 -12
  365. package/fixtures/recipes/monitoravailability.json +0 -11
  366. package/fixtures/recipes/monitornetworkquiet.json +0 -12
  367. package/fixtures/recipes/monitorpagechanges.json +0 -11
  368. package/fixtures/recipes/monitorpricechange.json +0 -11
  369. package/fixtures/recipes/monitorschedule.json +0 -12
  370. package/fixtures/recipes/scrapedynamicprices.json +0 -13
  371. package/fixtures/recipes/scrapeimagegallery.json +0 -12
  372. package/fixtures/recipes/scrapeinfinitefeed.json +0 -13
  373. package/fixtures/recipes/scrapenestedlists.json +0 -12
  374. package/fixtures/recipes/scrapeproductgrid.json +0 -12
  375. package/fixtures/recipes/scrapereviews.json +0 -12
  376. package/fixtures/recipes/scrapesearchresults.json +0 -12
  377. package/fixtures/recipes/scrapetablepagination.json +0 -12
  378. package/fixtures/recipes/testaccessibility.json +0 -34
  379. package/fixtures/recipes/testconsoleerrors.json +0 -12
  380. package/fixtures/recipes/testdeeplinks.json +0 -12
  381. package/fixtures/recipes/testformvalidation.json +0 -13
  382. package/fixtures/recipes/testlayoutshift.json +0 -12
  383. package/fixtures/recipes/testlinkcheck.json +0 -12
  384. package/fixtures/recipes/testloadtiming.json +0 -12
  385. package/fixtures/recipes/testvisualdiff.json +0 -12
  386. package/fixtures/workflows/changelog-digest-workflow.json +0 -13
  387. package/fixtures/workflows/form-survey-workflow.json +0 -13
  388. package/gallery.json +0 -706
  389. package/hardening.d.ts.map +0 -1
  390. package/hardening.js +0 -188
  391. package/headless.d.ts.map +0 -1
  392. package/headless.js +0 -11522
  393. package/http.d.ts.map +0 -1
  394. package/http.js +0 -2165
  395. package/index.cjs +0 -54682
  396. package/index.d.ts.map +0 -1
  397. package/index.js +0 -52310
  398. package/index.neutral.js +0 -46343
  399. package/mcp.d.ts.map +0 -1
  400. package/mcp.js +0 -20515
  401. package/memory.d.ts.map +0 -1
  402. package/nativehost.template.json +0 -10
  403. package/node.cjs +0 -54686
  404. package/node.d.ts.map +0 -1
  405. package/pack.d.ts.map +0 -1
  406. package/pack.js +0 -805
  407. package/policy.d.ts.map +0 -1
  408. package/progress.d.ts.map +0 -1
  409. package/progress.js +0 -645
  410. package/protocol.d.ts.map +0 -1
  411. package/protocol.js +0 -8334
  412. package/schemas/audit.schema.json +0 -226
  413. package/schemas/capability.schema.json +0 -113
  414. package/schemas/envelope.schema.json +0 -162
  415. package/schemas/memory.schema.json +0 -51
  416. package/schemas/observation.schema.json +0 -113
  417. package/schemas/plan.schema.json +0 -208
  418. package/schemas/progress.schema.json +0 -126
  419. package/schemas/proposal.schema.json +0 -68
  420. package/schemas/session.schema.json +0 -82
  421. package/schemas/tool.schema.json +0 -94
  422. package/server.d.ts.map +0 -1
  423. package/server.js +0 -28877
  424. package/umd-example.html +0 -18
  425. package/umd.d.ts.map +0 -1
  426. /package/{bun.d.ts → dist/bun.d.ts} +0 -0
  427. /package/{cli.d.ts → dist/cli.d.ts} +0 -0
  428. /package/{companion.d.ts → dist/companion.d.ts} +0 -0
  429. /package/{crossbrowser.d.ts → dist/crossbrowser.d.ts} +0 -0
  430. /package/{dashdone.d.ts → dist/dashdone.d.ts} +0 -0
  431. /package/{deno.d.ts → dist/deno.d.ts} +0 -0
  432. /package/{hardening.d.ts → dist/hardening.d.ts} +0 -0
  433. /package/{headless.d.ts → dist/headless.d.ts} +0 -0
  434. /package/{http.d.ts → dist/http.d.ts} +0 -0
  435. /package/{index.d.ts → dist/index.d.ts} +0 -0
  436. /package/{mcp.d.ts → dist/mcp.d.ts} +0 -0
  437. /package/{memory.d.ts → dist/memory.d.ts} +0 -0
  438. /package/{node.d.ts → dist/node.d.ts} +0 -0
  439. /package/{pack.d.ts → dist/pack.d.ts} +0 -0
  440. /package/{policy.d.ts → dist/policy.d.ts} +0 -0
  441. /package/{progress.d.ts → dist/progress.d.ts} +0 -0
  442. /package/{protocol.d.ts → dist/protocol.d.ts} +0 -0
  443. /package/{server.d.ts → dist/server.d.ts} +0 -0
  444. /package/{umd.d.ts → dist/umd.d.ts} +0 -0
@@ -0,0 +1,578 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ bundlesizeaccounting,
4
+ capabilityapireport,
5
+ composeworkflowdocument,
6
+ csphashesof,
7
+ deepmanifestchecks,
8
+ exitclassofrun,
9
+ exitcodeof,
10
+ exportdatacontent,
11
+ exportdatawindow,
12
+ manifestsourceline,
13
+ parseheadlessfixture,
14
+ parseworkflowdocument,
15
+ planlintfindings,
16
+ planrisksummaryof,
17
+ pngdimensions,
18
+ resolvefixture,
19
+ runworkflowsummaryof,
20
+ secretstorerefusal,
21
+ workflowauditlines,
22
+ } from "../cli.js";
23
+ import { headlessfixturestep, headlessprogress } from "../headless.js";
24
+ import type { headlessfixture, planfile, runlogentry, workflowrun } from "../types.js";
25
+
26
+ const now = 1_800_000_000_000;
27
+
28
+ /** Builds one minimal png byte payload whose ihdr header declares the given dimensions. */
29
+ function pngof(width: number, height: number): Uint8Array {
30
+ const bytes = new Uint8Array(24);
31
+ bytes.set([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], 0);
32
+ const view = new DataView(bytes.buffer);
33
+ view.setUint32(16, width);
34
+ view.setUint32(20, height);
35
+ return bytes;
36
+ }
37
+
38
+ /** Builds one reviewed plan file fixture of the example releases page. */
39
+ function planfixture(over: Partial<planfile> = {}): planfile {
40
+ return {
41
+ version: "1.1.80",
42
+ goal: "Read the release notes of the example project",
43
+ origin: "https://example.org",
44
+ grants: ["observe", "readtext", "countelements"],
45
+ steps: [
46
+ { id: "observe", kind: "observe", label: "Observe the release page" },
47
+ { id: "read", kind: "readtext", label: "Read the release notes text", target: "main" },
48
+ { id: "count", kind: "countelements", label: "Count the release sections", target: "main section" },
49
+ ],
50
+ ...over,
51
+ };
52
+ }
53
+
54
+ /** Builds one recorded page state fixture of the example releases page. */
55
+ function fixtureof(over: Partial<headlessfixture> = {}): headlessfixture {
56
+ return {
57
+ id: "example-org-pagestate",
58
+ origin: "https://example.org",
59
+ grants: ["observe", "readtext", "readforms", "inspect", "countelements"],
60
+ observation: {
61
+ schemaversion: 1,
62
+ url: "https://example.org/releases",
63
+ title: "Example releases",
64
+ textpreview: "Release notes of the example project.",
65
+ textlength: 512,
66
+ forms: [{ label: "email", type: "email", name: "email" }],
67
+ interactive: [{ selector: "main a", role: "link", label: "latest release" }],
68
+ capturedat: now,
69
+ },
70
+ recordedat: now,
71
+ ...over,
72
+ };
73
+ }
74
+
75
+ describe("cli deep manifest checks", () => {
76
+ it("verifies every manifest key against the allowlist, the permission sources, the csp hashes, the reviewed web resources, the duplicate permissions, the chrome floor and the icon dimensions", () => {
77
+ const manifesttext = JSON.stringify(
78
+ {
79
+ manifest_version: 3,
80
+ name: "devthink",
81
+ version: "1.1.80",
82
+ description: "The reviewed browser agent.",
83
+ permissions: ["storage", "tabs"],
84
+ optional_permissions: ["downloads", "storage"],
85
+ content_security_policy: { extension_scripts: "script-src 'self' 'sha256-KNOWNHASH=' 'sha256-UNKNOWN='" },
86
+ web_accessible_resources: [{ resources: ["sandbox.html"] }],
87
+ minimum_chrome_version: 88,
88
+ icons: { "16": "icon16.png" },
89
+ rogue_key: true,
90
+ },
91
+ null,
92
+ 2,
93
+ );
94
+ const manifest = JSON.parse(manifesttext) as Record<string, unknown>;
95
+ const bytes: Record<string, Uint8Array> = { "icon16.png": pngof(16, 16), "worker.js": new Uint8Array([1, 2, 3]) };
96
+ const digestof = (payload: Uint8Array): string => (payload.length === 3 ? "KNOWNHASH=" : "other");
97
+ const checked = deepmanifestchecks({
98
+ manifest,
99
+ manifesttext,
100
+ manifestfile: "manifest.json",
101
+ filebytes: bytes,
102
+ capabilities: capabilityapireport(["storage", "tabs", "downloads"]),
103
+ digestof,
104
+ });
105
+ const rules = (rule: string): number => checked.findings.filter((finding) => finding.rule === rule).length;
106
+ expect(rules("manifest.key.allowlist")).toBe(1);
107
+ expect(checked.findings.find((finding) => finding.rule === "manifest.key.allowlist")?.path).toBe("rogue_key");
108
+ expect(rules("manifest.permission.source")).toBe(4);
109
+ expect(
110
+ checked.findings.find(
111
+ (finding) => finding.rule === "manifest.permission.source" && finding.path === "permissions.storage",
112
+ )?.source,
113
+ ).toContain("manifest.json:");
114
+ expect(rules("manifest.permission.duplicate")).toBe(1);
115
+ expect(rules("manifest.permission.nativemessaging")).toBe(0);
116
+ const nativerequired = deepmanifestchecks({
117
+ manifest: {
118
+ manifest_version: 3,
119
+ name: "devthink",
120
+ version: "1.1.85",
121
+ permissions: ["storage", "nativeMessaging"],
122
+ },
123
+ manifesttext: JSON.stringify({ permissions: ["storage", "nativeMessaging"] }),
124
+ manifestfile: "manifest.json",
125
+ filebytes: {},
126
+ capabilities: capabilityapireport(["storage", "nativeMessaging"]),
127
+ digestof: () => "x",
128
+ });
129
+ expect(
130
+ nativerequired.findings.some(
131
+ (finding) =>
132
+ finding.rule === "manifest.permission.nativemessaging" &&
133
+ finding.severity === "error" &&
134
+ finding.path === "permissions.nativeMessaging",
135
+ ),
136
+ ).toBe(true);
137
+ expect(nativerequired.exitcode).toBe(1);
138
+ const nativeoptional = deepmanifestchecks({
139
+ manifest: {
140
+ manifest_version: 3,
141
+ name: "devthink",
142
+ version: "1.1.85",
143
+ permissions: ["storage"],
144
+ optional_permissions: ["nativeMessaging"],
145
+ },
146
+ manifesttext: JSON.stringify({ permissions: ["storage"], optional_permissions: ["nativeMessaging"] }),
147
+ manifestfile: "manifest.json",
148
+ filebytes: {},
149
+ capabilities: capabilityapireport(["storage", "nativeMessaging"]),
150
+ digestof: () => "x",
151
+ });
152
+ expect(
153
+ nativeoptional.findings.some(
154
+ (finding) =>
155
+ finding.rule === "manifest.permission.nativemessaging" &&
156
+ finding.severity === "info" &&
157
+ finding.path === "optional_permissions.nativeMessaging",
158
+ ),
159
+ ).toBe(true);
160
+ expect(nativeoptional.exitcode).toBe(0);
161
+ expect(rules("manifest.csp.hashes")).toBe(2);
162
+ expect(
163
+ checked.findings.some(
164
+ (finding) =>
165
+ finding.rule === "manifest.csp.hashes" &&
166
+ finding.severity === "info" &&
167
+ finding.message.includes("sha256-KNOWNHASH= verifies against worker.js"),
168
+ ),
169
+ ).toBe(true);
170
+ expect(rules("manifest.war.reviewed")).toBe(0);
171
+ expect(rules("manifest.icons")).toBe(1);
172
+ expect(
173
+ checked.findings.some(
174
+ (finding) => finding.rule === "manifest.icons" && finding.message.includes("decodes to 16x16"),
175
+ ),
176
+ ).toBe(true);
177
+ expect(rules("manifest.chromeminimum")).toBe(0);
178
+ expect(checked.exitcode).toBe(1);
179
+ const floored = deepmanifestchecks({
180
+ manifest: {
181
+ manifest_version: 3,
182
+ name: "devthink",
183
+ version: "1.1.80",
184
+ permissions: ["storage"],
185
+ icons: { "16": "icon16.png" },
186
+ minimum_chrome_version: 88,
187
+ },
188
+ manifesttext: JSON.stringify({ permissions: ["storage"] }),
189
+ manifestfile: "manifest.json",
190
+ filebytes: bytes,
191
+ capabilities: capabilityapireport(["storage", "sidePanel"]),
192
+ digestof,
193
+ });
194
+ expect(
195
+ floored.findings.some(
196
+ (finding) =>
197
+ finding.rule === "manifest.chromeminimum" &&
198
+ finding.severity === "error" &&
199
+ finding.message.includes("capability floor 114"),
200
+ ),
201
+ ).toBe(true);
202
+ const badicon = deepmanifestchecks({
203
+ manifest: { icons: { "16": "icon16.png" } },
204
+ manifesttext: "{}",
205
+ manifestfile: "m.json",
206
+ filebytes: { "icon16.png": pngof(32, 32) },
207
+ capabilities: [],
208
+ digestof: () => "x",
209
+ });
210
+ expect(
211
+ badicon.findings.some(
212
+ (finding) =>
213
+ finding.rule === "manifest.icons" &&
214
+ finding.message.includes("decodes to 32x32 while its size key declares 16"),
215
+ ),
216
+ ).toBe(true);
217
+ /* the 2.0.6 fail-soft: a running tree with no bundle at all (an installed library package, an unbuilt checkout) answers the icon family through the release-zip note instead of the absent-bundle error, while a bundle that exists and misses the icon keeps the error */
218
+ const unbundled = deepmanifestchecks({
219
+ manifest: { icons: { "16": "icons/16.png" } },
220
+ manifesttext: "{}",
221
+ manifestfile: "m.json",
222
+ filebytes: {},
223
+ capabilities: [],
224
+ digestof: () => "x",
225
+ bundlepresent: false,
226
+ });
227
+ expect(
228
+ unbundled.findings.some(
229
+ (finding) =>
230
+ finding.rule === "manifest.icons" &&
231
+ finding.severity === "info" &&
232
+ finding.message.includes("rides the browser bundle of the release zip channel"),
233
+ ),
234
+ ).toBe(true);
235
+ expect(unbundled.exitcode).toBe(0);
236
+ const bundledmissing = deepmanifestchecks({
237
+ manifest: { icons: { "16": "icons/16.png" } },
238
+ manifesttext: "{}",
239
+ manifestfile: "m.json",
240
+ filebytes: {},
241
+ capabilities: [],
242
+ digestof: () => "x",
243
+ bundlepresent: true,
244
+ });
245
+ expect(
246
+ bundledmissing.findings.some(
247
+ (finding) =>
248
+ finding.rule === "manifest.icons" &&
249
+ finding.severity === "error" &&
250
+ finding.message.includes("ships no file in the bundle"),
251
+ ),
252
+ ).toBe(true);
253
+ expect(bundledmissing.exitcode).toBe(1);
254
+ const unreviewedresource = deepmanifestchecks({
255
+ manifest: { web_accessible_resources: [{ resources: ["panel.html"] }] },
256
+ manifesttext: "{}",
257
+ manifestfile: "m.json",
258
+ filebytes: {},
259
+ capabilities: [],
260
+ digestof: () => "x",
261
+ });
262
+ expect(
263
+ unreviewedresource.findings.some(
264
+ (finding) =>
265
+ finding.rule === "manifest.war.reviewed" && finding.message.includes("outside the reviewed resource set"),
266
+ ),
267
+ ).toBe(true);
268
+ expect(() => pngdimensions(new Uint8Array(8))).toThrow(/png signature/);
269
+ expect(csphashesof("script-src 'self' 'sha256-ABC=' 'sha256-DEF='")).toEqual(["sha256-ABC=", "sha256-DEF="]);
270
+ expect(manifestsourceline('{\n "storage"\n}', "manifest.json", '"storage"')).toBe("manifest.json:2");
271
+ expect(capabilityapireport(["sidePanel", "offscreen"])).toEqual([
272
+ { api: "sidePanel", minchrome: 114 },
273
+ { api: "offscreen", minchrome: 109 },
274
+ ]);
275
+ expect(capabilityapireport(["nativeMessaging"])).toEqual([{ api: "nativeMessaging", minchrome: 88 }]);
276
+ });
277
+ });
278
+
279
+ describe("cli planlint findings and risk summary", () => {
280
+ it("accepts the reviewed plan while refusing forbidden kinds, broken selectors, missing option fields, capability misses and origins outside the consent allowlist", () => {
281
+ const clean = planlintfindings({
282
+ file: planfixture(),
283
+ capabilities: ["observe", "readtext", "countelements"],
284
+ allowlist: ["https://example.org"],
285
+ });
286
+ expect(clean).toHaveLength(0);
287
+ const broken = planlintfindings({
288
+ file: planfixture({
289
+ steps: [
290
+ { id: "observe", kind: "observe", label: "Observe the release page" },
291
+ { id: "forbidden", kind: "notakind", label: "A forbidden kind" },
292
+ { id: "badtarget", kind: "readtext", label: "Broken target", target: "{not json" },
293
+ { id: "wait", kind: "waitelement", label: "Wait without its bound", target: "main" },
294
+ ],
295
+ }),
296
+ allowlist: ["https://other.example"],
297
+ });
298
+ const codes = broken.map((diagnostic) => diagnostic.code);
299
+ expect(codes).toContain("plan.kind.catalog");
300
+ expect(codes).toContain("plan.selector.grammar");
301
+ expect(codes).toContain("plan.options.required");
302
+ expect(codes).toContain("plan.origin.allowlist");
303
+ expect(broken.find((diagnostic) => diagnostic.code === "plan.options.required")?.message).toContain(
304
+ "waitelement misses the required option field wait",
305
+ );
306
+ const bounded = planlintfindings({
307
+ file: planfixture({
308
+ steps: [
309
+ { id: "wait", kind: "waitelement", label: "Wait for the list", target: "main", options: '{"wait":5000}' },
310
+ ],
311
+ }),
312
+ });
313
+ expect(bounded).toHaveLength(0);
314
+ const capped = planlintfindings({ file: planfixture(), capabilities: ["observe"] });
315
+ expect(capped.filter((diagnostic) => diagnostic.code === "plan.capability.catalog")).toHaveLength(2);
316
+ const risk = planrisksummaryof(planfixture());
317
+ expect(risk).toMatchObject({ read: 3, interaction: 0, sensitive: 0, steps: 3 });
318
+ const mixed = planrisksummaryof(
319
+ planfixture({
320
+ steps: [
321
+ { id: "observe", kind: "observe", label: "Observe" },
322
+ { id: "scroll", kind: "scroll", label: "Scroll the release list", target: "main" },
323
+ { id: "pay", kind: "confirmpay", label: "Confirm the payment", gate: true },
324
+ ],
325
+ }),
326
+ );
327
+ expect(mixed).toMatchObject({ read: 1, interaction: 1, sensitive: 1, steps: 3 });
328
+ });
329
+ });
330
+
331
+ describe("cli runworkflow document, summary and audit trail", () => {
332
+ it("parses the workflow document under schemastrict, composes it through the shared engine and maps the run states onto the documented exit codes", () => {
333
+ const document = parseworkflowdocument({
334
+ version: "1.1.80",
335
+ workflow: {
336
+ name: "changelog digest",
337
+ version: 1,
338
+ origins: ["https://example.org"],
339
+ steps: [
340
+ { id: "observe", kind: "observe", label: "Observe the changelog page" },
341
+ { id: "read", kind: "readtext", label: "Read the changelog text", target: "main" },
342
+ ],
343
+ },
344
+ });
345
+ expect(document.workflow.name).toBe("changelog digest");
346
+ expect(document.workflow.steps).toHaveLength(2);
347
+ const record = composeworkflowdocument(document, now);
348
+ expect(record.steps.map((step) => step.id)).toEqual(["observe", "read"]);
349
+ expect(() =>
350
+ parseworkflowdocument({
351
+ version: "1.1.80",
352
+ workflow: {
353
+ name: "x",
354
+ version: 1,
355
+ origins: ["http://insecure.example"],
356
+ steps: [{ id: "s", kind: "observe", label: "l" }],
357
+ },
358
+ }),
359
+ ).toThrow(/HTTPS origin/);
360
+ expect(() => parseworkflowdocument({ version: "1.1.80", rogue: true })).toThrow(/unknown field rogue/);
361
+ const run: workflowrun = {
362
+ id: "run1",
363
+ workflowid: record.id,
364
+ state: "done",
365
+ cursor: 2,
366
+ startedat: now,
367
+ endedat: now + 500,
368
+ };
369
+ const log: runlogentry[] = [
370
+ {
371
+ stepid: "observe",
372
+ label: "Observe the changelog page",
373
+ state: "done",
374
+ startedat: now,
375
+ duration: 100,
376
+ summary: "The recorded page carried 512 text characters.",
377
+ },
378
+ {
379
+ stepid: "read",
380
+ label: "Read the changelog text",
381
+ state: "done",
382
+ startedat: now + 100,
383
+ duration: 400,
384
+ summary: "The text read the changelog.",
385
+ },
386
+ ];
387
+ const summary = runworkflowsummaryof({ runid: run.id, run, log });
388
+ expect(summary.exitclass).toBe("ok");
389
+ expect(summary.exitcode).toBe(0);
390
+ expect(summary.checkpoint).toBe(2);
391
+ expect(summary.steps).toHaveLength(2);
392
+ expect(summary.steps[1]?.duration).toBe(400);
393
+ const failed = runworkflowsummaryof({
394
+ runid: run.id,
395
+ run: { ...run, state: "failed", failreason: "The changelog list never appeared." },
396
+ log,
397
+ });
398
+ expect(failed.exitclass).toBe("stepfailed");
399
+ expect(failed.exitcode).toBe(2);
400
+ expect(failed.reason).toContain("never appeared");
401
+ const cancelled = runworkflowsummaryof({
402
+ runid: run.id,
403
+ run: { ...run, state: "cancelled", cancelreason: "The operator cancelled the replay." },
404
+ log,
405
+ });
406
+ expect(cancelled.exitclass).toBe("cancelled");
407
+ expect(exitclassofrun({ ...run, state: "paused" })).toBe("schemaerror");
408
+ expect(exitcodeof("consentrefused")).toBe(1);
409
+ expect(exitcodeof("unsupported")).toBe(4);
410
+ expect(() => exitcodeof("mystery")).toThrow(/outside the documented classes/);
411
+ const audit = workflowauditlines({ runid: run.id, workflow: record.id, log, now: now + 600 });
412
+ const lines = audit.split("\n");
413
+ expect(lines).toHaveLength(3);
414
+ expect(JSON.parse(lines[0] ?? "{}")).toMatchObject({ runid: "run1", workflow: record.id, state: "sealed" });
415
+ expect(JSON.parse(lines[1] ?? "{}").stepid).toBe("observe");
416
+ });
417
+ });
418
+
419
+ describe("cli exportdata windows, formats and secret refusal", () => {
420
+ it("filters the records through the from and to window while the shared serializers emit json, csv and markdown and the secret store material refuses the export in full", () => {
421
+ const records: Array<Record<string, unknown>> = [
422
+ { at: now - 5000, url: "https://example.org/a", title: "alpha" },
423
+ { at: now, url: "https://example.org/b", title: "beta" },
424
+ { at: now + 5000, url: "https://example.org/c", title: "gamma" },
425
+ { url: "https://example.org/undated", title: "undated" },
426
+ ];
427
+ const windowed = exportdatawindow(records, now - 1000, now + 1000);
428
+ expect(windowed.map((record) => record.title)).toEqual(["beta", "undated"]);
429
+ expect(exportdatawindow(records, undefined, now - 1000).map((record) => record.title)).toEqual([
430
+ "alpha",
431
+ "undated",
432
+ ]);
433
+ const jsonexport = exportdatacontent({
434
+ scope: "session",
435
+ format: "json",
436
+ records: records.slice(0, 2),
437
+ shapes: [],
438
+ });
439
+ expect(jsonexport.result.rows).toBe(2);
440
+ expect(jsonexport.result.bytes).toBe(jsonexport.content.length);
441
+ expect(jsonexport.result.path).toBeUndefined();
442
+ expect(JSON.parse(jsonexport.content)).toHaveLength(2);
443
+ const jsonl = exportdatacontent({ scope: "audit", format: "jsonl", records: records.slice(0, 2), shapes: [] });
444
+ expect(jsonl.content.split("\n")).toHaveLength(2);
445
+ const csvexport = exportdatacontent({
446
+ scope: "extraction",
447
+ format: "csv",
448
+ records: [{ title: "alpha", at: now }],
449
+ shapes: [],
450
+ path: "out.csv",
451
+ });
452
+ expect(csvexport.content.split("\n")[0]).toBe("at,title");
453
+ expect(csvexport.result.path).toBe("out.csv");
454
+ const markdown = exportdatacontent({
455
+ scope: "extraction",
456
+ format: "markdown",
457
+ records: [{ title: "alpha", at: now }],
458
+ shapes: [],
459
+ });
460
+ expect(markdown.content).toContain("| at | title |");
461
+ const vaultrefusal = secretstorerefusal([{ vaultid: "v1", title: "x" }, { apitoken: "ghp_rawtoken" }]);
462
+ expect(vaultrefusal.refused).toBe(true);
463
+ expect(vaultrefusal.fields).toEqual(["apitoken", "vaultid"]);
464
+ expect(vaultrefusal.reason).toContain("never ships through an export");
465
+ expect(secretstorerefusal([{ apitoken: "value [redacted] by the mask" }]).refused).toBe(false);
466
+ const refusedexport = exportdatacontent({
467
+ scope: "session",
468
+ format: "json",
469
+ records: [{ vaultid: "v1" }],
470
+ shapes: [],
471
+ });
472
+ expect(refusedexport.result.bytes).toBe(0);
473
+ expect(refusedexport.result.rows).toBe(0);
474
+ expect(refusedexport.result.reason).toContain("refuses the secret store material");
475
+ expect(refusedexport.content).toBe("");
476
+ });
477
+ });
478
+
479
+ describe("cli headlessmode fixture replay", () => {
480
+ it("parses the recorded page state under schemastrict while the consent gate serves the granted read only vocabulary, refuses the wider kinds and reports the kinds a fixture cannot satisfy", () => {
481
+ const fixture = fixtureof();
482
+ const parsed = parseheadlessfixture({
483
+ id: fixture.id,
484
+ origin: fixture.origin,
485
+ grants: fixture.grants,
486
+ observation: fixture.observation,
487
+ recordedat: fixture.recordedat,
488
+ });
489
+ expect(parsed.origin).toBe("https://example.org");
490
+ expect(() =>
491
+ parseheadlessfixture({
492
+ id: fixture.id,
493
+ origin: "http://insecure.example",
494
+ observation: fixture.observation,
495
+ grants: [],
496
+ recordedat: now,
497
+ }),
498
+ ).toThrow(/HTTPS origin/);
499
+ expect(() =>
500
+ parseheadlessfixture({
501
+ id: fixture.id,
502
+ origin: fixture.origin,
503
+ observation: { schemaversion: 1 },
504
+ grants: [],
505
+ recordedat: now,
506
+ }),
507
+ ).toThrow(/misses its/);
508
+ expect(() =>
509
+ parseheadlessfixture({
510
+ id: fixture.id,
511
+ origin: fixture.origin,
512
+ observation: fixture.observation,
513
+ grants: [],
514
+ recordedat: now,
515
+ extra: true,
516
+ }),
517
+ ).toThrow(/unknown field extra/);
518
+ expect(resolvefixture([fixture], "https://example.org").id).toBe(fixture.id);
519
+ expect(() => resolvefixture([fixture], "https://other.example")).toThrow(/No recorded fixture covers the origin/);
520
+ const observed = headlessfixturestep(fixture, {
521
+ id: "observe",
522
+ kind: "observe",
523
+ label: "Observe the release page",
524
+ });
525
+ expect(observed.state).toBe("done");
526
+ expect(observed.summary).toContain("512 text characters");
527
+ const counted = headlessfixturestep(fixture, {
528
+ id: "count",
529
+ kind: "countelements",
530
+ label: "Count the release sections",
531
+ target: "main section",
532
+ });
533
+ expect(counted.state).toBe("done");
534
+ expect((counted.details as { count: number } | undefined)?.count).toBe(1);
535
+ const formread = headlessfixturestep(fixture, { id: "forms", kind: "readforms", label: "Read the release form" });
536
+ expect(formread.state).toBe("done");
537
+ const wider = headlessfixturestep(
538
+ { ...fixture, grants: ["hover"] },
539
+ { id: "hover", kind: "hover", label: "Hover the release link" },
540
+ );
541
+ expect(wider.state).toBe("refused");
542
+ expect(wider.summary).toContain("grades interaction");
543
+ const unprojected = headlessfixturestep(
544
+ { ...fixture, grants: [...fixture.grants, "wait"] },
545
+ { id: "wait", kind: "wait", label: "Wait for the quiet page" },
546
+ );
547
+ expect(unprojected.state).toBe("unsupported");
548
+ expect(unprojected.summary).toContain("no projection for it");
549
+ const progress = headlessprogress({
550
+ progress: undefined,
551
+ planid: "plan1",
552
+ step: { id: "observe", kind: "observe", label: "Observe the release page" },
553
+ outcome: observed,
554
+ now,
555
+ });
556
+ expect(progress.outcomes ?? []).toHaveLength(1);
557
+ expect(progress.outcomes?.[0]?.ok).toBe(true);
558
+ });
559
+ });
560
+
561
+ describe("cli bundle size accounting and library entries", () => {
562
+ it("grades the dist targets against their size budgets so a bundle that outgrows its budget fails the build before it ships", () => {
563
+ const ok = bundlesizeaccounting([
564
+ { target: "cli", bytes: 480_000 },
565
+ { target: "headless", bytes: 690_000 },
566
+ ]);
567
+ expect(ok.ok).toBe(true);
568
+ expect(ok.over).toHaveLength(0);
569
+ const over = bundlesizeaccounting([
570
+ { target: "cli", bytes: 700_000 },
571
+ { target: "headless", bytes: 480_000 },
572
+ { target: "worker", bytes: 9_000_000 },
573
+ ]);
574
+ expect(over.ok).toBe(false);
575
+ expect(over.over).toEqual([{ target: "cli", bytes: 700_000, budget: 600_000 }]);
576
+ expect(over.reason).toContain("cli=700000b>600000b");
577
+ });
578
+ });