@hybridlabor-api/aos 4.6.6 → 4.7.1

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 (503) hide show
  1. package/.agents/agents.md +9 -3
  2. package/.agents/nodes.json +7 -2
  3. package/.agents/state.schema.json +26 -1
  4. package/.agents/vendor-manifest.json +56 -0
  5. package/.agents/workflows/startcycle-graph.md +2 -2
  6. package/.claude/agents/architect.md +7 -2
  7. package/.claude/agents/security-reviewer.md +1 -1
  8. package/.claude/agents/techlead.md +2 -0
  9. package/.claude/hooks/memb-inject.mjs +257 -135
  10. package/.claude/hooks/trail-relay.mjs +18 -0
  11. package/.claude/settings.json +70 -0
  12. package/.claude/workflows/startcycle-dispatch.mjs +203 -17
  13. package/.opencode/agents/architect.md +7 -2
  14. package/.opencode/agents/techlead.md +2 -0
  15. package/.opencode/plugins/bdb-aos.js +75 -0
  16. package/THIRD_PARTY_NOTICES.md +57 -1
  17. package/bin/aos-doctor.mjs +366 -0
  18. package/bin/aos-store.mjs +167 -0
  19. package/bin/aos-uninstall.mjs +55 -1
  20. package/docs/AUDIT_BRIEF_ECC_STORE.md +122 -0
  21. package/docs/PLAN_AOS_STORE_PHASE1.md +190 -0
  22. package/docs/handover/agenttrail-for-ao.md +58 -0
  23. package/docs/handover/deja-for-ao.md +51 -0
  24. package/installer.js +182 -38
  25. package/lib/aos-archify-contract.mjs +277 -0
  26. package/lib/ecc-store-index.json +1 -0
  27. package/mcp_config.json +8 -0
  28. package/mcps/mcsc/README.md +24 -0
  29. package/mcps/mcsc/package-lock.json +1234 -0
  30. package/mcps/mcsc/package.json +14 -0
  31. package/mcps/mcsc/packages/core/bin/mcsc.js +162 -0
  32. package/mcps/mcsc/packages/core/rulebook.default.yaml +49 -0
  33. package/mcps/mcsc/packages/core/schema/rulebook.schema.json +56 -0
  34. package/mcps/mcsc/packages/core/src/adapters/agy.js +107 -0
  35. package/mcps/mcsc/packages/core/src/adapters/codex.js +179 -0
  36. package/mcps/mcsc/packages/core/src/adapters/native.js +14 -0
  37. package/mcps/mcsc/packages/core/src/adapters/opencode.js +183 -0
  38. package/mcps/mcsc/packages/core/src/adapters/types.js +21 -0
  39. package/mcps/mcsc/packages/core/src/capabilities/buildOfferTable.js +34 -0
  40. package/mcps/mcsc/packages/core/src/capabilities/registry.js +99 -0
  41. package/mcps/mcsc/packages/core/src/capabilities/types.js +24 -0
  42. package/mcps/mcsc/packages/core/src/detect/cache.js +47 -0
  43. package/mcps/mcsc/packages/core/src/detect/inventory.js +73 -0
  44. package/mcps/mcsc/packages/core/src/detect/probes.js +158 -0
  45. package/mcps/mcsc/packages/core/src/detect/types.js +26 -0
  46. package/mcps/mcsc/packages/core/src/rulebook/load.js +53 -0
  47. package/mcps/mcsc/packages/core/src/rulebook/resolve.js +227 -0
  48. package/mcps/mcsc/packages/core/src/rulebook/rulebook.types.js +89 -0
  49. package/mcps/mcsc/packages/core/src/trail.js +17 -0
  50. package/mcps/mcsc/packages/mcp/server.js +302 -0
  51. package/mcps/mcsc/server.js +2 -0
  52. package/package.json +8 -2
  53. package/scripts/build-ecc-store-index.mjs +147 -0
  54. package/skills/basic/bdbmediastorm/SKILL.md +1 -1
  55. package/skills/basic/godmode-ui-ux/SKILL.md +2 -2
  56. package/skills/basic/godmode-ui-ux/references/anti-slop-rules.md +44 -40
  57. package/skills/basic/godmode-ui-ux/references/taste-archetypes.md +49 -0
  58. package/skills/basic/startcycle/SKILL.md +7 -0
  59. package/skills/basic/startcycle-graph/SKILL.md +25 -0
  60. package/skills/basic/startcycle-graph-user/SKILL.md +3 -0
  61. package/skills/basic/teamwork-preview/SKILL.md +2 -0
  62. package/skills/bdbrainstorm/SKILL.md +1 -0
  63. package/skills/global_config/agenttrail/LICENSE +21 -0
  64. package/skills/global_config/agenttrail/SKILL.md +48 -0
  65. package/skills/global_config/agenttrail/bin/agenttrail.mjs +1113 -0
  66. package/skills/global_config/agenttrail/public/index.html +662 -0
  67. package/skills/global_config/aos-project-init/SKILL.md +25 -5
  68. package/skills/global_config/aos-setup/SKILL.md +34 -1
  69. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +115 -1
  70. package/skills/global_config/archify/LICENSE +22 -0
  71. package/skills/global_config/archify/SKILL.md +140 -0
  72. package/skills/global_config/archify/THIRD_PARTY_NOTICES.md +69 -0
  73. package/skills/global_config/archify/assets/JetBrainsMono-OFL.txt +93 -0
  74. package/skills/global_config/archify/assets/template.html +14937 -0
  75. package/skills/global_config/archify/bin/archify.mjs +2139 -0
  76. package/skills/global_config/archify/bin/open-artifact.mjs +86 -0
  77. package/skills/global_config/archify/bin/preview.mjs +653 -0
  78. package/skills/global_config/archify/bin/visual-check.mjs +829 -0
  79. package/skills/global_config/archify/brand-marks/README.md +31 -0
  80. package/skills/global_config/archify/brand-marks/catalog.json +131 -0
  81. package/skills/global_config/archify/delta/architecture-delta.mjs +1232 -0
  82. package/skills/global_config/archify/examples/agent-run.lifecycle.json +60 -0
  83. package/skills/global_config/archify/examples/agent-tool-call.workflow.json +94 -0
  84. package/skills/global_config/archify/examples/async-job-roundtrip.sequence.json +61 -0
  85. package/skills/global_config/archify/examples/brand-aware-delivery.architecture.json +47 -0
  86. package/skills/global_config/archify/examples/cache-miss-request.sequence.json +82 -0
  87. package/skills/global_config/archify/examples/checkout-platform.base.architecture.json +31 -0
  88. package/skills/global_config/archify/examples/checkout-platform.head.architecture.json +31 -0
  89. package/skills/global_config/archify/examples/dataflow-product-analytics.html +15048 -0
  90. package/skills/global_config/archify/examples/deployment-release.lifecycle.json +49 -0
  91. package/skills/global_config/archify/examples/event-stream.dataflow.json +57 -0
  92. package/skills/global_config/archify/examples/incident-response.workflow.json +64 -0
  93. package/skills/global_config/archify/examples/lifecycle-agent-run.html +14983 -0
  94. package/skills/global_config/archify/examples/product-analytics.dataflow.json +76 -0
  95. package/skills/global_config/archify/examples/production-deployment.architecture.json +71 -0
  96. package/skills/global_config/archify/examples/release-delivery.workflow.json +62 -0
  97. package/skills/global_config/archify/examples/sequence-cache-miss-request.html +15063 -0
  98. package/skills/global_config/archify/examples/web-app-rendered.html +15012 -0
  99. package/skills/global_config/archify/examples/web-app.architecture.json +46 -0
  100. package/skills/global_config/archify/examples/workflow-agent-tool-call-rendered.html +15054 -0
  101. package/skills/global_config/archify/migrations/workflow-v2.mjs +279 -0
  102. package/skills/global_config/archify/package.json +39 -0
  103. package/skills/global_config/archify/recipes/scenarios.mjs +391 -0
  104. package/skills/global_config/archify/references/authoring-contract.md +247 -0
  105. package/skills/global_config/archify/references/brand-marks.md +65 -0
  106. package/skills/global_config/archify/references/delivery-contract.md +138 -0
  107. package/skills/global_config/archify/references/viewer-runtime.md +45 -0
  108. package/skills/global_config/archify/renderers/architecture/grid.mjs +62 -0
  109. package/skills/global_config/archify/renderers/architecture/render-architecture.mjs +1078 -0
  110. package/skills/global_config/archify/renderers/dataflow/README.md +104 -0
  111. package/skills/global_config/archify/renderers/dataflow/render-dataflow.mjs +483 -0
  112. package/skills/global_config/archify/renderers/lifecycle/README.md +115 -0
  113. package/skills/global_config/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
  114. package/skills/global_config/archify/renderers/sequence/README.md +114 -0
  115. package/skills/global_config/archify/renderers/sequence/render-sequence.mjs +464 -0
  116. package/skills/global_config/archify/renderers/shared/brand-marks.mjs +563 -0
  117. package/skills/global_config/archify/renderers/shared/cli.mjs +218 -0
  118. package/skills/global_config/archify/renderers/shared/desktop-readability.mjs +26 -0
  119. package/skills/global_config/archify/renderers/shared/diagnostics.mjs +160 -0
  120. package/skills/global_config/archify/renderers/shared/engineering-profiles.mjs +157 -0
  121. package/skills/global_config/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  122. package/skills/global_config/archify/renderers/shared/generated-validators.mjs +13 -0
  123. package/skills/global_config/archify/renderers/shared/geometry.mjs +1423 -0
  124. package/skills/global_config/archify/renderers/shared/i18n.mjs +595 -0
  125. package/skills/global_config/archify/renderers/shared/layout-report.mjs +40 -0
  126. package/skills/global_config/archify/renderers/shared/legend.mjs +217 -0
  127. package/skills/global_config/archify/renderers/shared/output-path.mjs +340 -0
  128. package/skills/global_config/archify/renderers/shared/repository-evidence.mjs +239 -0
  129. package/skills/global_config/archify/renderers/shared/repository-location.mjs +58 -0
  130. package/skills/global_config/archify/renderers/shared/text-fit.mjs +49 -0
  131. package/skills/global_config/archify/renderers/shared/utils.mjs +232 -0
  132. package/skills/global_config/archify/renderers/shared/validator.mjs +86 -0
  133. package/skills/global_config/archify/renderers/workflow/README.md +223 -0
  134. package/skills/global_config/archify/renderers/workflow/render-workflow.mjs +35 -0
  135. package/skills/global_config/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  136. package/skills/global_config/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  137. package/skills/global_config/archify/schemas/README.md +211 -0
  138. package/skills/global_config/archify/schemas/architecture.schema.json +178 -0
  139. package/skills/global_config/archify/schemas/common.schema.json +115 -0
  140. package/skills/global_config/archify/schemas/dataflow.schema.json +243 -0
  141. package/skills/global_config/archify/schemas/lifecycle.schema.json +266 -0
  142. package/skills/global_config/archify/schemas/sequence.schema.json +223 -0
  143. package/skills/global_config/archify/schemas/workflow.schema.json +428 -0
  144. package/skills/global_config/archify/scripts/check-render-output.mjs +916 -0
  145. package/skills/global_config/archify/scripts/generate-brand-marks.mjs +141 -0
  146. package/skills/global_config/archify/scripts/generate-validators.mjs +66 -0
  147. package/skills/global_config/archify/scripts/render-examples.mjs +26 -0
  148. package/skills/global_config/archify/skill-release.json +10 -0
  149. package/skills/global_config/ask-tim/SKILL.md +10 -0
  150. package/skills/global_config/bdb-security-audit/SKILL.md +109 -0
  151. package/skills/global_config/bdb-security-audit/references/audit-checklists.md +172 -0
  152. package/skills/global_config/bdb-security-audit/references/skylos-guide.md +139 -0
  153. package/skills/global_config/bdb-security-audit/references/xalgorix-bridge.md +110 -0
  154. package/skills/global_config/bdbdesignpro/SKILL.md +4 -4
  155. package/skills/global_config/bdbdesignpro/references/bdb-motion-tokens.md +52 -31
  156. package/skills/global_config/bdbresilience/SKILL.md +10 -0
  157. package/skills/global_config/brag/SKILL.md +179 -0
  158. package/skills/global_config/brag/assets/music/README.md +20 -0
  159. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-1-by-ende-dot-app.music-cues.json +3846 -0
  160. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-1-by-ende-dot-app.music-cues.md +35 -0
  161. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-10-by-ende-dot-app.music-cues.json +1826 -0
  162. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-10-by-ende-dot-app.music-cues.md +35 -0
  163. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-11-by-ende-dot-app.music-cues.json +2266 -0
  164. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-11-by-ende-dot-app.music-cues.md +35 -0
  165. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-12-by-ende-dot-app.music-cues.json +2806 -0
  166. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-12-by-ende-dot-app.music-cues.md +35 -0
  167. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-9-by-ende-dot-app.music-cues.json +2796 -0
  168. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-9-by-ende-dot-app.music-cues.md +35 -0
  169. package/skills/global_config/brag/assets/music/happy-beats-business-moves-vol-1-by-ende-dot-app.mp3 +0 -0
  170. package/skills/global_config/brag/assets/music/happy-beats-business-moves-vol-10-by-ende-dot-app.mp3 +0 -0
  171. package/skills/global_config/brag/assets/music/happy-beats-business-moves-vol-11-by-ende-dot-app.mp3 +0 -0
  172. package/skills/global_config/brag/assets/music/happy-beats-business-moves-vol-12-by-ende-dot-app.mp3 +0 -0
  173. package/skills/global_config/brag/assets/music/happy-beats-business-moves-vol-9-by-ende-dot-app.mp3 +0 -0
  174. package/skills/global_config/brag/assets/sfx/casino/card-fan-1.ogg +0 -0
  175. package/skills/global_config/brag/assets/sfx/casino/card-fan-2.ogg +0 -0
  176. package/skills/global_config/brag/assets/sfx/casino/card-place-1.ogg +0 -0
  177. package/skills/global_config/brag/assets/sfx/casino/card-place-2.ogg +0 -0
  178. package/skills/global_config/brag/assets/sfx/casino/card-place-3.ogg +0 -0
  179. package/skills/global_config/brag/assets/sfx/casino/card-place-4.ogg +0 -0
  180. package/skills/global_config/brag/assets/sfx/casino/card-shove-1.ogg +0 -0
  181. package/skills/global_config/brag/assets/sfx/casino/card-shove-2.ogg +0 -0
  182. package/skills/global_config/brag/assets/sfx/casino/card-shove-3.ogg +0 -0
  183. package/skills/global_config/brag/assets/sfx/casino/card-shove-4.ogg +0 -0
  184. package/skills/global_config/brag/assets/sfx/casino/card-shuffle.ogg +0 -0
  185. package/skills/global_config/brag/assets/sfx/casino/card-slide-1.ogg +0 -0
  186. package/skills/global_config/brag/assets/sfx/casino/card-slide-2.ogg +0 -0
  187. package/skills/global_config/brag/assets/sfx/casino/card-slide-3.ogg +0 -0
  188. package/skills/global_config/brag/assets/sfx/casino/card-slide-4.ogg +0 -0
  189. package/skills/global_config/brag/assets/sfx/casino/card-slide-5.ogg +0 -0
  190. package/skills/global_config/brag/assets/sfx/casino/card-slide-6.ogg +0 -0
  191. package/skills/global_config/brag/assets/sfx/casino/card-slide-7.ogg +0 -0
  192. package/skills/global_config/brag/assets/sfx/casino/card-slide-8.ogg +0 -0
  193. package/skills/global_config/brag/assets/sfx/casino/cards-pack-open-1.ogg +0 -0
  194. package/skills/global_config/brag/assets/sfx/casino/cards-pack-open-2.ogg +0 -0
  195. package/skills/global_config/brag/assets/sfx/casino/chip-lay-1.ogg +0 -0
  196. package/skills/global_config/brag/assets/sfx/casino/chip-lay-2.ogg +0 -0
  197. package/skills/global_config/brag/assets/sfx/casino/chip-lay-3.ogg +0 -0
  198. package/skills/global_config/brag/assets/sfx/casino/chips-collide-1.ogg +0 -0
  199. package/skills/global_config/brag/assets/sfx/casino/chips-collide-2.ogg +0 -0
  200. package/skills/global_config/brag/assets/sfx/casino/chips-collide-3.ogg +0 -0
  201. package/skills/global_config/brag/assets/sfx/casino/chips-collide-4.ogg +0 -0
  202. package/skills/global_config/brag/assets/sfx/casino/chips-handle-1.ogg +0 -0
  203. package/skills/global_config/brag/assets/sfx/casino/chips-handle-2.ogg +0 -0
  204. package/skills/global_config/brag/assets/sfx/casino/chips-handle-3.ogg +0 -0
  205. package/skills/global_config/brag/assets/sfx/casino/chips-handle-4.ogg +0 -0
  206. package/skills/global_config/brag/assets/sfx/casino/chips-handle-6.ogg +0 -0
  207. package/skills/global_config/brag/assets/sfx/casino/chips-stack-1.ogg +0 -0
  208. package/skills/global_config/brag/assets/sfx/casino/chips-stack-2.ogg +0 -0
  209. package/skills/global_config/brag/assets/sfx/casino/chips-stack-3.ogg +0 -0
  210. package/skills/global_config/brag/assets/sfx/casino/chips-stack-4.ogg +0 -0
  211. package/skills/global_config/brag/assets/sfx/casino/chips-stack-5.ogg +0 -0
  212. package/skills/global_config/brag/assets/sfx/casino/chips-stack-6.ogg +0 -0
  213. package/skills/global_config/brag/assets/sfx/casino/dice-grab-1.ogg +0 -0
  214. package/skills/global_config/brag/assets/sfx/casino/dice-grab-2.ogg +0 -0
  215. package/skills/global_config/brag/assets/sfx/casino/dice-shake-1.ogg +0 -0
  216. package/skills/global_config/brag/assets/sfx/casino/dice-shake-2.ogg +0 -0
  217. package/skills/global_config/brag/assets/sfx/casino/dice-shake-3.ogg +0 -0
  218. package/skills/global_config/brag/assets/sfx/casino/dice-throw-1.ogg +0 -0
  219. package/skills/global_config/brag/assets/sfx/casino/dice-throw-2.ogg +0 -0
  220. package/skills/global_config/brag/assets/sfx/casino/dice-throw-3.ogg +0 -0
  221. package/skills/global_config/brag/assets/sfx/casino/die-throw-1.ogg +0 -0
  222. package/skills/global_config/brag/assets/sfx/casino/die-throw-2.ogg +0 -0
  223. package/skills/global_config/brag/assets/sfx/casino/die-throw-3.ogg +0 -0
  224. package/skills/global_config/brag/assets/sfx/casino/die-throw-4.ogg +0 -0
  225. package/skills/global_config/brag/assets/sfx/impact/footstep_carpet_000.ogg +0 -0
  226. package/skills/global_config/brag/assets/sfx/impact/footstep_carpet_003.ogg +0 -0
  227. package/skills/global_config/brag/assets/sfx/impact/footstep_carpet_004.ogg +0 -0
  228. package/skills/global_config/brag/assets/sfx/impact/footstep_concrete_000.ogg +0 -0
  229. package/skills/global_config/brag/assets/sfx/impact/footstep_concrete_001.ogg +0 -0
  230. package/skills/global_config/brag/assets/sfx/impact/footstep_concrete_002.ogg +0 -0
  231. package/skills/global_config/brag/assets/sfx/impact/footstep_concrete_003.ogg +0 -0
  232. package/skills/global_config/brag/assets/sfx/impact/footstep_concrete_004.ogg +0 -0
  233. package/skills/global_config/brag/assets/sfx/impact/footstep_grass_000.ogg +0 -0
  234. package/skills/global_config/brag/assets/sfx/impact/footstep_grass_001.ogg +0 -0
  235. package/skills/global_config/brag/assets/sfx/impact/footstep_grass_002.ogg +0 -0
  236. package/skills/global_config/brag/assets/sfx/impact/footstep_grass_003.ogg +0 -0
  237. package/skills/global_config/brag/assets/sfx/impact/footstep_grass_004.ogg +0 -0
  238. package/skills/global_config/brag/assets/sfx/impact/footstep_snow_000.ogg +0 -0
  239. package/skills/global_config/brag/assets/sfx/impact/footstep_snow_001.ogg +0 -0
  240. package/skills/global_config/brag/assets/sfx/impact/footstep_snow_002.ogg +0 -0
  241. package/skills/global_config/brag/assets/sfx/impact/footstep_snow_003.ogg +0 -0
  242. package/skills/global_config/brag/assets/sfx/impact/footstep_snow_004.ogg +0 -0
  243. package/skills/global_config/brag/assets/sfx/impact/footstep_wood_000.ogg +0 -0
  244. package/skills/global_config/brag/assets/sfx/impact/footstep_wood_001.ogg +0 -0
  245. package/skills/global_config/brag/assets/sfx/impact/footstep_wood_002.ogg +0 -0
  246. package/skills/global_config/brag/assets/sfx/impact/footstep_wood_003.ogg +0 -0
  247. package/skills/global_config/brag/assets/sfx/impact/footstep_wood_004.ogg +0 -0
  248. package/skills/global_config/brag/assets/sfx/impact/impactBell_heavy_000.ogg +0 -0
  249. package/skills/global_config/brag/assets/sfx/impact/impactBell_heavy_003.ogg +0 -0
  250. package/skills/global_config/brag/assets/sfx/impact/impactBell_heavy_004.ogg +0 -0
  251. package/skills/global_config/brag/assets/sfx/impact/impactGeneric_light_000.ogg +0 -0
  252. package/skills/global_config/brag/assets/sfx/impact/impactGeneric_light_001.ogg +0 -0
  253. package/skills/global_config/brag/assets/sfx/impact/impactGeneric_light_002.ogg +0 -0
  254. package/skills/global_config/brag/assets/sfx/impact/impactGeneric_light_003.ogg +0 -0
  255. package/skills/global_config/brag/assets/sfx/impact/impactGeneric_light_004.ogg +0 -0
  256. package/skills/global_config/brag/assets/sfx/impact/impactGlass_heavy_002.ogg +0 -0
  257. package/skills/global_config/brag/assets/sfx/impact/impactGlass_light_001.ogg +0 -0
  258. package/skills/global_config/brag/assets/sfx/impact/impactGlass_light_002.ogg +0 -0
  259. package/skills/global_config/brag/assets/sfx/impact/impactGlass_light_003.ogg +0 -0
  260. package/skills/global_config/brag/assets/sfx/impact/impactGlass_medium_000.ogg +0 -0
  261. package/skills/global_config/brag/assets/sfx/impact/impactGlass_medium_002.ogg +0 -0
  262. package/skills/global_config/brag/assets/sfx/impact/impactGlass_medium_004.ogg +0 -0
  263. package/skills/global_config/brag/assets/sfx/impact/impactMetal_heavy_000.ogg +0 -0
  264. package/skills/global_config/brag/assets/sfx/impact/impactMetal_heavy_002.ogg +0 -0
  265. package/skills/global_config/brag/assets/sfx/impact/impactMetal_heavy_004.ogg +0 -0
  266. package/skills/global_config/brag/assets/sfx/impact/impactMetal_light_002.ogg +0 -0
  267. package/skills/global_config/brag/assets/sfx/impact/impactMetal_light_003.ogg +0 -0
  268. package/skills/global_config/brag/assets/sfx/impact/impactMetal_medium_000.ogg +0 -0
  269. package/skills/global_config/brag/assets/sfx/impact/impactMetal_medium_001.ogg +0 -0
  270. package/skills/global_config/brag/assets/sfx/impact/impactMetal_medium_002.ogg +0 -0
  271. package/skills/global_config/brag/assets/sfx/impact/impactMetal_medium_003.ogg +0 -0
  272. package/skills/global_config/brag/assets/sfx/impact/impactMetal_medium_004.ogg +0 -0
  273. package/skills/global_config/brag/assets/sfx/impact/impactMining_001.ogg +0 -0
  274. package/skills/global_config/brag/assets/sfx/impact/impactPlank_medium_000.ogg +0 -0
  275. package/skills/global_config/brag/assets/sfx/impact/impactPlank_medium_001.ogg +0 -0
  276. package/skills/global_config/brag/assets/sfx/impact/impactPlank_medium_002.ogg +0 -0
  277. package/skills/global_config/brag/assets/sfx/impact/impactPlank_medium_003.ogg +0 -0
  278. package/skills/global_config/brag/assets/sfx/impact/impactPlank_medium_004.ogg +0 -0
  279. package/skills/global_config/brag/assets/sfx/impact/impactPlate_heavy_000.ogg +0 -0
  280. package/skills/global_config/brag/assets/sfx/impact/impactPlate_heavy_001.ogg +0 -0
  281. package/skills/global_config/brag/assets/sfx/impact/impactPlate_heavy_002.ogg +0 -0
  282. package/skills/global_config/brag/assets/sfx/impact/impactPlate_heavy_003.ogg +0 -0
  283. package/skills/global_config/brag/assets/sfx/impact/impactPlate_heavy_004.ogg +0 -0
  284. package/skills/global_config/brag/assets/sfx/impact/impactPlate_light_000.ogg +0 -0
  285. package/skills/global_config/brag/assets/sfx/impact/impactPlate_light_001.ogg +0 -0
  286. package/skills/global_config/brag/assets/sfx/impact/impactPlate_light_002.ogg +0 -0
  287. package/skills/global_config/brag/assets/sfx/impact/impactPlate_light_003.ogg +0 -0
  288. package/skills/global_config/brag/assets/sfx/impact/impactPlate_light_004.ogg +0 -0
  289. package/skills/global_config/brag/assets/sfx/impact/impactPlate_medium_000.ogg +0 -0
  290. package/skills/global_config/brag/assets/sfx/impact/impactPlate_medium_001.ogg +0 -0
  291. package/skills/global_config/brag/assets/sfx/impact/impactPlate_medium_002.ogg +0 -0
  292. package/skills/global_config/brag/assets/sfx/impact/impactPlate_medium_003.ogg +0 -0
  293. package/skills/global_config/brag/assets/sfx/impact/impactPlate_medium_004.ogg +0 -0
  294. package/skills/global_config/brag/assets/sfx/impact/impactPunch_heavy_000.ogg +0 -0
  295. package/skills/global_config/brag/assets/sfx/impact/impactPunch_heavy_001.ogg +0 -0
  296. package/skills/global_config/brag/assets/sfx/impact/impactPunch_heavy_002.ogg +0 -0
  297. package/skills/global_config/brag/assets/sfx/impact/impactPunch_heavy_003.ogg +0 -0
  298. package/skills/global_config/brag/assets/sfx/impact/impactPunch_heavy_004.ogg +0 -0
  299. package/skills/global_config/brag/assets/sfx/impact/impactPunch_medium_000.ogg +0 -0
  300. package/skills/global_config/brag/assets/sfx/impact/impactPunch_medium_001.ogg +0 -0
  301. package/skills/global_config/brag/assets/sfx/impact/impactPunch_medium_002.ogg +0 -0
  302. package/skills/global_config/brag/assets/sfx/impact/impactPunch_medium_003.ogg +0 -0
  303. package/skills/global_config/brag/assets/sfx/impact/impactPunch_medium_004.ogg +0 -0
  304. package/skills/global_config/brag/assets/sfx/impact/impactSoft_heavy_000.ogg +0 -0
  305. package/skills/global_config/brag/assets/sfx/impact/impactSoft_heavy_001.ogg +0 -0
  306. package/skills/global_config/brag/assets/sfx/impact/impactSoft_heavy_002.ogg +0 -0
  307. package/skills/global_config/brag/assets/sfx/impact/impactSoft_heavy_003.ogg +0 -0
  308. package/skills/global_config/brag/assets/sfx/impact/impactSoft_heavy_004.ogg +0 -0
  309. package/skills/global_config/brag/assets/sfx/impact/impactSoft_medium_000.ogg +0 -0
  310. package/skills/global_config/brag/assets/sfx/impact/impactSoft_medium_001.ogg +0 -0
  311. package/skills/global_config/brag/assets/sfx/impact/impactSoft_medium_002.ogg +0 -0
  312. package/skills/global_config/brag/assets/sfx/impact/impactSoft_medium_003.ogg +0 -0
  313. package/skills/global_config/brag/assets/sfx/impact/impactSoft_medium_004.ogg +0 -0
  314. package/skills/global_config/brag/assets/sfx/impact/impactTin_medium_000.ogg +0 -0
  315. package/skills/global_config/brag/assets/sfx/impact/impactTin_medium_001.ogg +0 -0
  316. package/skills/global_config/brag/assets/sfx/impact/impactTin_medium_002.ogg +0 -0
  317. package/skills/global_config/brag/assets/sfx/impact/impactTin_medium_003.ogg +0 -0
  318. package/skills/global_config/brag/assets/sfx/impact/impactTin_medium_004.ogg +0 -0
  319. package/skills/global_config/brag/assets/sfx/impact/impactWood_heavy_000.ogg +0 -0
  320. package/skills/global_config/brag/assets/sfx/impact/impactWood_heavy_001.ogg +0 -0
  321. package/skills/global_config/brag/assets/sfx/impact/impactWood_heavy_002.ogg +0 -0
  322. package/skills/global_config/brag/assets/sfx/impact/impactWood_heavy_003.ogg +0 -0
  323. package/skills/global_config/brag/assets/sfx/impact/impactWood_heavy_004.ogg +0 -0
  324. package/skills/global_config/brag/assets/sfx/impact/impactWood_light_000.ogg +0 -0
  325. package/skills/global_config/brag/assets/sfx/impact/impactWood_light_001.ogg +0 -0
  326. package/skills/global_config/brag/assets/sfx/impact/impactWood_light_002.ogg +0 -0
  327. package/skills/global_config/brag/assets/sfx/impact/impactWood_light_003.ogg +0 -0
  328. package/skills/global_config/brag/assets/sfx/impact/impactWood_light_004.ogg +0 -0
  329. package/skills/global_config/brag/assets/sfx/impact/impactWood_medium_000.ogg +0 -0
  330. package/skills/global_config/brag/assets/sfx/impact/impactWood_medium_001.ogg +0 -0
  331. package/skills/global_config/brag/assets/sfx/impact/impactWood_medium_002.ogg +0 -0
  332. package/skills/global_config/brag/assets/sfx/impact/impactWood_medium_003.ogg +0 -0
  333. package/skills/global_config/brag/assets/sfx/impact/impactWood_medium_004.ogg +0 -0
  334. package/skills/global_config/brag/assets/sfx/interface/bong_001.ogg +0 -0
  335. package/skills/global_config/brag/assets/sfx/interface/click_001.ogg +0 -0
  336. package/skills/global_config/brag/assets/sfx/interface/click_002.ogg +0 -0
  337. package/skills/global_config/brag/assets/sfx/interface/click_003.ogg +0 -0
  338. package/skills/global_config/brag/assets/sfx/interface/click_004.ogg +0 -0
  339. package/skills/global_config/brag/assets/sfx/interface/click_005.ogg +0 -0
  340. package/skills/global_config/brag/assets/sfx/interface/drop_001.ogg +0 -0
  341. package/skills/global_config/brag/assets/sfx/interface/drop_002.ogg +0 -0
  342. package/skills/global_config/brag/assets/sfx/interface/drop_003.ogg +0 -0
  343. package/skills/global_config/brag/assets/sfx/interface/error_005.ogg +0 -0
  344. package/skills/global_config/brag/assets/sfx/interface/error_006.ogg +0 -0
  345. package/skills/global_config/brag/assets/sfx/interface/glitch_002.ogg +0 -0
  346. package/skills/global_config/brag/assets/sfx/interface/glitch_004.ogg +0 -0
  347. package/skills/global_config/brag/assets/sfx/interface/select_008.ogg +0 -0
  348. package/skills/global_config/brag/assets/sfx/interface/switch_001.ogg +0 -0
  349. package/skills/global_config/brag/assets/sfx/interface/switch_002.ogg +0 -0
  350. package/skills/global_config/brag/assets/sfx/interface/switch_004.ogg +0 -0
  351. package/skills/global_config/brag/assets/sfx/interface/switch_005.ogg +0 -0
  352. package/skills/global_config/brag/assets/sfx/interface/switch_006.ogg +0 -0
  353. package/skills/global_config/brag/assets/sfx/interface/switch_007.ogg +0 -0
  354. package/skills/global_config/brag/assets/sfx/keyboard/keypress-001.wav +0 -0
  355. package/skills/global_config/brag/assets/sfx/keyboard/keypress-002.wav +0 -0
  356. package/skills/global_config/brag/assets/sfx/keyboard/keypress-003.wav +0 -0
  357. package/skills/global_config/brag/assets/sfx/keyboard/keypress-004.wav +0 -0
  358. package/skills/global_config/brag/assets/sfx/keyboard/keypress-005.wav +0 -0
  359. package/skills/global_config/brag/assets/sfx/keyboard/keypress-006.wav +0 -0
  360. package/skills/global_config/brag/assets/sfx/keyboard/keypress-007.wav +0 -0
  361. package/skills/global_config/brag/assets/sfx/keyboard/keypress-008.wav +0 -0
  362. package/skills/global_config/brag/assets/sfx/keyboard/keypress-009.wav +0 -0
  363. package/skills/global_config/brag/assets/sfx/keyboard/keypress-010.wav +0 -0
  364. package/skills/global_config/brag/assets/sfx/keyboard/keypress-011.wav +0 -0
  365. package/skills/global_config/brag/assets/sfx/keyboard/keypress-012.wav +0 -0
  366. package/skills/global_config/brag/assets/sfx/keyboard/keypress-013.wav +0 -0
  367. package/skills/global_config/brag/assets/sfx/keyboard/keypress-014.wav +0 -0
  368. package/skills/global_config/brag/assets/sfx/keyboard/keypress-015.wav +0 -0
  369. package/skills/global_config/brag/assets/sfx/keyboard/keypress-016.wav +0 -0
  370. package/skills/global_config/brag/assets/sfx/keyboard/keypress-017.wav +0 -0
  371. package/skills/global_config/brag/assets/sfx/keyboard/keypress-018.wav +0 -0
  372. package/skills/global_config/brag/assets/sfx/keyboard/keypress-019.wav +0 -0
  373. package/skills/global_config/brag/assets/sfx/keyboard/keypress-020.wav +0 -0
  374. package/skills/global_config/brag/assets/sfx/keyboard/keypress-021.wav +0 -0
  375. package/skills/global_config/brag/assets/sfx/keyboard/keypress-022.wav +0 -0
  376. package/skills/global_config/brag/assets/sfx/keyboard/keypress-023.wav +0 -0
  377. package/skills/global_config/brag/assets/sfx/keyboard/keypress-024.wav +0 -0
  378. package/skills/global_config/brag/assets/sfx/keyboard/keypress-025.wav +0 -0
  379. package/skills/global_config/brag/assets/sfx/keyboard/keypress-026.wav +0 -0
  380. package/skills/global_config/brag/assets/sfx/keyboard/keypress-027.wav +0 -0
  381. package/skills/global_config/brag/assets/sfx/keyboard/keypress-028.wav +0 -0
  382. package/skills/global_config/brag/assets/sfx/keyboard/keypress-029.wav +0 -0
  383. package/skills/global_config/brag/assets/sfx/keyboard/keypress-030.wav +0 -0
  384. package/skills/global_config/brag/assets/sfx/keyboard/keypress-031.wav +0 -0
  385. package/skills/global_config/brag/assets/sfx/keyboard/keypress-032.wav +0 -0
  386. package/skills/global_config/brag/assets/sfx/sfx-analysis.json +15584 -0
  387. package/skills/global_config/brag/assets/sfx/sfx-analysis.md +75 -0
  388. package/skills/global_config/brag/assets/sfx/ui/click1.ogg +0 -0
  389. package/skills/global_config/brag/assets/sfx/ui/click2.ogg +0 -0
  390. package/skills/global_config/brag/assets/sfx/ui/click3.ogg +0 -0
  391. package/skills/global_config/brag/assets/sfx/ui/click4.ogg +0 -0
  392. package/skills/global_config/brag/assets/sfx/ui/click5.ogg +0 -0
  393. package/skills/global_config/brag/assets/sfx/ui/mouseclick1.ogg +0 -0
  394. package/skills/global_config/brag/assets/sfx/ui/rollover1.ogg +0 -0
  395. package/skills/global_config/brag/assets/sfx/ui/rollover2.ogg +0 -0
  396. package/skills/global_config/brag/assets/sfx/ui/rollover4.ogg +0 -0
  397. package/skills/global_config/brag/assets/sfx/ui/rollover5.ogg +0 -0
  398. package/skills/global_config/brag/assets/sfx/ui/switch1.ogg +0 -0
  399. package/skills/global_config/brag/assets/sfx/ui/switch10.ogg +0 -0
  400. package/skills/global_config/brag/assets/sfx/ui/switch11.ogg +0 -0
  401. package/skills/global_config/brag/assets/sfx/ui/switch12.ogg +0 -0
  402. package/skills/global_config/brag/assets/sfx/ui/switch13.ogg +0 -0
  403. package/skills/global_config/brag/assets/sfx/ui/switch14.ogg +0 -0
  404. package/skills/global_config/brag/assets/sfx/ui/switch15.ogg +0 -0
  405. package/skills/global_config/brag/assets/sfx/ui/switch16.ogg +0 -0
  406. package/skills/global_config/brag/assets/sfx/ui/switch17.ogg +0 -0
  407. package/skills/global_config/brag/assets/sfx/ui/switch18.ogg +0 -0
  408. package/skills/global_config/brag/assets/sfx/ui/switch19.ogg +0 -0
  409. package/skills/global_config/brag/assets/sfx/ui/switch2.ogg +0 -0
  410. package/skills/global_config/brag/assets/sfx/ui/switch20.ogg +0 -0
  411. package/skills/global_config/brag/assets/sfx/ui/switch21.ogg +0 -0
  412. package/skills/global_config/brag/assets/sfx/ui/switch22.ogg +0 -0
  413. package/skills/global_config/brag/assets/sfx/ui/switch23.ogg +0 -0
  414. package/skills/global_config/brag/assets/sfx/ui/switch24.ogg +0 -0
  415. package/skills/global_config/brag/assets/sfx/ui/switch25.ogg +0 -0
  416. package/skills/global_config/brag/assets/sfx/ui/switch26.ogg +0 -0
  417. package/skills/global_config/brag/assets/sfx/ui/switch27.ogg +0 -0
  418. package/skills/global_config/brag/assets/sfx/ui/switch28.ogg +0 -0
  419. package/skills/global_config/brag/assets/sfx/ui/switch29.ogg +0 -0
  420. package/skills/global_config/brag/assets/sfx/ui/switch3.ogg +0 -0
  421. package/skills/global_config/brag/assets/sfx/ui/switch30.ogg +0 -0
  422. package/skills/global_config/brag/assets/sfx/ui/switch31.ogg +0 -0
  423. package/skills/global_config/brag/assets/sfx/ui/switch32.ogg +0 -0
  424. package/skills/global_config/brag/assets/sfx/ui/switch33.ogg +0 -0
  425. package/skills/global_config/brag/assets/sfx/ui/switch34.ogg +0 -0
  426. package/skills/global_config/brag/assets/sfx/ui/switch35.ogg +0 -0
  427. package/skills/global_config/brag/assets/sfx/ui/switch36.ogg +0 -0
  428. package/skills/global_config/brag/assets/sfx/ui/switch37.ogg +0 -0
  429. package/skills/global_config/brag/assets/sfx/ui/switch38.ogg +0 -0
  430. package/skills/global_config/brag/assets/sfx/ui/switch4.ogg +0 -0
  431. package/skills/global_config/brag/assets/sfx/ui/switch5.ogg +0 -0
  432. package/skills/global_config/brag/assets/sfx/ui/switch6.ogg +0 -0
  433. package/skills/global_config/brag/assets/sfx/ui/switch7.ogg +0 -0
  434. package/skills/global_config/brag/assets/sfx/ui/switch8.ogg +0 -0
  435. package/skills/global_config/brag/assets/sfx/ui/switch9.ogg +0 -0
  436. package/skills/global_config/brag/references/audio.md +307 -0
  437. package/skills/global_config/brag/references/step-1-inspect.md +133 -0
  438. package/skills/global_config/brag/references/step-2-plan.md +234 -0
  439. package/skills/global_config/brag/references/step-3-compose.md +210 -0
  440. package/skills/global_config/brag/references/step-4-deliver.md +168 -0
  441. package/skills/global_config/brag/references/tones.md +231 -0
  442. package/skills/global_config/brag/scripts/analyze_music_cues.py +309 -0
  443. package/skills/global_config/brag/scripts/pyproject.toml +14 -0
  444. package/skills/global_config/brag/scripts/uv.lock +819 -0
  445. package/skills/global_config/deja-memory/SKILL.md +42 -0
  446. package/skills/global_config/editable-design/LICENSE +202 -0
  447. package/skills/global_config/editable-design/README.md +34 -0
  448. package/skills/global_config/editable-design/SKILL.md +630 -0
  449. package/skills/global_config/editable-design/THIRD_PARTY_NOTICES.md +10 -0
  450. package/skills/global_config/editable-design/agents/openai.yaml +4 -0
  451. package/skills/global_config/editable-design/assets/editor/layer-editor.css +262 -0
  452. package/skills/global_config/editable-design/assets/editor/layer-editor.js +1929 -0
  453. package/skills/global_config/editable-design/assets/poster-starter/README.md +92 -0
  454. package/skills/global_config/editable-design/assets/poster-starter/assets/.gitkeep +0 -0
  455. package/skills/global_config/editable-design/assets/poster-starter/index.html +91 -0
  456. package/skills/global_config/editable-design/assets/poster-starter/poster.json +6 -0
  457. package/skills/global_config/editable-design/assets/poster-starter/reference/.gitkeep +1 -0
  458. package/skills/global_config/editable-design/assets/replay/icons/assets.png +0 -0
  459. package/skills/global_config/editable-design/assets/replay/icons/html.png +0 -0
  460. package/skills/global_config/editable-design/assets/replay/icons/input.png +0 -0
  461. package/skills/global_config/editable-design/assets/replay/icons/layers.png +0 -0
  462. package/skills/global_config/editable-design/assets/replay/icons/plan.png +0 -0
  463. package/skills/global_config/editable-design/assets/replay/icons/reference.png +0 -0
  464. package/skills/global_config/editable-design/assets/replay/icons/review.png +0 -0
  465. package/skills/global_config/editable-design/assets/replay/index.html +59 -0
  466. package/skills/global_config/editable-design/assets/replay/replay-motion.js +410 -0
  467. package/skills/global_config/editable-design/assets/replay/replay.css +380 -0
  468. package/skills/global_config/editable-design/assets/replay/replay.js +374 -0
  469. package/skills/global_config/editable-design/font-kit/package-lock.json +115 -0
  470. package/skills/global_config/editable-design/font-kit/package.json +19 -0
  471. package/skills/global_config/editable-design/pack.sh +20 -0
  472. package/skills/global_config/editable-design/references/asset-architecture.md +72 -0
  473. package/skills/global_config/editable-design/references/editor-pitfalls.md +70 -0
  474. package/skills/global_config/editable-design/references/editor-runtime.md +131 -0
  475. package/skills/global_config/editable-design/references/font-system.md +89 -0
  476. package/skills/global_config/editable-design/references/imagery.md +145 -0
  477. package/skills/global_config/editable-design/references/layout-typography.md +116 -0
  478. package/skills/global_config/editable-design/references/replay-contract.md +169 -0
  479. package/skills/global_config/editable-design/scripts/_browser.mjs +247 -0
  480. package/skills/global_config/editable-design/scripts/bake.mjs +87 -0
  481. package/skills/global_config/editable-design/scripts/build-replay.mjs +316 -0
  482. package/skills/global_config/editable-design/scripts/check-contract.mjs +293 -0
  483. package/skills/global_config/editable-design/scripts/check-fonts.sh +252 -0
  484. package/skills/global_config/editable-design/scripts/check-poster.sh +70 -0
  485. package/skills/global_config/editable-design/scripts/doctor.sh +78 -0
  486. package/skills/global_config/editable-design/scripts/explode.mjs +115 -0
  487. package/skills/global_config/editable-design/scripts/font-kit.mjs +187 -0
  488. package/skills/global_config/editable-design/scripts/import-asset.sh +23 -0
  489. package/skills/global_config/editable-design/scripts/init-poster.sh +64 -0
  490. package/skills/global_config/editable-design/scripts/install-font-kit.sh +13 -0
  491. package/skills/global_config/editable-design/scripts/package-lock.json +334 -0
  492. package/skills/global_config/editable-design/scripts/package.json +14 -0
  493. package/skills/global_config/editable-design/scripts/remove-chroma-key.py +201 -0
  494. package/skills/global_config/editable-design/scripts/render-poster.sh +202 -0
  495. package/skills/global_config/editable-design/scripts/trace.sh +159 -0
  496. package/skills/global_config/editable-design/scripts/verify-replay.mjs +97 -0
  497. package/skills/global_config/editable-design/scripts/verify.mjs +782 -0
  498. package/skills/global_config/editable-design/scripts/wire-editor.mjs +104 -0
  499. package/skills/global_config/editable-design/tests/fixtures/small-explode/index.html +86 -0
  500. package/skills/global_config/mcsc/SKILL.md +60 -0
  501. package/skills/global_config/plan-canvas/SKILL.md +8 -0
  502. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +31 -6
  503. package/skills/global_config/plan-canvas/scripts/plan-canvas.js +4 -2
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ copyFileSync,
4
+ cpSync,
5
+ existsSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ readdirSync,
9
+ statSync,
10
+ writeFileSync,
11
+ } from 'node:fs';
12
+ import { dirname, extname, join, relative, resolve, sep } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
16
+ const args = process.argv.slice(2);
17
+ const projectArg = args.find((arg) => !arg.startsWith('--')) || process.cwd();
18
+ const outIndex = args.indexOf('--out');
19
+ const projectRoot = resolve(projectArg);
20
+ const outDir = resolve(outIndex >= 0 && args[outIndex + 1] ? args[outIndex + 1] : join(projectRoot, 'replay'));
21
+ const templateCandidates = [
22
+ join(scriptDir, 'replay-assets'),
23
+ resolve(scriptDir, '../assets/replay'),
24
+ ];
25
+ const templateDir = templateCandidates.find((path) => existsSync(join(path, 'index.html')));
26
+
27
+ if (!templateDir) {
28
+ console.error('Replay template is unavailable. Re-run poster initialization or use the skill-local builder.');
29
+ process.exit(2);
30
+ }
31
+ if (!existsSync(join(projectRoot, 'index.html'))) {
32
+ console.error(`No poster index.html found in ${projectRoot}`);
33
+ process.exit(2);
34
+ }
35
+
36
+ const posix = (value) => value.split(sep).join('/');
37
+ const read = (path, fallback = '') => existsSync(path) ? readFileSync(path, 'utf8') : fallback;
38
+ const readJSON = (path, fallback = {}) => {
39
+ try { return JSON.parse(read(path)); } catch { return fallback; }
40
+ };
41
+ const firstExisting = (paths) => paths.find((path) => existsSync(path));
42
+ const projectPath = (...parts) => join(projectRoot, ...parts);
43
+ const browserPath = (path, query = '') => path ? `../${posix(relative(projectRoot, path))}${query}` : '';
44
+
45
+ function filesIn(path, extensions = []) {
46
+ if (!existsSync(path)) return [];
47
+ return readdirSync(path)
48
+ .filter((name) => !name.startsWith('.') && (!extensions.length || extensions.includes(extname(name).toLowerCase())))
49
+ .map((name) => join(path, name))
50
+ .filter((file) => statSync(file).isFile())
51
+ .sort();
52
+ }
53
+
54
+ function markdownSections(markdown) {
55
+ const sections = [];
56
+ const lines = markdown.split(/\r?\n/);
57
+ let current = null;
58
+ for (const line of lines) {
59
+ const match = line.match(/^##\s+(.+?)\s*$/);
60
+ if (match) {
61
+ if (current) sections.push({ ...current, body: current.lines.join('\n').trim() });
62
+ current = { title: match[1].trim(), lines: [] };
63
+ } else if (current) {
64
+ current.lines.push(line);
65
+ }
66
+ }
67
+ if (current) sections.push({ ...current, body: current.lines.join('\n').trim() });
68
+ return sections;
69
+ }
70
+
71
+ function findSection(sections, key) {
72
+ if (!key) return null;
73
+ const exact = sections.find((section) => section.title.toLowerCase() === String(key).toLowerCase());
74
+ if (exact) return exact;
75
+ const normalized = String(key).toLowerCase().replace(/[^a-z0-9]+/g, '');
76
+ return sections.find((section) => section.title.toLowerCase().replace(/[^a-z0-9]+/g, '') === normalized)
77
+ || sections.find((section) => section.title.toLowerCase().includes(String(key).toLowerCase()));
78
+ }
79
+
80
+ function traceEvents() {
81
+ return read(projectPath('.trace', 'events.jsonl'))
82
+ .split(/\r?\n/)
83
+ .filter(Boolean)
84
+ .flatMap((line) => { try { return [JSON.parse(line)]; } catch { return []; } });
85
+ }
86
+
87
+ const manifest = readJSON(projectPath('.trace', 'manifest.json'));
88
+ const poster = readJSON(projectPath('poster.json'));
89
+ const assetPlanPath = firstExisting([
90
+ projectPath('asset-plan.json'),
91
+ projectPath('.trace', 'artifacts', 'P11_asset_plan', 'asset-plan.json'),
92
+ ]);
93
+ const assetPlanRaw = assetPlanPath ? read(assetPlanPath) : '';
94
+ const assetPlan = assetPlanPath ? readJSON(assetPlanPath) : {};
95
+ const promptPath = firstExisting([
96
+ projectPath('prompts.md'),
97
+ projectPath('.trace', 'artifacts', 'P12_generate_imagery', 'prompts.md'),
98
+ ]);
99
+ const promptsRaw = promptPath ? read(promptPath) : '';
100
+ const promptSections = markdownSections(promptsRaw);
101
+ const briefPath = firstExisting([
102
+ projectPath('brief.md'),
103
+ projectPath('.trace', 'artifacts', 'P01_input', 'brief.md'),
104
+ ]);
105
+ const briefRaw = briefPath ? read(briefPath).trim() : '';
106
+ const compositionPromptPath = firstExisting([
107
+ projectPath('reference', 'composition-prompt.md'),
108
+ projectPath('reference', 'reference-prompt.md'),
109
+ projectPath('.trace', 'artifacts', 'P02_reference', 'composition-prompt.md'),
110
+ ]);
111
+ const compositionSection = promptSections.find((section) => /composition reference|art-directed composition/i.test(section.title));
112
+ const compositionPrompt = compositionPromptPath ? read(compositionPromptPath).trim() : (compositionSection?.body || '');
113
+ const referenceImage = firstExisting([
114
+ projectPath('reference', 'composition-reference.png'),
115
+ projectPath('reference', 'art-direction.png'),
116
+ ...filesIn(projectPath('reference'), ['.png', '.jpg', '.jpeg', '.webp']),
117
+ ]);
118
+ const notesPath = firstExisting([
119
+ projectPath('design-plan.md'),
120
+ projectPath('reference', 'composition-notes.md'),
121
+ projectPath('zones-plan.md'),
122
+ projectPath('.trace', 'artifacts', 'P11_asset_plan', 'design-plan.md'),
123
+ projectPath('.trace', 'artifacts', 'P11_asset_plan', 'zones-plan.md'),
124
+ ]);
125
+ const notesRaw = notesPath ? read(notesPath).trim() : '';
126
+ const reviewPath = firstExisting([
127
+ projectPath('render-review.md'),
128
+ projectPath('.trace', 'artifacts', 'P16_read_render', 'render-review.md'),
129
+ ]);
130
+ const reviewRaw = reviewPath ? read(reviewPath).trim() : '';
131
+ const indexRaw = read(projectPath('index.html'));
132
+ const renderPath = firstExisting([
133
+ projectPath('out', 'poster.png'),
134
+ ...filesIn(projectPath('out'), ['.png', '.jpg', '.jpeg', '.webp']),
135
+ ]);
136
+ const layersPath = firstExisting([
137
+ projectPath('layers.html'),
138
+ projectPath('editor.html'),
139
+ ]);
140
+ const events = traceEvents();
141
+ const assetFiles = filesIn(projectPath('assets'), ['.png', '.jpg', '.jpeg', '.webp']);
142
+
143
+ function eventTime(match) {
144
+ return events.find(match)?.ts || '';
145
+ }
146
+
147
+ const startedAt = events.find((event) => event.type === 'run_start')?.ts || manifest.created_at || '';
148
+ function timeLabel(match) {
149
+ const stamp = eventTime(match);
150
+ if (!startedAt || !stamp) return '';
151
+ const seconds = Math.max(0, Math.round((new Date(stamp) - new Date(startedAt)) / 1000));
152
+ const minutes = Math.floor(seconds / 60);
153
+ return `T+${String(minutes).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;
154
+ }
155
+
156
+ function findAssetFile(id, index) {
157
+ if (id) {
158
+ const exact = assetFiles.find((path) => {
159
+ const stem = path.slice(0, -extname(path).length).split(sep).pop();
160
+ return stem.toLowerCase() === String(id).toLowerCase();
161
+ });
162
+ if (exact) return exact;
163
+ const loose = assetFiles.find((path) => path.toLowerCase().includes(String(id).toLowerCase()));
164
+ if (loose) return loose;
165
+ }
166
+ return assetFiles[index] || null;
167
+ }
168
+
169
+ let assetItems = [];
170
+ if (Array.isArray(assetPlan.assets) && assetPlan.assets.length) {
171
+ assetItems = assetPlan.assets.map((asset, index) => {
172
+ const promptKey = asset.prompt || asset.prompt_id || asset.id;
173
+ const section = findSection(promptSections, promptKey) || findSection(promptSections, asset.id);
174
+ const file = findAssetFile(asset.id, index);
175
+ return {
176
+ id: asset.id || `asset-${index + 1}`,
177
+ title: asset.label || asset.id || section?.title || `Asset ${index + 1}`,
178
+ promptId: promptKey || '',
179
+ prompt: section?.body || '',
180
+ src: browserPath(file),
181
+ source: file ? posix(relative(projectRoot, file)) : '',
182
+ contain: /cutout|relief|map|diagram|logo/i.test(`${asset.form || ''} ${asset.id || ''}`),
183
+ };
184
+ });
185
+ } else {
186
+ const shippingSections = promptSections.filter((section) => !/composition reference|art-directed composition/i.test(section.title));
187
+ assetItems = assetFiles.map((file, index) => ({
188
+ id: file.slice(0, -extname(file).length).split(sep).pop(),
189
+ title: shippingSections[index]?.title || file.split(sep).pop(),
190
+ promptId: shippingSections[index]?.title || '',
191
+ prompt: shippingSections[index]?.body || '',
192
+ src: browserPath(file),
193
+ source: posix(relative(projectRoot, file)),
194
+ contain: /cutout|relief|map|diagram|logo/i.test(file),
195
+ }));
196
+ }
197
+
198
+ const layerIds = [...indexRaw.matchAll(/data-layer-id\s*=\s*["']([^"']+)["']/g)].map((match) => match[1]);
199
+ const groupIds = [...indexRaw.matchAll(/data-explode-group\s*=\s*["']([^"']+)["']/g)].map((match) => match[1]);
200
+ const uniqueLayers = new Set(layerIds);
201
+ const uniqueGroups = new Set(groupIds);
202
+ const layersRaw = layersPath && layersPath.endsWith('layers.html') ? read(layersPath) : '';
203
+ const componentCount = layersRaw ? (layersRaw.match(/class=["'][^"']*hf-exploded-item\b/g) || []).length : 0;
204
+ const mainMatch = indexRaw.match(/<main\b[\s\S]*?<\/main>/i);
205
+ const codeExcerpt = (mainMatch?.[0] || indexRaw).slice(0, 5200).trim();
206
+ const reviewBullets = [...reviewRaw.matchAll(/^\s*-\s+(.+)$/gm)].map((match) => match[1].trim());
207
+ const explicitPasses = (reviewRaw.match(/^##\s+Pass\b/gmi) || []).length;
208
+ const renderPasses = events.filter((event) => event.type === 'exec_done' && /^P15_render/.test(event.step || '')).length;
209
+ const passCount = explicitPasses || Math.max(1, renderPasses);
210
+ const canvas = poster.canvas?.width && poster.canvas?.height ? `${poster.canvas.width} × ${poster.canvas.height}` : 'fixed canvas';
211
+ const documentTitle = (indexRaw.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1] || '').replace(/<[^>]+>/g, '').trim();
212
+ const title = manifest.label || documentTitle || projectRoot.split(sep).pop();
213
+
214
+ const stages = [
215
+ {
216
+ id: 'input', order: '01', kind: 'input', phase: 'think', title: '需求输入',
217
+ statusLabel: briefRaw ? '原始记录' : '记录缺失',
218
+ raw: briefRaw,
219
+ fallbackLabel: briefRaw ? '' : manifest.label || '',
220
+ attachments: filesIn(projectPath('input')).map((path) => posix(relative(projectRoot, path))),
221
+ timeLabel: timeLabel((event) => event.type === 'run_start'),
222
+ artifactLabel: briefRaw ? '1 个原始 Prompt' : '仅 trace label',
223
+ sources: briefPath ? [posix(relative(projectRoot, briefPath))] : ['missing: brief.md'],
224
+ },
225
+ {
226
+ id: 'reference', order: '02', kind: 'reference', phase: 'think', title: '视觉参考',
227
+ statusLabel: compositionPrompt && referenceImage ? '已确定' : '证据不完整',
228
+ prompt: compositionPrompt,
229
+ promptSource: compositionPromptPath ? posix(relative(projectRoot, compositionPromptPath)) : (compositionSection ? 'prompts.md / composition reference' : ''),
230
+ preview: browserPath(referenceImage),
231
+ timeLabel: timeLabel((event) => /P11_reference|P02_reference/.test(event.step || '')),
232
+ artifactLabel: referenceImage ? '1 个参考结果' : '无参考图',
233
+ sources: [compositionPromptPath, referenceImage].filter(Boolean).map((path) => posix(relative(projectRoot, path))).concat(compositionSection && !compositionPromptPath ? ['prompts.md / composition reference'] : []),
234
+ },
235
+ {
236
+ id: 'plan', order: '03', kind: 'plan', phase: 'think', title: '设计规划',
237
+ statusLabel: notesRaw || assetPlanRaw ? '已决策' : '记录缺失',
238
+ raw: notesRaw || assetPlanRaw,
239
+ assetPlanRaw,
240
+ architecture: assetPlan.architecture || assetPlan.type || '',
241
+ planTitle: assetPlan.architecture || (notesPath ? notesPath.split(sep).pop() : '版式与信息层级'),
242
+ slots: Array.isArray(assetPlan.assets) ? assetPlan.assets : [],
243
+ timeLabel: timeLabel((event) => /P09_canvas|P11_asset_plan/.test(event.step || '')),
244
+ artifactLabel: `${Array.isArray(assetPlan.assets) ? assetPlan.assets.length : 0} 个视觉槽位`,
245
+ sources: [notesPath, assetPlanPath].filter(Boolean).map((path) => posix(relative(projectRoot, path))),
246
+ },
247
+ {
248
+ id: 'assets', order: '04', kind: 'assets', phase: 'make', title: '素材生成',
249
+ statusLabel: assetItems.length ? '已生成' : '无生成素材',
250
+ items: assetItems,
251
+ raw: promptsRaw,
252
+ timeLabel: timeLabel((event) => /P12_generate_imagery|P21_bottom_scene/.test(event.step || '')),
253
+ artifactLabel: `${assetItems.length} 个素材`,
254
+ sources: [promptPath, ...assetFiles].filter(Boolean).map((path) => posix(relative(projectRoot, path))),
255
+ },
256
+ {
257
+ id: 'html', order: '05', kind: 'html', phase: 'make', title: 'HTML 构建',
258
+ statusLabel: renderPath ? '已渲染' : '已编码',
259
+ code: codeExcerpt,
260
+ fullCode: indexRaw.slice(0, 80000),
261
+ render: browserPath(renderPath),
262
+ canvas,
263
+ layerCount: uniqueLayers.size,
264
+ groupCount: uniqueGroups.size,
265
+ timeLabel: timeLabel((event) => /P14_cleanup|P15_render/.test(event.step || '')),
266
+ artifactLabel: renderPath ? 'HTML + PNG' : 'HTML',
267
+ sources: ['index.html'].concat(renderPath ? [posix(relative(projectRoot, renderPath))] : []),
268
+ },
269
+ {
270
+ id: 'review', order: '06', kind: 'review', phase: 'prove', title: 'Review 反馈',
271
+ statusLabel: reviewRaw ? '已修正' : '记录缺失',
272
+ raw: reviewRaw,
273
+ bullets: reviewBullets,
274
+ passCount,
275
+ timeLabel: timeLabel((event) => /P16_read_render|P16_optimization|P25_read_render/.test(event.step || '')),
276
+ artifactLabel: `${passCount} 轮 Review`,
277
+ sources: reviewPath ? [posix(relative(projectRoot, reviewPath)), '.trace/events.jsonl'] : ['missing: render-review.md', '.trace/events.jsonl'],
278
+ },
279
+ {
280
+ id: 'layers', order: '07', kind: 'layers', phase: 'prove', title: '分层展示',
281
+ statusLabel: layersPath ? '已验证' : '未生成',
282
+ layerCount: uniqueLayers.size,
283
+ groupCount: uniqueGroups.size,
284
+ componentCount,
285
+ preview: browserPath(layersPath),
286
+ previewSource: layersPath ? posix(relative(projectRoot, layersPath)) : '',
287
+ timeLabel: timeLabel((event) => /P17_editor|P20_editor|P26_editor/.test(event.step || '')),
288
+ artifactLabel: `${uniqueLayers.size} 图层 · ${uniqueGroups.size} 组`,
289
+ sources: layersPath ? [posix(relative(projectRoot, layersPath)), 'index.html / data-layer-id'] : ['missing: editor.html or layers.html'],
290
+ },
291
+ ];
292
+
293
+ const replay = {
294
+ schema: 'poster-replay/v1',
295
+ generatedAt: new Date().toISOString(),
296
+ project: {
297
+ title,
298
+ slug: projectRoot.split(sep).pop(),
299
+ status: manifest.status || 'ok',
300
+ canvas,
301
+ },
302
+ stages,
303
+ };
304
+
305
+ mkdirSync(outDir, { recursive: true });
306
+ for (const file of ['index.html', 'replay.css', 'replay.js', 'replay-motion.js']) {
307
+ copyFileSync(join(templateDir, file), join(outDir, file));
308
+ }
309
+ cpSync(join(templateDir, 'icons'), join(outDir, 'icons'), { recursive: true });
310
+ writeFileSync(join(outDir, 'data.js'), `window.__POSTER_REPLAY__ = ${JSON.stringify(replay, null, 2)};\n`);
311
+
312
+ const missing = stages.filter((stage) => /缺失|不完整|未生成/.test(stage.statusLabel));
313
+ console.log(`replay ${join(outDir, 'index.html')}`);
314
+ console.log(`stages ${stages.length}`);
315
+ console.log(`assets ${assetItems.length}`);
316
+ if (missing.length) console.log(`evidence missing or partial: ${missing.map((stage) => stage.id).join(', ')}`);
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-contract.mjs —— 检查一张海报是否满足可编辑性契约
4
+ *
5
+ * node check-contract.mjs <海报 index.html> [--json]
6
+ *
7
+ * 退出码 0 = 通过,1 = 有违规,2 = 用法/环境错误。
8
+ * 违规项交给 agent 修(改成什么值取决于设计意图),修完重跑。
9
+ */
10
+ import { existsSync } from 'node:fs';
11
+ import { resolve } from 'node:path';
12
+ import { launch, open, probe, diff } from './_browser.mjs';
13
+
14
+ const file = process.argv[2];
15
+ const asJson = process.argv.includes('--json');
16
+ if (!file || !existsSync(resolve(file))) {
17
+ console.error('用法: node check-contract.mjs <海报 index.html> [--json]');
18
+ process.exit(2);
19
+ }
20
+
21
+ const report = { file: resolve(file), pass: true, checks: [], hints: {} };
22
+ const add = (id, ok, msg, fix) => {
23
+ report.checks.push({ id, ok, msg, fix });
24
+ if (!ok) report.pass = false;
25
+ };
26
+
27
+ const browser = await launch();
28
+ try {
29
+ const { page, errors } = await open(browser, file, { width: 1400, height: 1000 });
30
+
31
+ const info = await page.evaluate(() => {
32
+ const cv = document.querySelector('[data-canvas-width]') || document.querySelector('.poster-canvas');
33
+ if (!cv) return { canvas: false };
34
+ const cs = getComputedStyle(cv);
35
+ const layers = [...cv.querySelectorAll('[data-layer-id]')];
36
+ const ids = layers.map((l) => l.dataset.layerId);
37
+ const groupNodes = [...cv.querySelectorAll('[data-explode-group]')];
38
+ const groupIds = groupNodes.map((n) => (n.dataset.explodeGroup || '').trim());
39
+ const groupSet = new Set(groupIds.filter(Boolean));
40
+ const allowedRoles = new Set(['surface', 'image', 'copy', 'icon', 'decoration']);
41
+ const groups = groupNodes.map((node) => {
42
+ const id = (node.dataset.explodeGroup || '').trim();
43
+ const members = layers.filter((layer) =>
44
+ layer.closest('[data-explode-group]') === node
45
+ || (layer.dataset.explodeParent || '').trim() === id);
46
+ return {
47
+ id,
48
+ label: (node.dataset.explodeLabel || '').trim(),
49
+ members: [...new Set(members)].map((layer) => layer.dataset.layerId),
50
+ };
51
+ });
52
+
53
+ // 编辑器会把每个文字叶子自动标成 hf-slot,并用命中位置进入编辑。
54
+ // 像素没有重叠不代表交互框没有重叠;若 slot 中心被另一个图层截获,
55
+ // 双击看似点中文字,实际却永远进不了编辑态。
56
+ const blockedTextTargets = [];
57
+ const textSlots = (root) => {
58
+ const out = [];
59
+ (function walk(node) {
60
+ if (node instanceof SVGElement) return;
61
+ const ownText = [...node.childNodes]
62
+ .some((child) => child.nodeType === 3 && child.textContent.trim());
63
+ if (!node.children.length || ownText) {
64
+ if (node.textContent.trim()) out.push(node);
65
+ return;
66
+ }
67
+ for (const child of node.children) walk(child);
68
+ })(root);
69
+ return out;
70
+ };
71
+ for (const layer of layers) {
72
+ for (const slot of textSlots(layer)) {
73
+ slot.scrollIntoView({ block: 'center', inline: 'center' });
74
+ const rect = slot.getBoundingClientRect();
75
+ if (rect.width < 1 || rect.height < 1) continue;
76
+ const top = document.elementFromPoint(
77
+ rect.left + rect.width / 2,
78
+ rect.top + rect.height / 2,
79
+ );
80
+ const owner = top?.closest('[data-layer-id]');
81
+ if (owner !== layer) {
82
+ blockedTextTargets.push({
83
+ layer: layer.dataset.layerId,
84
+ text: slot.textContent.trim().replace(/\s+/g, ' ').slice(0, 24),
85
+ blocker: owner?.dataset.layerId || top?.tagName?.toLowerCase() || '(none)',
86
+ });
87
+ }
88
+ }
89
+ }
90
+ window.scrollTo(0, 0);
91
+
92
+ // 候选漏标块:有文字、不属于任何图层、且内部也不含图层。
93
+ // 最后一条很关键 —— 图层的祖先容器(如 .content / .footer)本身
94
+ // 没有 data-layer-id,但它不是漏标,只是包装。
95
+ const unlabeled = [];
96
+ for (const el of cv.querySelectorAll('*')) {
97
+ if (el.closest('[data-layer-id]') || el instanceof SVGElement) continue;
98
+ if (el.querySelector('[data-layer-id]')) continue;
99
+ if (!el.textContent.trim()) continue;
100
+ if (unlabeled.some((o) => o.node.contains(el))) continue;
101
+ unlabeled.push({ node: el });
102
+ }
103
+
104
+ // 静态定位 vw/vh 的出现处,便于修复时直接跳到那一行
105
+ const viewportUnits = [];
106
+ for (const sheet of document.styleSheets) {
107
+ let rules;
108
+ try { rules = sheet.cssRules; } catch { continue; }
109
+ for (const r of rules) {
110
+ if (!r.cssText) continue;
111
+ for (const m of r.cssText.matchAll(/[^;{}]*\b\d*\.?\d+v(?:w|h|min|max)\b[^;{}]*/g)) {
112
+ const frag = m[0].trim();
113
+ const sel = r.selectorText || r.conditionText || '@rule';
114
+ if (viewportUnits.length < 25) viewportUnits.push(`${sel} { ${frag} }`);
115
+ }
116
+ }
117
+ }
118
+
119
+ return {
120
+ canvas: true,
121
+ declaredW: +cv.dataset.canvasWidth || null,
122
+ declaredH: +cv.dataset.canvasHeight || null,
123
+ actualW: Math.round(cv.getBoundingClientRect().width),
124
+ actualH: Math.round(cv.getBoundingClientRect().height),
125
+ position: cs.position,
126
+ layerCount: layers.length,
127
+ duplicateIds: ids.filter((v, i) => ids.indexOf(v) !== i),
128
+ emptyIds: ids.filter((v) => !v || !v.trim()).length,
129
+ nestedLayers: layers.filter((layer) => layer.querySelector('[data-layer-id]'))
130
+ .map((layer) => layer.dataset.layerId),
131
+ groups,
132
+ duplicateGroups: groupIds.filter((v, i) => groupIds.indexOf(v) !== i),
133
+ emptyGroups: groupIds.filter((v) => !v).length,
134
+ groupsAlsoLayers: groupNodes.filter((node) => node.hasAttribute('data-layer-id'))
135
+ .map((node) => node.dataset.explodeGroup || '(empty)'),
136
+ nestedGroups: groupNodes.filter((node) => node.parentElement?.closest('[data-explode-group]'))
137
+ .map((node) => node.dataset.explodeGroup || '(empty)'),
138
+ orphanParents: layers.filter((layer) => {
139
+ const parent = (layer.dataset.explodeParent || '').trim();
140
+ return parent && !groupSet.has(parent);
141
+ }).map((layer) => `${layer.dataset.layerId}→${layer.dataset.explodeParent}`),
142
+ invalidRoles: layers.filter((layer) => {
143
+ const role = (layer.dataset.explodeRole || '').trim();
144
+ return role && !allowedRoles.has(role);
145
+ }).map((layer) => `${layer.dataset.layerId}:${layer.dataset.explodeRole}`),
146
+ blockedTextTargets,
147
+ unlabeled: unlabeled.map((o) => ({
148
+ tag: o.node.tagName.toLowerCase(),
149
+ cls: (typeof o.node.className === 'string' ? o.node.className : '') || '-',
150
+ text: o.node.textContent.trim().replace(/\s+/g, ' ').slice(0, 34),
151
+ })),
152
+ viewportUnits,
153
+ fontStatus: document.fonts.status,
154
+ /* 声明了但没被用到的 @font-face 会一直是 unloaded,这是浏览器按需加载的
155
+ 正常行为,仅作提示。注意 Google Fonts 把中文字体切成上百个
156
+ unicode-range 分片,同一 family 下必然部分 loaded 部分 unloaded,
157
+ 所以只有「没有任何分片被加载」才算这个 family 真的没用上。 */
158
+ idleFonts: (() => {
159
+ const tally = new Map();
160
+ for (const f of document.fonts) {
161
+ const cur = tally.get(f.family) || { loaded: 0 };
162
+ if (f.status === 'loaded') cur.loaded++;
163
+ tally.set(f.family, cur);
164
+ }
165
+ return [...tally].filter(([, v]) => v.loaded === 0).map(([k]) => k).slice(0, 10);
166
+ })(),
167
+ };
168
+ });
169
+
170
+ if (!info.canvas) {
171
+ add('C1-canvas', false, '找不到画布根节点',
172
+ '给海报根节点加 data-canvas-width / data-canvas-height(或使用 class="poster-canvas")');
173
+ } else {
174
+ add('C1-canvas', true, `画布 ${info.actualW}×${info.actualH}`);
175
+
176
+ const declared = info.declaredW && info.declaredH;
177
+ add('C1-declared', !!declared,
178
+ declared ? `已声明 data-canvas-width/height = ${info.declaredW}×${info.declaredH}` : '缺少 data-canvas-width / data-canvas-height',
179
+ '在画布根节点上声明 data-canvas-width="<宽>" data-canvas-height="<高>"');
180
+
181
+ if (declared) {
182
+ const match = Math.abs(info.declaredW - info.actualW) <= 1 && Math.abs(info.declaredH - info.actualH) <= 1;
183
+ add('C1-match', match,
184
+ match ? '声明尺寸与实际渲染尺寸一致' : `声明 ${info.declaredW}×${info.declaredH} 与实际 ${info.actualW}×${info.actualH} 不一致`,
185
+ '把画布宽高改成 px 写死,并与 data-canvas-* 的声明保持一致');
186
+ }
187
+
188
+ add('C1-position', ['relative', 'absolute', 'fixed'].includes(info.position),
189
+ `画布 position: ${info.position}`,
190
+ '画布需要 position:relative,否则图层没有确定的包含块');
191
+
192
+ add('C3-layers', info.layerCount > 0,
193
+ `找到 ${info.layerCount} 个 data-layer-id 图层`,
194
+ '给每个语义模块加 data-layer-id(判据:使用者是否会想单独移动它)');
195
+
196
+ add('C3-unique', info.duplicateIds.length === 0 && info.emptyIds === 0,
197
+ info.duplicateIds.length ? `data-layer-id 重复: ${[...new Set(info.duplicateIds)].join(', ')}` : 'data-layer-id 无重复无空值',
198
+ 'data-layer-id 必须唯一且非空');
199
+
200
+ add('C3-flat', info.nestedLayers.length === 0,
201
+ info.nestedLayers.length ? `data-layer-id 不可嵌套: ${info.nestedLayers.join(', ')}` : 'data-layer-id 叶子图层无嵌套',
202
+ '把共同容器改为不带 data-layer-id 的 data-explode-group,实际可编辑单元保留为叶子图层');
203
+
204
+ const groupIdsOk = info.duplicateGroups.length === 0 && info.emptyGroups === 0;
205
+ add('C4-group-ids', groupIdsOk,
206
+ !info.groups.length ? '未声明一级内容分组(使用扁平组件模式)'
207
+ : groupIdsOk ? `找到 ${info.groups.length} 个唯一分组` : `分组 id 重复或为空: ${[...new Set(info.duplicateGroups)].join(', ') || '(empty)'}`,
208
+ 'data-explode-group 必须唯一且非空');
209
+
210
+ const groupShapeOk = info.groupsAlsoLayers.length === 0 && info.nestedGroups.length === 0;
211
+ add('C4-group-shape', groupShapeOk,
212
+ groupShapeOk ? '分组容器不参与拖拽且不互相嵌套'
213
+ : `无效分组容器: ${[...info.groupsAlsoLayers, ...info.nestedGroups].join(', ')}`,
214
+ '分组容器只加 data-explode-group,不加 data-layer-id,也不要嵌套另一个分组容器');
215
+
216
+ const usefulGroups = info.groups.filter((group) => group.members.length >= 2).length;
217
+ const membersOk = info.groups.every((group) => group.members.length >= 2)
218
+ && info.orphanParents.length === 0 && info.invalidRoles.length === 0;
219
+ add('C4-group-members', membersOk,
220
+ !info.groups.length ? '无需分组成员检查'
221
+ : membersOk ? `${usefulGroups} 个分组均含至少 2 个叶子图层`
222
+ : `分组成员/角色无效${info.orphanParents.length ? `;孤立归属 ${info.orphanParents.join(', ')}` : ''}${info.invalidRoles.length ? `;未知角色 ${info.invalidRoles.join(', ')}` : ''}`,
223
+ '每个分组至少包含 2 个叶子 data-layer-id;角色仅用 surface/image/copy/icon/decoration');
224
+
225
+ add('C5-text-hit', info.blockedTextTargets.length === 0,
226
+ info.blockedTextTargets.length
227
+ ? `文字编辑命中区被遮挡: ${info.blockedTextTargets.slice(0, 8)
228
+ .map((item) => `${item.layer}→${item.blocker} (${item.text})`).join(', ')}`
229
+ : '所有文字编辑命中区均可到达',
230
+ '调整重叠图层的位置、pointer-events 或 DOM/z-index 顺序,使文字槽中心能命中所属 data-layer-id');
231
+ }
232
+
233
+ /* C2 的实质检测:调整同一真实浏览器页面的视口,图层坐标不应变化。
234
+ 这比正则查 vh 精确得多 —— 百分比、vmin、calc() 混用都能抓到。 */
235
+ const m1 = await probe(page);
236
+ await page.setViewport({ width: 1000, height: 1500, deviceScaleFactor: 1 });
237
+ await page.evaluate(() => new Promise((resolveFrame) => {
238
+ requestAnimationFrame(() => requestAnimationFrame(resolveFrame));
239
+ }));
240
+ const m2 = await probe(page);
241
+ const d = diff(m1, m2, 0.5);
242
+ add('C2-viewport', d.ok,
243
+ d.ok ? '调整视口后坐标不变(布局不依赖视口)'
244
+ : `调整视口后坐标发生变化,最大 ${d.worst}px:${d.rows.filter((r) => r.fail).map((r) => r.id).join(', ')}`,
245
+ '布局尺寸不能用 vw / vh / 百分比承担,改为 px 固定值(渐变、纹理等装饰不限)');
246
+
247
+ add('fonts', info.fontStatus === 'loaded',
248
+ info.fontStatus === 'loaded' ? '字体加载已完成(document.fonts.status = loaded)' : `字体仍在加载: ${info.fontStatus}`,
249
+ '字体未落地会导致测量到 fallback 字体的坐标;检查网络或字体引用');
250
+
251
+ add('runtime', errors.length === 0,
252
+ errors.length ? `页面有 ${errors.length} 处报错` : '页面无控制台报错');
253
+
254
+ report.hints = {
255
+ unlabeled: info.unlabeled || [],
256
+ viewportUnits: info.viewportUnits || [],
257
+ idleFonts: info.idleFonts || [],
258
+ pageErrors: errors,
259
+ };
260
+ } finally {
261
+ await browser.close();
262
+ }
263
+
264
+ if (asJson) {
265
+ console.log(JSON.stringify(report, null, 2));
266
+ process.exit(report.pass ? 0 : 1);
267
+ }
268
+
269
+ console.log(`\n契约检查 · ${report.file}\n`);
270
+ for (const c of report.checks) {
271
+ console.log(` ${c.ok ? '✅' : '❌'} [${c.id}] ${c.msg}`);
272
+ if (!c.ok && c.fix) console.log(` 修法: ${c.fix}`);
273
+ }
274
+
275
+ const { unlabeled, viewportUnits, idleFonts, pageErrors } = report.hints;
276
+ if (idleFonts.length) {
277
+ console.log(`\n声明但未被使用的字体(按需加载,正常现象): ${idleFonts.join(', ')}`);
278
+ }
279
+ if (viewportUnits.length) {
280
+ console.log(`\n视口单位出现处(供定位,装饰性用途可忽略):`);
281
+ for (const v of viewportUnits) console.log(' ' + v);
282
+ }
283
+ if (unlabeled.length) {
284
+ console.log(`\n可能漏标 data-layer-id 的候选块(${unlabeled.length} 个,需人工判断):`);
285
+ for (const u of unlabeled) console.log(` <${u.tag} class="${u.cls}"> ${u.text}`);
286
+ }
287
+ if (pageErrors.length) {
288
+ console.log(`\n页面报错:`);
289
+ for (const e of pageErrors) console.log(' ' + e);
290
+ }
291
+
292
+ console.log(report.pass ? '\n契约通过 ✅ 可以执行 bake.mjs\n' : '\n契约未通过 ❌ 修复后重跑\n');
293
+ process.exit(report.pass ? 0 : 1);