@1agh/maude 0.42.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/apps/studio/.ai/cache/_stats.json +7 -0
  2. package/apps/studio/acp/bootstrap-brief.ts +12 -2
  3. package/apps/studio/acp/bridge.ts +44 -10
  4. package/apps/studio/acp/env.ts +23 -0
  5. package/apps/studio/acp/login-state.ts +263 -0
  6. package/apps/studio/acp/plugin-bootstrap.ts +31 -25
  7. package/apps/studio/acp/probe.ts +152 -1
  8. package/apps/studio/annotations-layer.tsx +1451 -324
  9. package/apps/studio/annotations-model.ts +58 -0
  10. package/apps/studio/api.ts +568 -24
  11. package/apps/studio/bin/_audio-search.mjs +140 -0
  12. package/apps/studio/bin/_import-asset-pdf-worker.mjs +34 -0
  13. package/apps/studio/bin/_import-asset.mjs +815 -0
  14. package/apps/studio/bin/_import-brand.mjs +635 -0
  15. package/apps/studio/bin/_import-tokens-alias-resolver.mjs +95 -0
  16. package/apps/studio/bin/_import-tokens.mjs +1201 -0
  17. package/apps/studio/bin/_ingest-footage.mjs +386 -0
  18. package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
  19. package/apps/studio/bin/_transcribe.mjs +419 -0
  20. package/apps/studio/bin/_transcribe.test.mjs +80 -0
  21. package/apps/studio/bin/_video-playwright.mjs +141 -71
  22. package/apps/studio/bin/audio-search.sh +28 -0
  23. package/apps/studio/bin/generate.sh +154 -0
  24. package/apps/studio/bin/import-asset.sh +34 -0
  25. package/apps/studio/bin/import-brand.sh +29 -0
  26. package/apps/studio/bin/import-tokens.sh +33 -0
  27. package/apps/studio/bin/ingest-footage.sh +27 -0
  28. package/apps/studio/bin/photo-adjust.sh +163 -0
  29. package/apps/studio/bin/photo-bg-remove.sh +258 -0
  30. package/apps/studio/bin/probe-footage.sh +28 -0
  31. package/apps/studio/bin/read-annotations.mjs +125 -2
  32. package/apps/studio/bin/transcribe.sh +39 -0
  33. package/apps/studio/canvas-edit.ts +656 -12
  34. package/apps/studio/canvas-lib.tsx +637 -10
  35. package/apps/studio/canvas-pipeline.ts +65 -7
  36. package/apps/studio/canvas-shell.tsx +156 -16
  37. package/apps/studio/client/app.jsx +2385 -699
  38. package/apps/studio/client/export-center.jsx +35 -1
  39. package/apps/studio/client/generate-dialog.jsx +352 -0
  40. package/apps/studio/client/github.js +18 -0
  41. package/apps/studio/client/inspector-controls.jsx +781 -0
  42. package/apps/studio/client/panels/BrandUploadPanel.jsx +233 -0
  43. package/apps/studio/client/panels/ChatPanel.jsx +15 -33
  44. package/apps/studio/client/panels/GitPanel.jsx +8 -0
  45. package/apps/studio/client/panels/IntroVideoDialog.jsx +44 -0
  46. package/apps/studio/client/panels/OnboardingWizard.jsx +12 -0
  47. package/apps/studio/client/panels/ReadinessList.jsx +227 -7
  48. package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
  49. package/apps/studio/client/panels/SettingsPanel.jsx +828 -0
  50. package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
  51. package/apps/studio/client/panels/StickerPicker.jsx +160 -0
  52. package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
  53. package/apps/studio/client/photo-knobs.jsx +432 -0
  54. package/apps/studio/client/styles/3-shell-maude.css +376 -28
  55. package/apps/studio/client/styles/4-components.css +223 -0
  56. package/apps/studio/client/styles/6-acp-chat.css +86 -0
  57. package/apps/studio/client/tour/quick-setup-tour.js +39 -0
  58. package/apps/studio/commands/annotation-strokes-command.ts +13 -3
  59. package/apps/studio/commands/edit-source-command.ts +31 -4
  60. package/apps/studio/config.schema.json +70 -0
  61. package/apps/studio/context-menu.tsx +42 -8
  62. package/apps/studio/context.ts +16 -0
  63. package/apps/studio/design-setup-readiness.ts +115 -0
  64. package/apps/studio/dist/client.bundle.js +5572 -21
  65. package/apps/studio/dist/comment-mount.js +2 -2
  66. package/apps/studio/dist/runtime/.min-sizes.json +1 -0
  67. package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
  68. package/apps/studio/dist/runtime/pixi-js.js +519 -519
  69. package/apps/studio/dist/styles.css +1 -1
  70. package/apps/studio/dom-selection.ts +25 -0
  71. package/apps/studio/draw/palette.ts +34 -0
  72. package/apps/studio/exporters/jobs.ts +47 -5
  73. package/apps/studio/exporters/video.ts +24 -4
  74. package/apps/studio/footage/schema.test.ts +287 -0
  75. package/apps/studio/footage/schema.ts +662 -0
  76. package/apps/studio/footage-store.ts +235 -0
  77. package/apps/studio/fs-watch.ts +10 -0
  78. package/apps/studio/generation/adapters/elevenlabs.ts +370 -0
  79. package/apps/studio/generation/adapters/gemini.ts +542 -0
  80. package/apps/studio/generation/adapters/groq.test.ts +80 -0
  81. package/apps/studio/generation/adapters/groq.ts +247 -0
  82. package/apps/studio/generation/audio-library.test.ts +95 -0
  83. package/apps/studio/generation/audio-library.ts +156 -0
  84. package/apps/studio/generation/captions.test.ts +78 -0
  85. package/apps/studio/generation/captions.ts +132 -0
  86. package/apps/studio/generation/download.ts +76 -0
  87. package/apps/studio/generation/elevenlabs.test.ts +223 -0
  88. package/apps/studio/generation/gemini.test.ts +350 -0
  89. package/apps/studio/generation/jobs.test.ts +128 -0
  90. package/apps/studio/generation/jobs.ts +243 -0
  91. package/apps/studio/generation/keys.test.ts +75 -0
  92. package/apps/studio/generation/keys.ts +162 -0
  93. package/apps/studio/generation/prefs.test.ts +95 -0
  94. package/apps/studio/generation/prefs.ts +82 -0
  95. package/apps/studio/generation/registry.ts +88 -0
  96. package/apps/studio/generation/types.test.ts +123 -0
  97. package/apps/studio/generation/types.ts +266 -0
  98. package/apps/studio/generation/whisper-models.test.ts +152 -0
  99. package/apps/studio/generation/whisper-models.ts +257 -0
  100. package/apps/studio/generation/whisper-spike-results.md +61 -0
  101. package/apps/studio/git/endpoints.ts +68 -7
  102. package/apps/studio/git/service.ts +199 -21
  103. package/apps/studio/github/service.ts +67 -0
  104. package/apps/studio/handoff.ts +3 -2
  105. package/apps/studio/http.ts +1039 -22
  106. package/apps/studio/input-router.tsx +10 -0
  107. package/apps/studio/inspect.ts +53 -1
  108. package/apps/studio/media/intro.mp4 +0 -0
  109. package/apps/studio/media-commit-chain.ts +119 -0
  110. package/apps/studio/paths.ts +20 -0
  111. package/apps/studio/photo/filters.ts +285 -0
  112. package/apps/studio/photo/pipeline.ts +607 -0
  113. package/apps/studio/photo/schema.ts +367 -0
  114. package/apps/studio/photo-store.ts +154 -0
  115. package/apps/studio/readiness.ts +108 -20
  116. package/apps/studio/runtime-bundle.ts +29 -6
  117. package/apps/studio/scaffold-design.ts +1145 -0
  118. package/apps/studio/server.ts +18 -2
  119. package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
  120. package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
  121. package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
  122. package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
  123. package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
  124. package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
  125. package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
  126. package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
  127. package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
  128. package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
  129. package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
  130. package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
  131. package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
  132. package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
  133. package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
  134. package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
  135. package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
  136. package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
  137. package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
  138. package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
  139. package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
  140. package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
  141. package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
  142. package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
  143. package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
  144. package/apps/studio/stickers/life-style/best.png +0 -0
  145. package/apps/studio/stickers/life-style/come-on.png +0 -0
  146. package/apps/studio/stickers/life-style/cut.png +0 -0
  147. package/apps/studio/stickers/life-style/detox.png +0 -0
  148. package/apps/studio/stickers/life-style/just.png +0 -0
  149. package/apps/studio/stickers/life-style/manifest.json +68 -0
  150. package/apps/studio/stickers/life-style/mirror.png +0 -0
  151. package/apps/studio/stickers/life-style/nice.png +0 -0
  152. package/apps/studio/stickers/life-style/objects.png +0 -0
  153. package/apps/studio/stickers/life-style/ok.png +0 -0
  154. package/apps/studio/stickers/life-style/queen.png +0 -0
  155. package/apps/studio/stickers/life-style/star.png +0 -0
  156. package/apps/studio/stickers/life-style/sun.png +0 -0
  157. package/apps/studio/stickers/life-style/water.png +0 -0
  158. package/apps/studio/stickers/life-style/yeah.png +0 -0
  159. package/apps/studio/stickers/life-style/you-can.png +0 -0
  160. package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
  161. package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
  162. package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
  163. package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
  164. package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
  165. package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
  166. package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
  167. package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
  168. package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
  169. package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
  170. package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
  171. package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
  172. package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
  173. package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
  174. package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
  175. package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
  176. package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
  177. package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
  178. package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
  179. package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
  180. package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
  181. package/apps/studio/stickers/project-status/group-100.png +0 -0
  182. package/apps/studio/stickers/project-status/group-101.png +0 -0
  183. package/apps/studio/stickers/project-status/group-102.png +0 -0
  184. package/apps/studio/stickers/project-status/group-103.png +0 -0
  185. package/apps/studio/stickers/project-status/group-104.png +0 -0
  186. package/apps/studio/stickers/project-status/group-105.png +0 -0
  187. package/apps/studio/stickers/project-status/group-106.png +0 -0
  188. package/apps/studio/stickers/project-status/group-107.png +0 -0
  189. package/apps/studio/stickers/project-status/group-108.png +0 -0
  190. package/apps/studio/stickers/project-status/group-109.png +0 -0
  191. package/apps/studio/stickers/project-status/group-110.png +0 -0
  192. package/apps/studio/stickers/project-status/group-111.png +0 -0
  193. package/apps/studio/stickers/project-status/group-112.png +0 -0
  194. package/apps/studio/stickers/project-status/group-113.png +0 -0
  195. package/apps/studio/stickers/project-status/group-114.png +0 -0
  196. package/apps/studio/stickers/project-status/group-115.png +0 -0
  197. package/apps/studio/stickers/project-status/group-117.png +0 -0
  198. package/apps/studio/stickers/project-status/group-118.png +0 -0
  199. package/apps/studio/stickers/project-status/group-119.png +0 -0
  200. package/apps/studio/stickers/project-status/group-120.png +0 -0
  201. package/apps/studio/stickers/project-status/group-121.png +0 -0
  202. package/apps/studio/stickers/project-status/group-122.png +0 -0
  203. package/apps/studio/stickers/project-status/group-41.png +0 -0
  204. package/apps/studio/stickers/project-status/group-42.png +0 -0
  205. package/apps/studio/stickers/project-status/group-43.png +0 -0
  206. package/apps/studio/stickers/project-status/group-44.png +0 -0
  207. package/apps/studio/stickers/project-status/group-45.png +0 -0
  208. package/apps/studio/stickers/project-status/group-46.png +0 -0
  209. package/apps/studio/stickers/project-status/group-47.png +0 -0
  210. package/apps/studio/stickers/project-status/group-48.png +0 -0
  211. package/apps/studio/stickers/project-status/group-49.png +0 -0
  212. package/apps/studio/stickers/project-status/group-50.png +0 -0
  213. package/apps/studio/stickers/project-status/group-51.png +0 -0
  214. package/apps/studio/stickers/project-status/group-52.png +0 -0
  215. package/apps/studio/stickers/project-status/group-53.png +0 -0
  216. package/apps/studio/stickers/project-status/group-54.png +0 -0
  217. package/apps/studio/stickers/project-status/group-55.png +0 -0
  218. package/apps/studio/stickers/project-status/group-56.png +0 -0
  219. package/apps/studio/stickers/project-status/group-57.png +0 -0
  220. package/apps/studio/stickers/project-status/group-58.png +0 -0
  221. package/apps/studio/stickers/project-status/group-59.png +0 -0
  222. package/apps/studio/stickers/project-status/group-60.png +0 -0
  223. package/apps/studio/stickers/project-status/group-61.png +0 -0
  224. package/apps/studio/stickers/project-status/group-62.png +0 -0
  225. package/apps/studio/stickers/project-status/group-63.png +0 -0
  226. package/apps/studio/stickers/project-status/group-64.png +0 -0
  227. package/apps/studio/stickers/project-status/group-65.png +0 -0
  228. package/apps/studio/stickers/project-status/group-66.png +0 -0
  229. package/apps/studio/stickers/project-status/group-67.png +0 -0
  230. package/apps/studio/stickers/project-status/group-68.png +0 -0
  231. package/apps/studio/stickers/project-status/group-69.png +0 -0
  232. package/apps/studio/stickers/project-status/group-70.png +0 -0
  233. package/apps/studio/stickers/project-status/group-71.png +0 -0
  234. package/apps/studio/stickers/project-status/group-72.png +0 -0
  235. package/apps/studio/stickers/project-status/group-73.png +0 -0
  236. package/apps/studio/stickers/project-status/group-74.png +0 -0
  237. package/apps/studio/stickers/project-status/group-75.png +0 -0
  238. package/apps/studio/stickers/project-status/group-76.png +0 -0
  239. package/apps/studio/stickers/project-status/group-77.png +0 -0
  240. package/apps/studio/stickers/project-status/group-78.png +0 -0
  241. package/apps/studio/stickers/project-status/group-79.png +0 -0
  242. package/apps/studio/stickers/project-status/group-80.png +0 -0
  243. package/apps/studio/stickers/project-status/group-81.png +0 -0
  244. package/apps/studio/stickers/project-status/group-82.png +0 -0
  245. package/apps/studio/stickers/project-status/group-83.png +0 -0
  246. package/apps/studio/stickers/project-status/group-84.png +0 -0
  247. package/apps/studio/stickers/project-status/group-85.png +0 -0
  248. package/apps/studio/stickers/project-status/group-86.png +0 -0
  249. package/apps/studio/stickers/project-status/group-87.png +0 -0
  250. package/apps/studio/stickers/project-status/group-88.png +0 -0
  251. package/apps/studio/stickers/project-status/group-89.png +0 -0
  252. package/apps/studio/stickers/project-status/group-90.png +0 -0
  253. package/apps/studio/stickers/project-status/group-91.png +0 -0
  254. package/apps/studio/stickers/project-status/group-92.png +0 -0
  255. package/apps/studio/stickers/project-status/group-93.png +0 -0
  256. package/apps/studio/stickers/project-status/group-94.png +0 -0
  257. package/apps/studio/stickers/project-status/group-96.png +0 -0
  258. package/apps/studio/stickers/project-status/group-97.png +0 -0
  259. package/apps/studio/stickers/project-status/group-98.png +0 -0
  260. package/apps/studio/stickers/project-status/group-99.png +0 -0
  261. package/apps/studio/stickers/project-status/manifest.json +328 -0
  262. package/apps/studio/test/_helpers.ts +15 -2
  263. package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
  264. package/apps/studio/test/acp-bridge.test.ts +34 -1
  265. package/apps/studio/test/acp-commands.test.ts +2 -1
  266. package/apps/studio/test/acp-env.test.ts +30 -0
  267. package/apps/studio/test/acp-plugin-bootstrap.test.ts +23 -57
  268. package/apps/studio/test/acp-session-plugins.test.ts +72 -1
  269. package/apps/studio/test/annotation-strokes-command.test.ts +17 -0
  270. package/apps/studio/test/annotations-layer.test.ts +174 -0
  271. package/apps/studio/test/asset-api.test.ts +3 -3
  272. package/apps/studio/test/canvas-edit.test.ts +17 -6
  273. package/apps/studio/test/canvas-origin-gate.test.ts +50 -0
  274. package/apps/studio/test/csp-canvas-shell.test.ts +11 -0
  275. package/apps/studio/test/csrf-write-guard.test.ts +23 -1
  276. package/apps/studio/test/design-setup-readiness.test.ts +155 -0
  277. package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
  278. package/apps/studio/test/edit-source-command.test.ts +23 -0
  279. package/apps/studio/test/element-structural-edit.test.ts +154 -0
  280. package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
  281. package/apps/studio/test/footage-store.test.ts +100 -0
  282. package/apps/studio/test/generate-route.test.ts +106 -0
  283. package/apps/studio/test/git-branches.test.ts +97 -0
  284. package/apps/studio/test/github-api.test.ts +56 -1
  285. package/apps/studio/test/import-asset-browser.test.ts +64 -0
  286. package/apps/studio/test/import-asset-route.test.ts +71 -0
  287. package/apps/studio/test/import-asset.test.ts +399 -0
  288. package/apps/studio/test/import-brand-browser.test.ts +88 -0
  289. package/apps/studio/test/import-brand-route.test.ts +120 -0
  290. package/apps/studio/test/import-brand.test.ts +206 -0
  291. package/apps/studio/test/import-tokens.test.ts +722 -0
  292. package/apps/studio/test/input-router.test.ts +33 -0
  293. package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
  294. package/apps/studio/test/media-commit-chain.test.ts +210 -0
  295. package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
  296. package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
  297. package/apps/studio/test/photo-edit-api.test.ts +201 -0
  298. package/apps/studio/test/photo-filters.test.ts +149 -0
  299. package/apps/studio/test/photo-pipeline.test.ts +106 -0
  300. package/apps/studio/test/photo-taxonomy.test.ts +96 -0
  301. package/apps/studio/test/read-annotations.test.ts +164 -0
  302. package/apps/studio/test/readiness.test.ts +13 -11
  303. package/apps/studio/test/scaffold-design.test.ts +122 -0
  304. package/apps/studio/test/shell-importmap.test.ts +49 -0
  305. package/apps/studio/test/text-editability-stamp.test.ts +131 -0
  306. package/apps/studio/test/timeline-parse.test.ts +57 -0
  307. package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
  308. package/apps/studio/test/tour-overlay.test.tsx +16 -0
  309. package/apps/studio/test/undo-stack.test.ts +33 -0
  310. package/apps/studio/test/video-asset.test.ts +5 -5
  311. package/apps/studio/test/video-render-bridge.test.ts +23 -1
  312. package/apps/studio/text-caret.ts +239 -0
  313. package/apps/studio/tool-palette.tsx +49 -21
  314. package/apps/studio/ui-prefs.ts +130 -0
  315. package/apps/studio/undo-stack.ts +94 -2
  316. package/apps/studio/use-annotation-resize.tsx +4 -4
  317. package/apps/studio/use-canvas-media-drop.tsx +40 -1
  318. package/apps/studio/use-chrome-visibility.tsx +8 -2
  319. package/apps/studio/use-selection-set.tsx +21 -0
  320. package/apps/studio/video-comp.tsx +48 -7
  321. package/apps/studio/whats-new.json +149 -0
  322. package/apps/studio/ws.ts +5 -0
  323. package/cli/bin/maude.mjs +6 -6
  324. package/cli/commands/design.mjs +67 -3
  325. package/cli/lib/gitignore-block.mjs +2 -0
  326. package/cli/lib/pkg-root.mjs +107 -0
  327. package/cli/lib/pkg-root.test.mjs +79 -0
  328. package/cli/lib/reconstruct-toolset.test.mjs +194 -0
  329. package/cli/lib/update-check.mjs +8 -0
  330. package/package.json +13 -11
  331. package/plugins/design/dependencies.json +18 -0
  332. package/plugins/design/templates/_shell.html +1 -0
@@ -14,6 +14,8 @@ import {
14
14
  gitFetchRemote,
15
15
  gitFoldDraft,
16
16
  gitListBranches,
17
+ gitPull,
18
+ gitPush,
17
19
  remoteAheadBehind,
18
20
  } from '../git/service.ts';
19
21
 
@@ -111,6 +113,30 @@ test('fold: merges the draft into the Shared version locally; a tokenless publis
111
113
  expect((await gitListBranches(dir)).some((b) => b.name === 'nav')).toBe(true);
112
114
  });
113
115
 
116
+ test('fold: a GitHub remote takes the PR path — pushes the draft, never locally merges main (DDR-162)', async () => {
117
+ // isGitHubRemote(github.com) → PR flow: publish the DRAFT branch (fails here — no
118
+ // real access) and NEVER merge/modify the local Shared version. `false` as the ssh
119
+ // command fails the transport instantly, so there's no network to github.com.
120
+ const prevSsh = process.env.GIT_SSH_COMMAND;
121
+ process.env.GIT_SSH_COMMAND = 'false';
122
+ const shared = (await gitListBranches(dir)).find((b) => b.current)?.name as string;
123
+ sh(['remote', 'add', 'origin', 'git@github.com:fake-org/fake-repo.git']);
124
+ await gitCreateBranch(dir, 'nav'); // now on 'nav'
125
+ writeFileSync(join(dir, 'b.txt'), 'draft work\n');
126
+ sh(['add', '.']);
127
+ sh(['commit', '-q', '-m', 'draft work']);
128
+ const r = await gitFoldDraft(dir, 'nav', undefined, {});
129
+ process.env.GIT_SSH_COMMAND = prevSsh ?? '';
130
+ // The draft push failed (fake repo) → not ok, and NOT the iso transport error…
131
+ expect(r.ok).toBe(false);
132
+ expect(r.error || '').not.toMatch(/unrecognized transport/i);
133
+ // …but crucially the Shared version was NEVER locally merged/modified…
134
+ sh(['checkout', shared]);
135
+ expect(existsSync(join(dir, 'b.txt'))).toBe(false);
136
+ // …and the draft still exists (nothing destroyed on a failed publish).
137
+ expect((await gitListBranches(dir)).some((b) => b.name === 'nav')).toBe(true);
138
+ });
139
+
114
140
  // ── remote drafts (phase: surface remote branches) ───────────────────────────
115
141
 
116
142
  /** Stand up a bare "remote" with main + a teammate draft, clone it locally (system
@@ -197,6 +223,77 @@ test('an ssh remote routes to system git, never the iso transport error', async
197
223
  expect(r.error || '').not.toMatch(/unrecognized transport/i); // didn't fall into iso
198
224
  });
199
225
 
226
+ // ── publish / get-latest over ssh (DDR-131/DDR-133 parity — the gap that shipped) ──
227
+ // gitFetchRemote was hardened for ssh; gitPush/gitPull historically routed on the
228
+ // USE_SYSTEM_GIT env flag only (default off) and fell into iso → "unrecognized
229
+ // transport protocol: ssh". These prove the write paths now take the same gate.
230
+
231
+ test('gitPush: an ssh remote routes to system git, never the iso transport error', async () => {
232
+ process.env.GIT_SSH_COMMAND =
233
+ 'ssh -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=no';
234
+ sh(['remote', 'add', 'origin', 'git@nonexistent.invalid:team/app.git']);
235
+ const r = await gitPush(dir, undefined, {});
236
+ process.env.GIT_SSH_COMMAND = '';
237
+ expect(r.ok).toBe(false);
238
+ expect(r.authRequired).toBeFalsy(); // an ssh failure is not a GitHub sign-in prompt
239
+ expect(r.error || '').not.toMatch(/unrecognized transport/i); // didn't fall into iso
240
+ });
241
+
242
+ test('gitPull: an ssh remote routes to system git, never the iso transport error', async () => {
243
+ process.env.GIT_SSH_COMMAND =
244
+ 'ssh -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=no';
245
+ sh(['remote', 'add', 'origin', 'git@nonexistent.invalid:team/app.git']);
246
+ const r = await gitPull(dir, undefined, {});
247
+ process.env.GIT_SSH_COMMAND = '';
248
+ expect(r.ok).toBe(false);
249
+ expect(r.authRequired).toBeFalsy();
250
+ expect(r.error || '').not.toMatch(/unrecognized transport/i);
251
+ });
252
+
253
+ test('SECURITY: gitPush refuses an unsafe (ext::) remote — no spawn, no RCE', async () => {
254
+ // A poisoned .git/config remote — must be refused BEFORE any git spawn (the payload
255
+ // never runs); classifyRemoteUrl reads the url via iso getConfig, not the git binary.
256
+ const sentinel = join(dir, 'PWNED_PUSH');
257
+ sh(['remote', 'add', 'origin', `ext::sh -c "touch ${sentinel}"`]);
258
+ const r = await gitPush(dir, 'tok_should_not_be_used', {});
259
+ expect(r.ok).toBe(false);
260
+ expect(r.error).toMatch(/github\.com/i); // "can only sync github.com…"
261
+ expect(existsSync(sentinel)).toBe(false); // payload NEVER executed
262
+ });
263
+
264
+ test('SECURITY: gitPush does not send the GitHub token to a non-GitHub HTTPS host', async () => {
265
+ sh(['remote', 'add', 'origin', 'https://evil.example.com/repo.git']);
266
+ const r = await gitPush(dir, 'tok_secret', {});
267
+ expect(r.ok).toBe(false);
268
+ expect(r.error).toMatch(/github\.com/i); // refused by policy, token never attached
269
+ });
270
+
271
+ test('SECURITY: a backslash-@ host-confusion remote is refused, PAT never attached (F1)', async () => {
272
+ // `https://github.com\@evil` — new URL() parses host=github.com (backslash → '/'),
273
+ // but git/curl dials `evil` (backslash = path/userinfo char). The token-host allowlist
274
+ // must reject the parser differential and NOT lend the PAT — on every token path:
275
+ // explicit fetch, the unattended ahead/behind probe (the zero-click trigger), and push.
276
+ sh(['remote', 'add', 'origin', 'https://github.com\\@evil.example/x.git']);
277
+ const f = await gitFetchRemote(dir, 'tok_secret');
278
+ expect(f.ok).toBe(false);
279
+ expect(f.error).toMatch(/github\.com/i); // refused by policy — token never attached
280
+ const ab = await remoteAheadBehind(dir, 'tok_secret');
281
+ expect(ab).toEqual({ ahead: 0, behind: 0 }); // the unattended probe refuses too (0/0, no fetch)
282
+ const p = await gitPush(dir, 'tok_secret', {});
283
+ expect(p.ok).toBe(false);
284
+ expect(p.error).toMatch(/github\.com/i);
285
+ });
286
+
287
+ test('SECURITY: the unattended probe refuses a repo with a url.insteadOf rewrite (verify re-review)', async () => {
288
+ // A clean github `origin.url` (passes isTrustedTokenHost) + a poisoned
289
+ // `url.<attacker>.insteadOf` that redirects github.com → attacker (only the system
290
+ // engine honors it). git would dial the attacker; the zero-click probe must refuse.
291
+ sh(['remote', 'add', 'origin', 'https://github.com/owner/repo.git']);
292
+ sh(['config', '--local', 'url.https://attacker.example/.insteadOf', 'https://github.com/']);
293
+ const ab = await remoteAheadBehind(dir, 'tok_secret');
294
+ expect(ab).toEqual({ ahead: 0, behind: 0 });
295
+ });
296
+
200
297
  // ── transport-injection hardening (adversarial review of 75a2f0d) ────────────
201
298
 
202
299
  test('SECURITY: refuses to fetch an `ext::` (command-executing) remote — no spawn, no RCE', async () => {
@@ -18,6 +18,7 @@ import git from 'isomorphic-git';
18
18
  import type { Context } from '../context.ts';
19
19
  import { createGitHubEndpoints, __testing as epTesting } from '../github/endpoints.ts';
20
20
  import {
21
+ createPullRequest,
21
22
  createRepo,
22
23
  GitHubApiError,
23
24
  getIdentity,
@@ -98,6 +99,59 @@ describe('GitHub REST request construction', () => {
98
99
  expect((err as GitHubApiError).message).toMatch(/already have a project with that name/i);
99
100
  });
100
101
 
102
+ test('createPullRequest POSTs title/head/base/body and shapes the result', async () => {
103
+ const calls = stubFetch(() =>
104
+ json({ number: 12, html_url: 'https://github.com/octocat/acme/pull/12' }, 201)
105
+ );
106
+ const pr = await createPullRequest('t', 'octocat', 'acme', {
107
+ head: 'nav-redesign',
108
+ base: 'main',
109
+ title: 'Add draft',
110
+ body: 'hi',
111
+ });
112
+ expect(pr).toEqual({ number: 12, html_url: 'https://github.com/octocat/acme/pull/12' });
113
+ expect(calls[0].url).toBe('https://api.github.com/repos/octocat/acme/pulls');
114
+ expect(calls[0].init?.method).toBe('POST');
115
+ expect(JSON.parse(calls[0].init?.body as string)).toEqual({
116
+ title: 'Add draft',
117
+ head: 'nav-redesign',
118
+ base: 'main',
119
+ body: 'hi',
120
+ });
121
+ });
122
+
123
+ test('createPullRequest recovers the existing PR link on a 422 (already exists)', async () => {
124
+ const calls = stubFetch((_url, init) =>
125
+ init?.method === 'POST'
126
+ ? json({ message: 'A pull request already exists for octocat:nav.' }, 422)
127
+ : json([{ number: 7, html_url: 'https://github.com/octocat/acme/pull/7' }])
128
+ );
129
+ const pr = await createPullRequest('t', 'octocat', 'acme', {
130
+ head: 'nav',
131
+ base: 'main',
132
+ title: 'x',
133
+ });
134
+ expect(pr).toEqual({ number: 7, html_url: 'https://github.com/octocat/acme/pull/7' });
135
+ // the recovery GET filters by head=owner:branch + base + state=open
136
+ expect(calls[1].url).toContain('head=octocat%3Anav');
137
+ expect(calls[1].url).toContain('base=main');
138
+ expect(calls[1].url).toContain('state=open');
139
+ });
140
+
141
+ test('createPullRequest maps a 422 with no existing PR to a "nothing new" message', async () => {
142
+ stubFetch((_url, init) =>
143
+ init?.method === 'POST' ? json({ message: 'No commits between main and nav' }, 422) : json([])
144
+ );
145
+ const err = await createPullRequest('t', 'octocat', 'acme', {
146
+ head: 'nav',
147
+ base: 'main',
148
+ title: 'x',
149
+ }).catch((e) => e);
150
+ expect(err).toBeInstanceOf(GitHubApiError);
151
+ expect((err as GitHubApiError).status).toBe(422);
152
+ expect((err as GitHubApiError).message).toMatch(/nothing new to add/i);
153
+ });
154
+
101
155
  test('inviteCollaborator PUTs permission=push, 201 = invited', async () => {
102
156
  const calls = stubFetch(() => json({ id: 1 }, 201));
103
157
  const r = await inviteCollaborator('t', 'octocat', 'acme', 'anna');
@@ -242,7 +296,8 @@ describe('token bridge fetch', () => {
242
296
  const calls = stubFetch(() => new Response('gho_secret\n', { status: 200 }));
243
297
  expect(tokenBridgeAvailable()).toBe(true);
244
298
  expect(await getGithubToken()).toBe('gho_secret');
245
- expect((calls[0].init?.headers as Record<string, string>)['X-Maude-Token-Key']).toBe('k123');
299
+ const headers = calls[0].init?.headers as Record<string, string>;
300
+ expect(headers['X-Maude-Token-Key']).toBe('k123');
246
301
  });
247
302
 
248
303
  test('returns null on 404 (not signed in) and 403 (bad key)', async () => {
@@ -0,0 +1,64 @@
1
+ // Browser-driven pieces of local-file/SVG/PDF ingestion (DDR-167) — the SVG
2
+ // execution canary. Spawns real agent-browser sessions, so this is slower
3
+ // than import-asset.test.ts and is skipped (not failed) when agent-browser
4
+ // can't be resolved, mirroring how the rest of this codebase treats
5
+ // browser-dependent verification as a real-environment concern rather than a
6
+ // hard CI requirement (e.g. ensure-browser.test.ts's resolution-only tests).
7
+ //
8
+ // DDR-167 requires the SVG canary fixtures be a PERMANENT regression test,
9
+ // not a one-time pre-ship check — this file is that test. The PDF rasterize
10
+ // adversarial fixtures (OpenAction/JS canary, SubmitForm/URI/Launch) are NOT
11
+ // here: live spike-verification found the planned rasterization mechanism
12
+ // (headless-Chromium navigating a file://*.pdf URL) does not render PDF
13
+ // content under agent-browser automation (confirmed blank output on both
14
+ // chrome-headless-shell and full Chrome) — see DDR-167's addendum. PDF
15
+ // rasterization throws "not yet available" rather than shipping broken
16
+ // output; there is nothing to fixture-test until a follow-up mechanism lands.
17
+
18
+ import { describe, expect, test } from 'bun:test';
19
+ import { execFileSync } from 'node:child_process';
20
+
21
+ import { rasterizePdfPage, runSvgExecutionCanary } from '../bin/_import-asset.mjs';
22
+
23
+ function agentBrowserAvailable(): boolean {
24
+ try {
25
+ execFileSync(process.env.MAUDE_AGENT_BROWSER_BIN || 'agent-browser', ['--version'], {
26
+ stdio: 'ignore',
27
+ timeout: 5000,
28
+ });
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ const HAS_AGENT_BROWSER = agentBrowserAvailable();
36
+ const d = HAS_AGENT_BROWSER ? describe : describe.skip;
37
+
38
+ d('SVG execution canary (Decision 1, step 6) — permanent regression fixtures', () => {
39
+ test('accepts a clean, allowlist-sanitized SVG (no false positive)', async () => {
40
+ const clean =
41
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M16 5l2.8 8.2L27 16l-8.2 2.8L16 27l-2.8-8.2L5 16l8.2-2.8z" fill="#4f46e5"/></svg>';
42
+ await expect(runSvgExecutionCanary(clean)).resolves.toBe(clean);
43
+ }, 30_000);
44
+
45
+ test('trips on a raw <script>-bearing SVG (defense-in-depth if the allowlist sanitizer were ever bypassed)', async () => {
46
+ const malicious =
47
+ '<svg xmlns="http://www.w3.org/2000/svg"><script>window.__MAUDE_IMPORT_CANARY__=true;</script></svg>';
48
+ await expect(runSvgExecutionCanary(malicious)).rejects.toThrow(/canary tripped/);
49
+ }, 30_000);
50
+
51
+ test('the sandboxed-render helper does not block the file:// navigation it needs (regression: a bare "*" network route also blocks local file loads)', async () => {
52
+ // A file:// SVG with no scripting at all must still load and be
53
+ // observable — this specifically guards against re-introducing the
54
+ // wildcard-route regression found during implementation.
55
+ const trivial = '<svg xmlns="http://www.w3.org/2000/svg"><rect width="1" height="1"/></svg>';
56
+ await expect(runSvgExecutionCanary(trivial)).resolves.toBeTruthy();
57
+ }, 30_000);
58
+ });
59
+
60
+ d('PDF rasterization — explicitly NOT shipped (DDR-167 addendum)', () => {
61
+ test('rasterizePdfPage throws "not yet available" rather than producing output', async () => {
62
+ await expect(rasterizePdfPage('/nonexistent.pdf', 1)).rejects.toThrow(/not yet available/);
63
+ });
64
+ });
@@ -0,0 +1,71 @@
1
+ // POST /_api/import-asset — DDR-167 (Phase 3 / T10). Full HTTP round-trip
2
+ // against a real booted server (privilege/CSRF boundary is covered generically
3
+ // by canvas-origin-gate.test.ts — this file exercises the route's own
4
+ // success/error shapes).
5
+
6
+ import { describe, expect, test } from 'bun:test';
7
+ import { readFileSync } from 'node:fs';
8
+
9
+ import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
10
+
11
+ const MALICIOUS_SVG =
12
+ '<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script>' +
13
+ '<rect onclick="alert(2)" fill="#4f46e5" width="10" height="10"/></svg>';
14
+
15
+ describe('POST /_api/import-asset', () => {
16
+ test('imports and sanitizes an SVG, rejects a PDF kind as not-yet-available, rejects GET', async () => {
17
+ const { root, designRoot } = makeSandbox();
18
+ const port = nextPort();
19
+ const proc = await bootServer(root, port);
20
+ try {
21
+ const base = `http://localhost:${port}`;
22
+
23
+ const post = await fetch(`${base}/_api/import-asset`, {
24
+ method: 'POST',
25
+ headers: { 'X-Import-Kind': 'svg', Origin: base },
26
+ body: MALICIOUS_SVG,
27
+ });
28
+ expect(post.status).toBe(201);
29
+ const body = (await post.json()) as { ok: boolean; path: string };
30
+ expect(body.ok).toBe(true);
31
+ expect(body.path).toMatch(/^assets\/[a-z0-9]{8}\.svg$/);
32
+ const written = readFileSync(`${designRoot}/${body.path}`, 'utf8');
33
+ expect(written).not.toContain('script');
34
+ expect(written).not.toContain('onclick');
35
+
36
+ const pdfAttempt = await fetch(`${base}/_api/import-asset`, {
37
+ method: 'POST',
38
+ headers: { 'X-Import-Kind': 'pdf', Origin: base },
39
+ body: '%PDF-1.4 fixture',
40
+ });
41
+ expect(pdfAttempt.status).toBe(501);
42
+ const pdfBody = (await pdfAttempt.json()) as { ok: boolean; error: string };
43
+ expect(pdfBody.ok).toBe(false);
44
+ expect(pdfBody.error).toContain('not yet available');
45
+
46
+ const getAttempt = await fetch(`${base}/_api/import-asset`, { method: 'GET' });
47
+ expect(getAttempt.status).toBe(405);
48
+ } finally {
49
+ await killProc(proc);
50
+ }
51
+ }, 30_000);
52
+
53
+ test('a malformed SVG is rejected with a clean 400, not a 500 crash', async () => {
54
+ const { root } = makeSandbox();
55
+ const port = nextPort();
56
+ const proc = await bootServer(root, port);
57
+ try {
58
+ const base = `http://localhost:${port}`;
59
+ const res = await fetch(`${base}/_api/import-asset`, {
60
+ method: 'POST',
61
+ headers: { 'X-Import-Kind': 'svg', Origin: base },
62
+ body: '<svg><rect',
63
+ });
64
+ expect(res.status).toBe(400);
65
+ const body = (await res.json()) as { ok: boolean };
66
+ expect(body.ok).toBe(false);
67
+ } finally {
68
+ await killProc(proc);
69
+ }
70
+ }, 30_000);
71
+ });