@hybridlabor-api/aos 4.6.6 → 4.7.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 (484) hide show
  1. package/.agents/agents.md +1 -0
  2. package/.agents/nodes.json +5 -1
  3. package/.agents/state.schema.json +2 -1
  4. package/.agents/vendor-manifest.json +50 -0
  5. package/.claude/agents/architect.md +4 -0
  6. package/.claude/agents/security-reviewer.md +1 -1
  7. package/.claude/hooks/memb-inject.mjs +257 -135
  8. package/.claude/hooks/trail-relay.mjs +18 -0
  9. package/.claude/settings.json +70 -0
  10. package/.claude/workflows/startcycle-dispatch.mjs +29 -1
  11. package/.opencode/agents/architect.md +4 -0
  12. package/.opencode/plugins/bdb-aos.js +75 -0
  13. package/THIRD_PARTY_NOTICES.md +48 -0
  14. package/bin/aos-uninstall.mjs +55 -1
  15. package/docs/handover/agenttrail-for-ao.md +58 -0
  16. package/docs/handover/deja-for-ao.md +51 -0
  17. package/installer.js +141 -37
  18. package/mcp_config.json +4 -0
  19. package/mcps/mcsc/README.md +24 -0
  20. package/mcps/mcsc/package-lock.json +1234 -0
  21. package/mcps/mcsc/package.json +14 -0
  22. package/mcps/mcsc/packages/core/bin/mcsc.js +162 -0
  23. package/mcps/mcsc/packages/core/rulebook.default.yaml +49 -0
  24. package/mcps/mcsc/packages/core/schema/rulebook.schema.json +56 -0
  25. package/mcps/mcsc/packages/core/src/adapters/agy.js +107 -0
  26. package/mcps/mcsc/packages/core/src/adapters/codex.js +56 -0
  27. package/mcps/mcsc/packages/core/src/adapters/native.js +14 -0
  28. package/mcps/mcsc/packages/core/src/adapters/opencode.js +183 -0
  29. package/mcps/mcsc/packages/core/src/adapters/types.js +21 -0
  30. package/mcps/mcsc/packages/core/src/capabilities/buildOfferTable.js +34 -0
  31. package/mcps/mcsc/packages/core/src/capabilities/registry.js +99 -0
  32. package/mcps/mcsc/packages/core/src/capabilities/types.js +24 -0
  33. package/mcps/mcsc/packages/core/src/detect/cache.js +47 -0
  34. package/mcps/mcsc/packages/core/src/detect/inventory.js +73 -0
  35. package/mcps/mcsc/packages/core/src/detect/probes.js +158 -0
  36. package/mcps/mcsc/packages/core/src/detect/types.js +26 -0
  37. package/mcps/mcsc/packages/core/src/rulebook/load.js +53 -0
  38. package/mcps/mcsc/packages/core/src/rulebook/resolve.js +227 -0
  39. package/mcps/mcsc/packages/core/src/rulebook/rulebook.types.js +89 -0
  40. package/mcps/mcsc/packages/core/src/trail.js +17 -0
  41. package/mcps/mcsc/packages/mcp/server.js +302 -0
  42. package/mcps/mcsc/server.js +2 -0
  43. package/package.json +3 -1
  44. package/skills/basic/bdbmediastorm/SKILL.md +1 -1
  45. package/skills/basic/godmode-ui-ux/SKILL.md +2 -2
  46. package/skills/basic/godmode-ui-ux/references/anti-slop-rules.md +44 -40
  47. package/skills/basic/godmode-ui-ux/references/taste-archetypes.md +49 -0
  48. package/skills/basic/startcycle/SKILL.md +7 -0
  49. package/skills/basic/startcycle-graph/SKILL.md +12 -0
  50. package/skills/basic/startcycle-graph-user/SKILL.md +3 -0
  51. package/skills/basic/teamwork-preview/SKILL.md +2 -0
  52. package/skills/bdbrainstorm/SKILL.md +1 -0
  53. package/skills/global_config/agenttrail/LICENSE +21 -0
  54. package/skills/global_config/agenttrail/SKILL.md +48 -0
  55. package/skills/global_config/agenttrail/bin/agenttrail.mjs +1002 -0
  56. package/skills/global_config/agenttrail/public/index.html +661 -0
  57. package/skills/global_config/aos-project-init/SKILL.md +25 -5
  58. package/skills/global_config/aos-setup/SKILL.md +34 -1
  59. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +115 -1
  60. package/skills/global_config/archify/LICENSE +22 -0
  61. package/skills/global_config/archify/SKILL.md +140 -0
  62. package/skills/global_config/archify/THIRD_PARTY_NOTICES.md +69 -0
  63. package/skills/global_config/archify/assets/JetBrainsMono-OFL.txt +93 -0
  64. package/skills/global_config/archify/assets/template.html +14937 -0
  65. package/skills/global_config/archify/bin/archify.mjs +2139 -0
  66. package/skills/global_config/archify/bin/open-artifact.mjs +86 -0
  67. package/skills/global_config/archify/bin/preview.mjs +653 -0
  68. package/skills/global_config/archify/bin/visual-check.mjs +829 -0
  69. package/skills/global_config/archify/brand-marks/README.md +31 -0
  70. package/skills/global_config/archify/brand-marks/catalog.json +131 -0
  71. package/skills/global_config/archify/delta/architecture-delta.mjs +1232 -0
  72. package/skills/global_config/archify/examples/agent-run.lifecycle.json +60 -0
  73. package/skills/global_config/archify/examples/agent-tool-call.workflow.json +94 -0
  74. package/skills/global_config/archify/examples/async-job-roundtrip.sequence.json +61 -0
  75. package/skills/global_config/archify/examples/brand-aware-delivery.architecture.json +47 -0
  76. package/skills/global_config/archify/examples/cache-miss-request.sequence.json +82 -0
  77. package/skills/global_config/archify/examples/checkout-platform.base.architecture.json +31 -0
  78. package/skills/global_config/archify/examples/checkout-platform.head.architecture.json +31 -0
  79. package/skills/global_config/archify/examples/deployment-release.lifecycle.json +49 -0
  80. package/skills/global_config/archify/examples/event-stream.dataflow.json +57 -0
  81. package/skills/global_config/archify/examples/incident-response.workflow.json +64 -0
  82. package/skills/global_config/archify/examples/product-analytics.dataflow.json +76 -0
  83. package/skills/global_config/archify/examples/production-deployment.architecture.json +71 -0
  84. package/skills/global_config/archify/examples/release-delivery.workflow.json +62 -0
  85. package/skills/global_config/archify/examples/web-app.architecture.json +46 -0
  86. package/skills/global_config/archify/migrations/workflow-v2.mjs +279 -0
  87. package/skills/global_config/archify/package.json +39 -0
  88. package/skills/global_config/archify/recipes/scenarios.mjs +391 -0
  89. package/skills/global_config/archify/references/authoring-contract.md +247 -0
  90. package/skills/global_config/archify/references/brand-marks.md +65 -0
  91. package/skills/global_config/archify/references/delivery-contract.md +138 -0
  92. package/skills/global_config/archify/references/viewer-runtime.md +45 -0
  93. package/skills/global_config/archify/renderers/architecture/grid.mjs +62 -0
  94. package/skills/global_config/archify/renderers/architecture/render-architecture.mjs +1078 -0
  95. package/skills/global_config/archify/renderers/dataflow/README.md +104 -0
  96. package/skills/global_config/archify/renderers/dataflow/render-dataflow.mjs +483 -0
  97. package/skills/global_config/archify/renderers/lifecycle/README.md +115 -0
  98. package/skills/global_config/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
  99. package/skills/global_config/archify/renderers/sequence/README.md +114 -0
  100. package/skills/global_config/archify/renderers/sequence/render-sequence.mjs +464 -0
  101. package/skills/global_config/archify/renderers/shared/brand-marks.mjs +563 -0
  102. package/skills/global_config/archify/renderers/shared/cli.mjs +218 -0
  103. package/skills/global_config/archify/renderers/shared/desktop-readability.mjs +26 -0
  104. package/skills/global_config/archify/renderers/shared/diagnostics.mjs +160 -0
  105. package/skills/global_config/archify/renderers/shared/engineering-profiles.mjs +157 -0
  106. package/skills/global_config/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  107. package/skills/global_config/archify/renderers/shared/generated-validators.mjs +13 -0
  108. package/skills/global_config/archify/renderers/shared/geometry.mjs +1423 -0
  109. package/skills/global_config/archify/renderers/shared/i18n.mjs +595 -0
  110. package/skills/global_config/archify/renderers/shared/layout-report.mjs +40 -0
  111. package/skills/global_config/archify/renderers/shared/legend.mjs +217 -0
  112. package/skills/global_config/archify/renderers/shared/output-path.mjs +340 -0
  113. package/skills/global_config/archify/renderers/shared/repository-evidence.mjs +239 -0
  114. package/skills/global_config/archify/renderers/shared/repository-location.mjs +58 -0
  115. package/skills/global_config/archify/renderers/shared/text-fit.mjs +49 -0
  116. package/skills/global_config/archify/renderers/shared/utils.mjs +232 -0
  117. package/skills/global_config/archify/renderers/shared/validator.mjs +86 -0
  118. package/skills/global_config/archify/renderers/workflow/README.md +223 -0
  119. package/skills/global_config/archify/renderers/workflow/render-workflow.mjs +35 -0
  120. package/skills/global_config/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  121. package/skills/global_config/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  122. package/skills/global_config/archify/schemas/README.md +211 -0
  123. package/skills/global_config/archify/schemas/architecture.schema.json +178 -0
  124. package/skills/global_config/archify/schemas/common.schema.json +115 -0
  125. package/skills/global_config/archify/schemas/dataflow.schema.json +243 -0
  126. package/skills/global_config/archify/schemas/lifecycle.schema.json +266 -0
  127. package/skills/global_config/archify/schemas/sequence.schema.json +223 -0
  128. package/skills/global_config/archify/schemas/workflow.schema.json +428 -0
  129. package/skills/global_config/archify/scripts/check-render-output.mjs +916 -0
  130. package/skills/global_config/archify/scripts/generate-brand-marks.mjs +141 -0
  131. package/skills/global_config/archify/scripts/generate-validators.mjs +66 -0
  132. package/skills/global_config/archify/scripts/render-examples.mjs +26 -0
  133. package/skills/global_config/archify/skill-release.json +10 -0
  134. package/skills/global_config/bdb-security-audit/SKILL.md +109 -0
  135. package/skills/global_config/bdb-security-audit/references/audit-checklists.md +172 -0
  136. package/skills/global_config/bdb-security-audit/references/skylos-guide.md +139 -0
  137. package/skills/global_config/bdb-security-audit/references/xalgorix-bridge.md +110 -0
  138. package/skills/global_config/bdbdesignpro/SKILL.md +4 -4
  139. package/skills/global_config/bdbdesignpro/references/bdb-motion-tokens.md +52 -31
  140. package/skills/global_config/bdbresilience/SKILL.md +10 -0
  141. package/skills/global_config/brag/SKILL.md +179 -0
  142. package/skills/global_config/brag/assets/music/README.md +20 -0
  143. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-1-by-ende-dot-app.music-cues.json +3846 -0
  144. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-1-by-ende-dot-app.music-cues.md +35 -0
  145. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-10-by-ende-dot-app.music-cues.json +1826 -0
  146. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-10-by-ende-dot-app.music-cues.md +35 -0
  147. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-11-by-ende-dot-app.music-cues.json +2266 -0
  148. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-11-by-ende-dot-app.music-cues.md +35 -0
  149. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-12-by-ende-dot-app.music-cues.json +2806 -0
  150. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-12-by-ende-dot-app.music-cues.md +35 -0
  151. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-9-by-ende-dot-app.music-cues.json +2796 -0
  152. package/skills/global_config/brag/assets/music/cues/happy-beats-business-moves-vol-9-by-ende-dot-app.music-cues.md +35 -0
  153. package/skills/global_config/brag/assets/music/happy-beats-business-moves-vol-1-by-ende-dot-app.mp3 +0 -0
  154. package/skills/global_config/brag/assets/music/happy-beats-business-moves-vol-10-by-ende-dot-app.mp3 +0 -0
  155. package/skills/global_config/brag/assets/music/happy-beats-business-moves-vol-11-by-ende-dot-app.mp3 +0 -0
  156. package/skills/global_config/brag/assets/music/happy-beats-business-moves-vol-12-by-ende-dot-app.mp3 +0 -0
  157. package/skills/global_config/brag/assets/music/happy-beats-business-moves-vol-9-by-ende-dot-app.mp3 +0 -0
  158. package/skills/global_config/brag/assets/sfx/casino/card-fan-1.ogg +0 -0
  159. package/skills/global_config/brag/assets/sfx/casino/card-fan-2.ogg +0 -0
  160. package/skills/global_config/brag/assets/sfx/casino/card-place-1.ogg +0 -0
  161. package/skills/global_config/brag/assets/sfx/casino/card-place-2.ogg +0 -0
  162. package/skills/global_config/brag/assets/sfx/casino/card-place-3.ogg +0 -0
  163. package/skills/global_config/brag/assets/sfx/casino/card-place-4.ogg +0 -0
  164. package/skills/global_config/brag/assets/sfx/casino/card-shove-1.ogg +0 -0
  165. package/skills/global_config/brag/assets/sfx/casino/card-shove-2.ogg +0 -0
  166. package/skills/global_config/brag/assets/sfx/casino/card-shove-3.ogg +0 -0
  167. package/skills/global_config/brag/assets/sfx/casino/card-shove-4.ogg +0 -0
  168. package/skills/global_config/brag/assets/sfx/casino/card-shuffle.ogg +0 -0
  169. package/skills/global_config/brag/assets/sfx/casino/card-slide-1.ogg +0 -0
  170. package/skills/global_config/brag/assets/sfx/casino/card-slide-2.ogg +0 -0
  171. package/skills/global_config/brag/assets/sfx/casino/card-slide-3.ogg +0 -0
  172. package/skills/global_config/brag/assets/sfx/casino/card-slide-4.ogg +0 -0
  173. package/skills/global_config/brag/assets/sfx/casino/card-slide-5.ogg +0 -0
  174. package/skills/global_config/brag/assets/sfx/casino/card-slide-6.ogg +0 -0
  175. package/skills/global_config/brag/assets/sfx/casino/card-slide-7.ogg +0 -0
  176. package/skills/global_config/brag/assets/sfx/casino/card-slide-8.ogg +0 -0
  177. package/skills/global_config/brag/assets/sfx/casino/cards-pack-open-1.ogg +0 -0
  178. package/skills/global_config/brag/assets/sfx/casino/cards-pack-open-2.ogg +0 -0
  179. package/skills/global_config/brag/assets/sfx/casino/chip-lay-1.ogg +0 -0
  180. package/skills/global_config/brag/assets/sfx/casino/chip-lay-2.ogg +0 -0
  181. package/skills/global_config/brag/assets/sfx/casino/chip-lay-3.ogg +0 -0
  182. package/skills/global_config/brag/assets/sfx/casino/chips-collide-1.ogg +0 -0
  183. package/skills/global_config/brag/assets/sfx/casino/chips-collide-2.ogg +0 -0
  184. package/skills/global_config/brag/assets/sfx/casino/chips-collide-3.ogg +0 -0
  185. package/skills/global_config/brag/assets/sfx/casino/chips-collide-4.ogg +0 -0
  186. package/skills/global_config/brag/assets/sfx/casino/chips-handle-1.ogg +0 -0
  187. package/skills/global_config/brag/assets/sfx/casino/chips-handle-2.ogg +0 -0
  188. package/skills/global_config/brag/assets/sfx/casino/chips-handle-3.ogg +0 -0
  189. package/skills/global_config/brag/assets/sfx/casino/chips-handle-4.ogg +0 -0
  190. package/skills/global_config/brag/assets/sfx/casino/chips-handle-6.ogg +0 -0
  191. package/skills/global_config/brag/assets/sfx/casino/chips-stack-1.ogg +0 -0
  192. package/skills/global_config/brag/assets/sfx/casino/chips-stack-2.ogg +0 -0
  193. package/skills/global_config/brag/assets/sfx/casino/chips-stack-3.ogg +0 -0
  194. package/skills/global_config/brag/assets/sfx/casino/chips-stack-4.ogg +0 -0
  195. package/skills/global_config/brag/assets/sfx/casino/chips-stack-5.ogg +0 -0
  196. package/skills/global_config/brag/assets/sfx/casino/chips-stack-6.ogg +0 -0
  197. package/skills/global_config/brag/assets/sfx/casino/dice-grab-1.ogg +0 -0
  198. package/skills/global_config/brag/assets/sfx/casino/dice-grab-2.ogg +0 -0
  199. package/skills/global_config/brag/assets/sfx/casino/dice-shake-1.ogg +0 -0
  200. package/skills/global_config/brag/assets/sfx/casino/dice-shake-2.ogg +0 -0
  201. package/skills/global_config/brag/assets/sfx/casino/dice-shake-3.ogg +0 -0
  202. package/skills/global_config/brag/assets/sfx/casino/dice-throw-1.ogg +0 -0
  203. package/skills/global_config/brag/assets/sfx/casino/dice-throw-2.ogg +0 -0
  204. package/skills/global_config/brag/assets/sfx/casino/dice-throw-3.ogg +0 -0
  205. package/skills/global_config/brag/assets/sfx/casino/die-throw-1.ogg +0 -0
  206. package/skills/global_config/brag/assets/sfx/casino/die-throw-2.ogg +0 -0
  207. package/skills/global_config/brag/assets/sfx/casino/die-throw-3.ogg +0 -0
  208. package/skills/global_config/brag/assets/sfx/casino/die-throw-4.ogg +0 -0
  209. package/skills/global_config/brag/assets/sfx/impact/footstep_carpet_000.ogg +0 -0
  210. package/skills/global_config/brag/assets/sfx/impact/footstep_carpet_003.ogg +0 -0
  211. package/skills/global_config/brag/assets/sfx/impact/footstep_carpet_004.ogg +0 -0
  212. package/skills/global_config/brag/assets/sfx/impact/footstep_concrete_000.ogg +0 -0
  213. package/skills/global_config/brag/assets/sfx/impact/footstep_concrete_001.ogg +0 -0
  214. package/skills/global_config/brag/assets/sfx/impact/footstep_concrete_002.ogg +0 -0
  215. package/skills/global_config/brag/assets/sfx/impact/footstep_concrete_003.ogg +0 -0
  216. package/skills/global_config/brag/assets/sfx/impact/footstep_concrete_004.ogg +0 -0
  217. package/skills/global_config/brag/assets/sfx/impact/footstep_grass_000.ogg +0 -0
  218. package/skills/global_config/brag/assets/sfx/impact/footstep_grass_001.ogg +0 -0
  219. package/skills/global_config/brag/assets/sfx/impact/footstep_grass_002.ogg +0 -0
  220. package/skills/global_config/brag/assets/sfx/impact/footstep_grass_003.ogg +0 -0
  221. package/skills/global_config/brag/assets/sfx/impact/footstep_grass_004.ogg +0 -0
  222. package/skills/global_config/brag/assets/sfx/impact/footstep_snow_000.ogg +0 -0
  223. package/skills/global_config/brag/assets/sfx/impact/footstep_snow_001.ogg +0 -0
  224. package/skills/global_config/brag/assets/sfx/impact/footstep_snow_002.ogg +0 -0
  225. package/skills/global_config/brag/assets/sfx/impact/footstep_snow_003.ogg +0 -0
  226. package/skills/global_config/brag/assets/sfx/impact/footstep_snow_004.ogg +0 -0
  227. package/skills/global_config/brag/assets/sfx/impact/footstep_wood_000.ogg +0 -0
  228. package/skills/global_config/brag/assets/sfx/impact/footstep_wood_001.ogg +0 -0
  229. package/skills/global_config/brag/assets/sfx/impact/footstep_wood_002.ogg +0 -0
  230. package/skills/global_config/brag/assets/sfx/impact/footstep_wood_003.ogg +0 -0
  231. package/skills/global_config/brag/assets/sfx/impact/footstep_wood_004.ogg +0 -0
  232. package/skills/global_config/brag/assets/sfx/impact/impactBell_heavy_000.ogg +0 -0
  233. package/skills/global_config/brag/assets/sfx/impact/impactBell_heavy_003.ogg +0 -0
  234. package/skills/global_config/brag/assets/sfx/impact/impactBell_heavy_004.ogg +0 -0
  235. package/skills/global_config/brag/assets/sfx/impact/impactGeneric_light_000.ogg +0 -0
  236. package/skills/global_config/brag/assets/sfx/impact/impactGeneric_light_001.ogg +0 -0
  237. package/skills/global_config/brag/assets/sfx/impact/impactGeneric_light_002.ogg +0 -0
  238. package/skills/global_config/brag/assets/sfx/impact/impactGeneric_light_003.ogg +0 -0
  239. package/skills/global_config/brag/assets/sfx/impact/impactGeneric_light_004.ogg +0 -0
  240. package/skills/global_config/brag/assets/sfx/impact/impactGlass_heavy_002.ogg +0 -0
  241. package/skills/global_config/brag/assets/sfx/impact/impactGlass_light_001.ogg +0 -0
  242. package/skills/global_config/brag/assets/sfx/impact/impactGlass_light_002.ogg +0 -0
  243. package/skills/global_config/brag/assets/sfx/impact/impactGlass_light_003.ogg +0 -0
  244. package/skills/global_config/brag/assets/sfx/impact/impactGlass_medium_000.ogg +0 -0
  245. package/skills/global_config/brag/assets/sfx/impact/impactGlass_medium_002.ogg +0 -0
  246. package/skills/global_config/brag/assets/sfx/impact/impactGlass_medium_004.ogg +0 -0
  247. package/skills/global_config/brag/assets/sfx/impact/impactMetal_heavy_000.ogg +0 -0
  248. package/skills/global_config/brag/assets/sfx/impact/impactMetal_heavy_002.ogg +0 -0
  249. package/skills/global_config/brag/assets/sfx/impact/impactMetal_heavy_004.ogg +0 -0
  250. package/skills/global_config/brag/assets/sfx/impact/impactMetal_light_002.ogg +0 -0
  251. package/skills/global_config/brag/assets/sfx/impact/impactMetal_light_003.ogg +0 -0
  252. package/skills/global_config/brag/assets/sfx/impact/impactMetal_medium_000.ogg +0 -0
  253. package/skills/global_config/brag/assets/sfx/impact/impactMetal_medium_001.ogg +0 -0
  254. package/skills/global_config/brag/assets/sfx/impact/impactMetal_medium_002.ogg +0 -0
  255. package/skills/global_config/brag/assets/sfx/impact/impactMetal_medium_003.ogg +0 -0
  256. package/skills/global_config/brag/assets/sfx/impact/impactMetal_medium_004.ogg +0 -0
  257. package/skills/global_config/brag/assets/sfx/impact/impactMining_001.ogg +0 -0
  258. package/skills/global_config/brag/assets/sfx/impact/impactPlank_medium_000.ogg +0 -0
  259. package/skills/global_config/brag/assets/sfx/impact/impactPlank_medium_001.ogg +0 -0
  260. package/skills/global_config/brag/assets/sfx/impact/impactPlank_medium_002.ogg +0 -0
  261. package/skills/global_config/brag/assets/sfx/impact/impactPlank_medium_003.ogg +0 -0
  262. package/skills/global_config/brag/assets/sfx/impact/impactPlank_medium_004.ogg +0 -0
  263. package/skills/global_config/brag/assets/sfx/impact/impactPlate_heavy_000.ogg +0 -0
  264. package/skills/global_config/brag/assets/sfx/impact/impactPlate_heavy_001.ogg +0 -0
  265. package/skills/global_config/brag/assets/sfx/impact/impactPlate_heavy_002.ogg +0 -0
  266. package/skills/global_config/brag/assets/sfx/impact/impactPlate_heavy_003.ogg +0 -0
  267. package/skills/global_config/brag/assets/sfx/impact/impactPlate_heavy_004.ogg +0 -0
  268. package/skills/global_config/brag/assets/sfx/impact/impactPlate_light_000.ogg +0 -0
  269. package/skills/global_config/brag/assets/sfx/impact/impactPlate_light_001.ogg +0 -0
  270. package/skills/global_config/brag/assets/sfx/impact/impactPlate_light_002.ogg +0 -0
  271. package/skills/global_config/brag/assets/sfx/impact/impactPlate_light_003.ogg +0 -0
  272. package/skills/global_config/brag/assets/sfx/impact/impactPlate_light_004.ogg +0 -0
  273. package/skills/global_config/brag/assets/sfx/impact/impactPlate_medium_000.ogg +0 -0
  274. package/skills/global_config/brag/assets/sfx/impact/impactPlate_medium_001.ogg +0 -0
  275. package/skills/global_config/brag/assets/sfx/impact/impactPlate_medium_002.ogg +0 -0
  276. package/skills/global_config/brag/assets/sfx/impact/impactPlate_medium_003.ogg +0 -0
  277. package/skills/global_config/brag/assets/sfx/impact/impactPlate_medium_004.ogg +0 -0
  278. package/skills/global_config/brag/assets/sfx/impact/impactPunch_heavy_000.ogg +0 -0
  279. package/skills/global_config/brag/assets/sfx/impact/impactPunch_heavy_001.ogg +0 -0
  280. package/skills/global_config/brag/assets/sfx/impact/impactPunch_heavy_002.ogg +0 -0
  281. package/skills/global_config/brag/assets/sfx/impact/impactPunch_heavy_003.ogg +0 -0
  282. package/skills/global_config/brag/assets/sfx/impact/impactPunch_heavy_004.ogg +0 -0
  283. package/skills/global_config/brag/assets/sfx/impact/impactPunch_medium_000.ogg +0 -0
  284. package/skills/global_config/brag/assets/sfx/impact/impactPunch_medium_001.ogg +0 -0
  285. package/skills/global_config/brag/assets/sfx/impact/impactPunch_medium_002.ogg +0 -0
  286. package/skills/global_config/brag/assets/sfx/impact/impactPunch_medium_003.ogg +0 -0
  287. package/skills/global_config/brag/assets/sfx/impact/impactPunch_medium_004.ogg +0 -0
  288. package/skills/global_config/brag/assets/sfx/impact/impactSoft_heavy_000.ogg +0 -0
  289. package/skills/global_config/brag/assets/sfx/impact/impactSoft_heavy_001.ogg +0 -0
  290. package/skills/global_config/brag/assets/sfx/impact/impactSoft_heavy_002.ogg +0 -0
  291. package/skills/global_config/brag/assets/sfx/impact/impactSoft_heavy_003.ogg +0 -0
  292. package/skills/global_config/brag/assets/sfx/impact/impactSoft_heavy_004.ogg +0 -0
  293. package/skills/global_config/brag/assets/sfx/impact/impactSoft_medium_000.ogg +0 -0
  294. package/skills/global_config/brag/assets/sfx/impact/impactSoft_medium_001.ogg +0 -0
  295. package/skills/global_config/brag/assets/sfx/impact/impactSoft_medium_002.ogg +0 -0
  296. package/skills/global_config/brag/assets/sfx/impact/impactSoft_medium_003.ogg +0 -0
  297. package/skills/global_config/brag/assets/sfx/impact/impactSoft_medium_004.ogg +0 -0
  298. package/skills/global_config/brag/assets/sfx/impact/impactTin_medium_000.ogg +0 -0
  299. package/skills/global_config/brag/assets/sfx/impact/impactTin_medium_001.ogg +0 -0
  300. package/skills/global_config/brag/assets/sfx/impact/impactTin_medium_002.ogg +0 -0
  301. package/skills/global_config/brag/assets/sfx/impact/impactTin_medium_003.ogg +0 -0
  302. package/skills/global_config/brag/assets/sfx/impact/impactTin_medium_004.ogg +0 -0
  303. package/skills/global_config/brag/assets/sfx/impact/impactWood_heavy_000.ogg +0 -0
  304. package/skills/global_config/brag/assets/sfx/impact/impactWood_heavy_001.ogg +0 -0
  305. package/skills/global_config/brag/assets/sfx/impact/impactWood_heavy_002.ogg +0 -0
  306. package/skills/global_config/brag/assets/sfx/impact/impactWood_heavy_003.ogg +0 -0
  307. package/skills/global_config/brag/assets/sfx/impact/impactWood_heavy_004.ogg +0 -0
  308. package/skills/global_config/brag/assets/sfx/impact/impactWood_light_000.ogg +0 -0
  309. package/skills/global_config/brag/assets/sfx/impact/impactWood_light_001.ogg +0 -0
  310. package/skills/global_config/brag/assets/sfx/impact/impactWood_light_002.ogg +0 -0
  311. package/skills/global_config/brag/assets/sfx/impact/impactWood_light_003.ogg +0 -0
  312. package/skills/global_config/brag/assets/sfx/impact/impactWood_light_004.ogg +0 -0
  313. package/skills/global_config/brag/assets/sfx/impact/impactWood_medium_000.ogg +0 -0
  314. package/skills/global_config/brag/assets/sfx/impact/impactWood_medium_001.ogg +0 -0
  315. package/skills/global_config/brag/assets/sfx/impact/impactWood_medium_002.ogg +0 -0
  316. package/skills/global_config/brag/assets/sfx/impact/impactWood_medium_003.ogg +0 -0
  317. package/skills/global_config/brag/assets/sfx/impact/impactWood_medium_004.ogg +0 -0
  318. package/skills/global_config/brag/assets/sfx/interface/bong_001.ogg +0 -0
  319. package/skills/global_config/brag/assets/sfx/interface/click_001.ogg +0 -0
  320. package/skills/global_config/brag/assets/sfx/interface/click_002.ogg +0 -0
  321. package/skills/global_config/brag/assets/sfx/interface/click_003.ogg +0 -0
  322. package/skills/global_config/brag/assets/sfx/interface/click_004.ogg +0 -0
  323. package/skills/global_config/brag/assets/sfx/interface/click_005.ogg +0 -0
  324. package/skills/global_config/brag/assets/sfx/interface/drop_001.ogg +0 -0
  325. package/skills/global_config/brag/assets/sfx/interface/drop_002.ogg +0 -0
  326. package/skills/global_config/brag/assets/sfx/interface/drop_003.ogg +0 -0
  327. package/skills/global_config/brag/assets/sfx/interface/error_005.ogg +0 -0
  328. package/skills/global_config/brag/assets/sfx/interface/error_006.ogg +0 -0
  329. package/skills/global_config/brag/assets/sfx/interface/glitch_002.ogg +0 -0
  330. package/skills/global_config/brag/assets/sfx/interface/glitch_004.ogg +0 -0
  331. package/skills/global_config/brag/assets/sfx/interface/select_008.ogg +0 -0
  332. package/skills/global_config/brag/assets/sfx/interface/switch_001.ogg +0 -0
  333. package/skills/global_config/brag/assets/sfx/interface/switch_002.ogg +0 -0
  334. package/skills/global_config/brag/assets/sfx/interface/switch_004.ogg +0 -0
  335. package/skills/global_config/brag/assets/sfx/interface/switch_005.ogg +0 -0
  336. package/skills/global_config/brag/assets/sfx/interface/switch_006.ogg +0 -0
  337. package/skills/global_config/brag/assets/sfx/interface/switch_007.ogg +0 -0
  338. package/skills/global_config/brag/assets/sfx/keyboard/keypress-001.wav +0 -0
  339. package/skills/global_config/brag/assets/sfx/keyboard/keypress-002.wav +0 -0
  340. package/skills/global_config/brag/assets/sfx/keyboard/keypress-003.wav +0 -0
  341. package/skills/global_config/brag/assets/sfx/keyboard/keypress-004.wav +0 -0
  342. package/skills/global_config/brag/assets/sfx/keyboard/keypress-005.wav +0 -0
  343. package/skills/global_config/brag/assets/sfx/keyboard/keypress-006.wav +0 -0
  344. package/skills/global_config/brag/assets/sfx/keyboard/keypress-007.wav +0 -0
  345. package/skills/global_config/brag/assets/sfx/keyboard/keypress-008.wav +0 -0
  346. package/skills/global_config/brag/assets/sfx/keyboard/keypress-009.wav +0 -0
  347. package/skills/global_config/brag/assets/sfx/keyboard/keypress-010.wav +0 -0
  348. package/skills/global_config/brag/assets/sfx/keyboard/keypress-011.wav +0 -0
  349. package/skills/global_config/brag/assets/sfx/keyboard/keypress-012.wav +0 -0
  350. package/skills/global_config/brag/assets/sfx/keyboard/keypress-013.wav +0 -0
  351. package/skills/global_config/brag/assets/sfx/keyboard/keypress-014.wav +0 -0
  352. package/skills/global_config/brag/assets/sfx/keyboard/keypress-015.wav +0 -0
  353. package/skills/global_config/brag/assets/sfx/keyboard/keypress-016.wav +0 -0
  354. package/skills/global_config/brag/assets/sfx/keyboard/keypress-017.wav +0 -0
  355. package/skills/global_config/brag/assets/sfx/keyboard/keypress-018.wav +0 -0
  356. package/skills/global_config/brag/assets/sfx/keyboard/keypress-019.wav +0 -0
  357. package/skills/global_config/brag/assets/sfx/keyboard/keypress-020.wav +0 -0
  358. package/skills/global_config/brag/assets/sfx/keyboard/keypress-021.wav +0 -0
  359. package/skills/global_config/brag/assets/sfx/keyboard/keypress-022.wav +0 -0
  360. package/skills/global_config/brag/assets/sfx/keyboard/keypress-023.wav +0 -0
  361. package/skills/global_config/brag/assets/sfx/keyboard/keypress-024.wav +0 -0
  362. package/skills/global_config/brag/assets/sfx/keyboard/keypress-025.wav +0 -0
  363. package/skills/global_config/brag/assets/sfx/keyboard/keypress-026.wav +0 -0
  364. package/skills/global_config/brag/assets/sfx/keyboard/keypress-027.wav +0 -0
  365. package/skills/global_config/brag/assets/sfx/keyboard/keypress-028.wav +0 -0
  366. package/skills/global_config/brag/assets/sfx/keyboard/keypress-029.wav +0 -0
  367. package/skills/global_config/brag/assets/sfx/keyboard/keypress-030.wav +0 -0
  368. package/skills/global_config/brag/assets/sfx/keyboard/keypress-031.wav +0 -0
  369. package/skills/global_config/brag/assets/sfx/keyboard/keypress-032.wav +0 -0
  370. package/skills/global_config/brag/assets/sfx/sfx-analysis.json +15584 -0
  371. package/skills/global_config/brag/assets/sfx/sfx-analysis.md +75 -0
  372. package/skills/global_config/brag/assets/sfx/ui/click1.ogg +0 -0
  373. package/skills/global_config/brag/assets/sfx/ui/click2.ogg +0 -0
  374. package/skills/global_config/brag/assets/sfx/ui/click3.ogg +0 -0
  375. package/skills/global_config/brag/assets/sfx/ui/click4.ogg +0 -0
  376. package/skills/global_config/brag/assets/sfx/ui/click5.ogg +0 -0
  377. package/skills/global_config/brag/assets/sfx/ui/mouseclick1.ogg +0 -0
  378. package/skills/global_config/brag/assets/sfx/ui/rollover1.ogg +0 -0
  379. package/skills/global_config/brag/assets/sfx/ui/rollover2.ogg +0 -0
  380. package/skills/global_config/brag/assets/sfx/ui/rollover4.ogg +0 -0
  381. package/skills/global_config/brag/assets/sfx/ui/rollover5.ogg +0 -0
  382. package/skills/global_config/brag/assets/sfx/ui/switch1.ogg +0 -0
  383. package/skills/global_config/brag/assets/sfx/ui/switch10.ogg +0 -0
  384. package/skills/global_config/brag/assets/sfx/ui/switch11.ogg +0 -0
  385. package/skills/global_config/brag/assets/sfx/ui/switch12.ogg +0 -0
  386. package/skills/global_config/brag/assets/sfx/ui/switch13.ogg +0 -0
  387. package/skills/global_config/brag/assets/sfx/ui/switch14.ogg +0 -0
  388. package/skills/global_config/brag/assets/sfx/ui/switch15.ogg +0 -0
  389. package/skills/global_config/brag/assets/sfx/ui/switch16.ogg +0 -0
  390. package/skills/global_config/brag/assets/sfx/ui/switch17.ogg +0 -0
  391. package/skills/global_config/brag/assets/sfx/ui/switch18.ogg +0 -0
  392. package/skills/global_config/brag/assets/sfx/ui/switch19.ogg +0 -0
  393. package/skills/global_config/brag/assets/sfx/ui/switch2.ogg +0 -0
  394. package/skills/global_config/brag/assets/sfx/ui/switch20.ogg +0 -0
  395. package/skills/global_config/brag/assets/sfx/ui/switch21.ogg +0 -0
  396. package/skills/global_config/brag/assets/sfx/ui/switch22.ogg +0 -0
  397. package/skills/global_config/brag/assets/sfx/ui/switch23.ogg +0 -0
  398. package/skills/global_config/brag/assets/sfx/ui/switch24.ogg +0 -0
  399. package/skills/global_config/brag/assets/sfx/ui/switch25.ogg +0 -0
  400. package/skills/global_config/brag/assets/sfx/ui/switch26.ogg +0 -0
  401. package/skills/global_config/brag/assets/sfx/ui/switch27.ogg +0 -0
  402. package/skills/global_config/brag/assets/sfx/ui/switch28.ogg +0 -0
  403. package/skills/global_config/brag/assets/sfx/ui/switch29.ogg +0 -0
  404. package/skills/global_config/brag/assets/sfx/ui/switch3.ogg +0 -0
  405. package/skills/global_config/brag/assets/sfx/ui/switch30.ogg +0 -0
  406. package/skills/global_config/brag/assets/sfx/ui/switch31.ogg +0 -0
  407. package/skills/global_config/brag/assets/sfx/ui/switch32.ogg +0 -0
  408. package/skills/global_config/brag/assets/sfx/ui/switch33.ogg +0 -0
  409. package/skills/global_config/brag/assets/sfx/ui/switch34.ogg +0 -0
  410. package/skills/global_config/brag/assets/sfx/ui/switch35.ogg +0 -0
  411. package/skills/global_config/brag/assets/sfx/ui/switch36.ogg +0 -0
  412. package/skills/global_config/brag/assets/sfx/ui/switch37.ogg +0 -0
  413. package/skills/global_config/brag/assets/sfx/ui/switch38.ogg +0 -0
  414. package/skills/global_config/brag/assets/sfx/ui/switch4.ogg +0 -0
  415. package/skills/global_config/brag/assets/sfx/ui/switch5.ogg +0 -0
  416. package/skills/global_config/brag/assets/sfx/ui/switch6.ogg +0 -0
  417. package/skills/global_config/brag/assets/sfx/ui/switch7.ogg +0 -0
  418. package/skills/global_config/brag/assets/sfx/ui/switch8.ogg +0 -0
  419. package/skills/global_config/brag/assets/sfx/ui/switch9.ogg +0 -0
  420. package/skills/global_config/brag/references/audio.md +307 -0
  421. package/skills/global_config/brag/references/step-1-inspect.md +133 -0
  422. package/skills/global_config/brag/references/step-2-plan.md +234 -0
  423. package/skills/global_config/brag/references/step-3-compose.md +210 -0
  424. package/skills/global_config/brag/references/step-4-deliver.md +168 -0
  425. package/skills/global_config/brag/references/tones.md +231 -0
  426. package/skills/global_config/brag/scripts/analyze_music_cues.py +309 -0
  427. package/skills/global_config/brag/scripts/pyproject.toml +14 -0
  428. package/skills/global_config/brag/scripts/uv.lock +819 -0
  429. package/skills/global_config/deja-memory/SKILL.md +42 -0
  430. package/skills/global_config/editable-design/LICENSE +202 -0
  431. package/skills/global_config/editable-design/README.md +34 -0
  432. package/skills/global_config/editable-design/SKILL.md +630 -0
  433. package/skills/global_config/editable-design/THIRD_PARTY_NOTICES.md +10 -0
  434. package/skills/global_config/editable-design/agents/openai.yaml +4 -0
  435. package/skills/global_config/editable-design/assets/editor/layer-editor.css +262 -0
  436. package/skills/global_config/editable-design/assets/editor/layer-editor.js +1929 -0
  437. package/skills/global_config/editable-design/assets/poster-starter/README.md +92 -0
  438. package/skills/global_config/editable-design/assets/poster-starter/assets/.gitkeep +0 -0
  439. package/skills/global_config/editable-design/assets/poster-starter/index.html +91 -0
  440. package/skills/global_config/editable-design/assets/poster-starter/poster.json +6 -0
  441. package/skills/global_config/editable-design/assets/poster-starter/reference/.gitkeep +1 -0
  442. package/skills/global_config/editable-design/assets/replay/icons/assets.png +0 -0
  443. package/skills/global_config/editable-design/assets/replay/icons/html.png +0 -0
  444. package/skills/global_config/editable-design/assets/replay/icons/input.png +0 -0
  445. package/skills/global_config/editable-design/assets/replay/icons/layers.png +0 -0
  446. package/skills/global_config/editable-design/assets/replay/icons/plan.png +0 -0
  447. package/skills/global_config/editable-design/assets/replay/icons/reference.png +0 -0
  448. package/skills/global_config/editable-design/assets/replay/icons/review.png +0 -0
  449. package/skills/global_config/editable-design/assets/replay/index.html +59 -0
  450. package/skills/global_config/editable-design/assets/replay/replay-motion.js +410 -0
  451. package/skills/global_config/editable-design/assets/replay/replay.css +380 -0
  452. package/skills/global_config/editable-design/assets/replay/replay.js +374 -0
  453. package/skills/global_config/editable-design/font-kit/package-lock.json +115 -0
  454. package/skills/global_config/editable-design/font-kit/package.json +19 -0
  455. package/skills/global_config/editable-design/pack.sh +20 -0
  456. package/skills/global_config/editable-design/references/asset-architecture.md +72 -0
  457. package/skills/global_config/editable-design/references/editor-pitfalls.md +70 -0
  458. package/skills/global_config/editable-design/references/editor-runtime.md +131 -0
  459. package/skills/global_config/editable-design/references/font-system.md +89 -0
  460. package/skills/global_config/editable-design/references/imagery.md +145 -0
  461. package/skills/global_config/editable-design/references/layout-typography.md +116 -0
  462. package/skills/global_config/editable-design/references/replay-contract.md +169 -0
  463. package/skills/global_config/editable-design/scripts/_browser.mjs +247 -0
  464. package/skills/global_config/editable-design/scripts/bake.mjs +87 -0
  465. package/skills/global_config/editable-design/scripts/build-replay.mjs +316 -0
  466. package/skills/global_config/editable-design/scripts/check-contract.mjs +293 -0
  467. package/skills/global_config/editable-design/scripts/check-fonts.sh +252 -0
  468. package/skills/global_config/editable-design/scripts/check-poster.sh +70 -0
  469. package/skills/global_config/editable-design/scripts/doctor.sh +78 -0
  470. package/skills/global_config/editable-design/scripts/explode.mjs +115 -0
  471. package/skills/global_config/editable-design/scripts/font-kit.mjs +187 -0
  472. package/skills/global_config/editable-design/scripts/import-asset.sh +23 -0
  473. package/skills/global_config/editable-design/scripts/init-poster.sh +64 -0
  474. package/skills/global_config/editable-design/scripts/install-font-kit.sh +13 -0
  475. package/skills/global_config/editable-design/scripts/package-lock.json +334 -0
  476. package/skills/global_config/editable-design/scripts/package.json +14 -0
  477. package/skills/global_config/editable-design/scripts/remove-chroma-key.py +201 -0
  478. package/skills/global_config/editable-design/scripts/render-poster.sh +202 -0
  479. package/skills/global_config/editable-design/scripts/trace.sh +159 -0
  480. package/skills/global_config/editable-design/scripts/verify-replay.mjs +97 -0
  481. package/skills/global_config/editable-design/scripts/verify.mjs +782 -0
  482. package/skills/global_config/editable-design/scripts/wire-editor.mjs +104 -0
  483. package/skills/global_config/editable-design/tests/fixtures/small-explode/index.html +86 -0
  484. package/skills/global_config/plan-canvas/SKILL.md +8 -0
@@ -0,0 +1,104 @@
1
+ # Data Flow Renderer
2
+
3
+ Render `diagram_type: "dataflow"` JSON files into the standard Archify HTML
4
+ template.
5
+
6
+ ```bash
7
+ node archify/renderers/dataflow/render-dataflow.mjs input.dataflow.json output.html
8
+ ```
9
+
10
+ The renderer validates input against `archify/schemas/dataflow.schema.json`
11
+ with the bundled standalone validator. No dependency installation is required.
12
+
13
+ If `output.html` is omitted, the renderer uses `meta.output` from the JSON file
14
+ or falls back to `dataflow.html` in the current working directory.
15
+
16
+ ## Input
17
+
18
+ Data-flow JSON files must set:
19
+
20
+ ```json
21
+ {
22
+ "schema_version": 1,
23
+ "diagram_type": "dataflow",
24
+ "meta": {
25
+ "title": "Product Analytics Data Flow",
26
+ "viewBox": [940, 720]
27
+ },
28
+ "stages": [],
29
+ "nodes": [],
30
+ "flows": [],
31
+ "cards": []
32
+ }
33
+ ```
34
+
35
+ A complete worked example lives at
36
+ `archify/examples/product-analytics.dataflow.json`.
37
+
38
+ The schema lives at:
39
+
40
+ ```text
41
+ archify/schemas/dataflow.schema.json
42
+ ```
43
+
44
+ ## Legend
45
+
46
+ The default visual legend derives kinds from `flows[].variant` (omitting
47
+ `variant` means `default`) and adds `database` only when a database node exists.
48
+ Supported `meta.legend.entries` keys, in stable order, are `emphasis`,
49
+ `security`, `dashed`, `database`, and `default`. Flow variants remain
50
+ visual-only because Archify has no compiled edge-kind facts in this slice. A
51
+ present `database` entry is different: it comes from exact
52
+ `nodes[].type: "database"` facts, so it publishes the normal Semantic Legend
53
+ count, accessible name, and keyboard interaction. Forcing `database` visible
54
+ without a database node keeps it visual-only.
55
+
56
+ ## Layout budget
57
+
58
+ | Constant | Value |
59
+ |----------|-------|
60
+ | viewBox | default `[940, 720]`; schema minimum `[360, 360]` |
61
+ | Stages (2–5) | centers at x = 100 + stage×215; stage band 168 wide, header at y 46 |
62
+ | Row tops (`row` 0–4) | y = 128, 242, 356, 470, 584 (plus `yOffset`) |
63
+ | Default node | 112×58 |
64
+ | Node area | x within `[24, width − 24]`; y within `[104, height − 74]` |
65
+ | Node spacing | ≥10px between any two nodes (checked across stages and rows) |
66
+ | Flow length | ≥34px between endpoints |
67
+ | Legend row | y = height − 36 |
68
+
69
+ Route presets for flows: `straight`, `vertical-channel`, `bottom-channel`,
70
+ `top-channel`, explicit `via` points, or the default `auto` (midpoint elbow).
71
+
72
+ ## Design Rules
73
+
74
+ - Use stages for data lifecycle boundaries: source, ingest, process, store,
75
+ consume.
76
+ - Place nodes by stage index and row index; do not hand-place raw SVG for the
77
+ common case.
78
+ - Use flow labels to name the data asset, not the transport primitive:
79
+ `clickstream`, `identity map`, `normalized facts`, `feature vectors`.
80
+ - Use `classification` for short sensitivity or governance context:
81
+ `PII touch`, `non-PII`, `approved only`, `batch`, `read-only`.
82
+ - Use `security` for PII, policy, consent, access-control, or restricted joins.
83
+ - Use `emphasis` for the primary data path and `dashed` for async or batch
84
+ derivations.
85
+ - Keep labels short enough to fit in narrow previews.
86
+
87
+ Schema violations exit non-zero with path-prefixed messages annotated with the
88
+ element's id or label. The renderer additionally fails when it can detect
89
+ layout problems, including missing stages, duplicate node IDs, nodes outside
90
+ the readable diagram area, node overlap, labels colliding with nodes or other
91
+ labels, labels wider than their node, unknown flow endpoints, missing flow
92
+ labels, unreadably short flows, flows crossing unrelated nodes (2px Clean Flow
93
+ clearance), or stages that exceed the viewBox. Stage frames remain intentional
94
+ pass-through containers. Text width
95
+ is estimated CJK-aware: fullwidth glyphs count as two units.
96
+
97
+ Set `meta.quality_profile` to `showcase` for polished delivery. Unrelated proper
98
+ X crossings then fail with `composition/proper-crossing`; default `standard`
99
+ keeps them as artifact-receipt warnings. Collinear stage corridors are outside
100
+ the proper-X rule, but a separate gate warns in `standard` and fails in
101
+ `showcase` when unrelated flows overlap for at least 8px. Shared semantic
102
+ endpoints, point touches, and shorter overlaps remain valid. Showcase also
103
+ rejects any route segment below 8px and any interior turn segment below 16px;
104
+ ordinary 8–15px endpoint stubs remain valid.
@@ -0,0 +1,483 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { esc, renderDefinitions, renderSemanticSigil, textUnits } from '../shared/utils.mjs';
4
+ import { animateAttr, focusEdgeAttrs, focusNodeAttrs, focusNodeTitle, loadDiagramWithBrandMarks, writeDiagram, svgAccessibleText, svgRootAttrs } from '../shared/cli.mjs';
5
+ import { throwDiagnosticProblems } from '../shared/diagnostics.mjs';
6
+ import { resolveLegend, renderLegend as renderResolvedLegend } from '../shared/legend.mjs';
7
+ import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs';
8
+ import { brandLabelFitWidth, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs';
9
+ import { translateMessage as i18nText } from '../shared/i18n.mjs';
10
+ import {
11
+ asArray,
12
+ isFinitePoint,
13
+ rectsOverlap,
14
+ cleanEndpointSideProblems,
15
+ cleanFlowProblems,
16
+ cleanCrossingProblems,
17
+ cleanAmbiguousCorridorProblems,
18
+ cleanBorderRunProblems,
19
+ cleanRouteRhythmProblems,
20
+ cleanLabelRouteClearanceProblems,
21
+ suggestLabelObstacleFix,
22
+ suggestLabelPairFix,
23
+ anchor,
24
+ automaticPortSpread,
25
+ defaultFromSide,
26
+ defaultToSide,
27
+ chosenSide,
28
+ polylinePath,
29
+ routePointsValue,
30
+ labelPoint,
31
+ componentFill,
32
+ componentText,
33
+ arrowClassMap,
34
+ variantAccent
35
+ } from '../shared/geometry.mjs';
36
+
37
+ const nodeTextFit = {
38
+ sublabelPreferred: 7,
39
+ sublabelMinimum: 6,
40
+ tagPreferred: 7,
41
+ tagMinimum: 6,
42
+ };
43
+
44
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
45
+ const { diagram: dataflow, template, outPath } = await loadDiagramWithBrandMarks({
46
+ rendererDir: __dirname,
47
+ diagramType: 'dataflow',
48
+ defaultExample: 'product-analytics.dataflow.json'
49
+ });
50
+
51
+ const viewBox = dataflow.meta?.viewBox || [940, 720];
52
+ const layout = {
53
+ stageY: 46,
54
+ stageH: 36,
55
+ stageBottomPad: 74,
56
+ leftX: 100,
57
+ colGap: 215,
58
+ stageW: 168,
59
+ nodeW: 112,
60
+ nodeH: 58,
61
+ rowYs: [128, 242, 356, 470, 584],
62
+ labelH: 16
63
+ };
64
+
65
+ function flowLabelSize(flow) {
66
+ const longestLine = Math.max(textUnits(flow.label), textUnits(flow.classification || ''));
67
+ return {
68
+ width: Math.round(Math.max(34, longestLine * 4.9 + 12) * 10) / 10,
69
+ height: flow.classification ? 27 : layout.labelH,
70
+ };
71
+ }
72
+
73
+ function stageX(index) {
74
+ return layout.leftX + index * layout.colGap;
75
+ }
76
+
77
+ function stageFrame(stage, index) {
78
+ return {
79
+ id: index,
80
+ label: stage.label,
81
+ kind: 'stage',
82
+ x: stageX(index) - layout.stageW / 2,
83
+ y: layout.stageY,
84
+ width: layout.stageW,
85
+ height: viewBox[1] - layout.stageY - layout.stageBottomPad,
86
+ radius: 10,
87
+ };
88
+ }
89
+
90
+ const compositionFrames = asArray(dataflow.stages).map(stageFrame);
91
+
92
+ function measureNode(node) {
93
+ const width = node.width || layout.nodeW;
94
+ const height = node.height || layout.nodeH;
95
+ const cx = stageX(node.stage);
96
+ const y = layout.rowYs[node.row] + (node.yOffset || 0);
97
+ return {
98
+ ...node,
99
+ width,
100
+ height,
101
+ cx,
102
+ cy: y + height / 2,
103
+ x: cx - width / 2,
104
+ y
105
+ };
106
+ }
107
+
108
+ const nodes = new Map(asArray(dataflow.nodes).map((node) => [node.id, measureNode(node)]));
109
+ const nodeSteps = new Map();
110
+ for (const [index, flow] of asArray(dataflow.flows).entries()) {
111
+ if (!nodeSteps.has(flow.from)) nodeSteps.set(flow.from, index);
112
+ if (!nodeSteps.has(flow.to)) nodeSteps.set(flow.to, index + 1);
113
+ }
114
+ for (const [index, node] of asArray(dataflow.nodes).entries()) {
115
+ if (!nodeSteps.has(node.id)) nodeSteps.set(node.id, index);
116
+ }
117
+
118
+ function validateDataflow() {
119
+ const problems = [];
120
+ if (nodes.size !== asArray(dataflow.nodes).length) problems.push('Node ids must be unique.');
121
+
122
+ const stageCount = asArray(dataflow.stages).length;
123
+ for (const node of nodes.values()) {
124
+ if (typeof node.stage !== 'number' || node.stage < 0 || node.stage >= stageCount) {
125
+ problems.push(`Node "${node.id}" uses invalid stage ${node.stage} — valid stages are 0..${stageCount - 1}.`);
126
+ }
127
+ if (typeof node.row !== 'number' || node.row < 0 || node.row >= layout.rowYs.length) {
128
+ problems.push(`Node "${node.id}" uses invalid row ${node.row} — valid rows are 0..${layout.rowYs.length - 1}.`);
129
+ }
130
+ if (!isFinitePoint(node.x, node.y, node.cx, node.cy)) {
131
+ problems.push(`Node "${node.id}" produced non-finite coordinates — check stage, row, width, height, and yOffset are numbers.`);
132
+ continue;
133
+ }
134
+ if (node.x < 24 || node.x + node.width > viewBox[0] - 24) {
135
+ problems.push(`Node "${node.id}" exceeds the horizontal bounds of the viewBox — reduce node.width or increase meta.viewBox[0].`);
136
+ }
137
+ if (node.y < layout.stageY + layout.stageH + 22 || node.y + node.height > viewBox[1] - layout.stageBottomPad) {
138
+ problems.push(`Node "${node.id}" exceeds the readable diagram area — keep y between ${layout.stageY + layout.stageH + 22} and ${viewBox[1] - layout.stageBottomPad} (adjust row/yOffset or increase meta.viewBox[1]).`);
139
+ }
140
+ const estLabelW = textUnits(node.label) * 6.2;
141
+ if (estLabelW > node.width + 6) {
142
+ problems.push(`Label "${node.label}" (~${Math.round(estLabelW)}px) is wider than node "${node.id}" (${node.width}px) — shorten the label or increase node.width.`);
143
+ }
144
+ const brandRailProblem = brandTopRailProblem(node, node.width, 8);
145
+ if (brandRailProblem) problems.push(brandRailProblem);
146
+ // sublabel and tag render as single unwrapped <text> elements; shrink-to-fit
147
+ // handles the ordinary case, this rejects what it cannot rescue.
148
+ const availableTextW = availableNodeTextWidth(node.width);
149
+ for (const [field, value, minimum] of [
150
+ ['Sublabel', node.sublabel, nodeTextFit.sublabelMinimum],
151
+ ['Tag', node.tag, nodeTextFit.tagMinimum],
152
+ ]) {
153
+ if (!value) continue;
154
+ const minimumW = minimumNodeTextWidth(value, minimum);
155
+ if (minimumW > availableTextW) {
156
+ problems.push(`${field} "${value}" needs ~${Math.ceil(minimumW)}px at the ${minimum}px legible minimum, but node "${node.id}" provides ${availableTextW}px — shorten the ${field.toLowerCase()} or increase node.width.`);
157
+ }
158
+ }
159
+ }
160
+
161
+ const nodeList = asArray(dataflow.nodes);
162
+ for (let i = 0; i < nodeList.length; i += 1) {
163
+ for (let j = i + 1; j < nodeList.length; j += 1) {
164
+ const a = nodes.get(nodeList[i].id);
165
+ const b = nodes.get(nodeList[j].id);
166
+ if (rectsOverlap(a, b, 10)) {
167
+ problems.push(`Nodes "${a.id}" and "${b.id}" are less than 10px apart — move one to another stage/row or adjust yOffset.`);
168
+ }
169
+ }
170
+ }
171
+
172
+ for (const flow of asArray(dataflow.flows)) {
173
+ if (!nodes.has(flow.from)) problems.push(`Flow "${flow.label || flow.from}" references unknown source "${flow.from}".`);
174
+ if (!nodes.has(flow.to)) problems.push(`Flow "${flow.label || flow.to}" references unknown target "${flow.to}".`);
175
+ if (!flow.label) problems.push(`Flow "${flow.from}" -> "${flow.to}" must include a short data label.`);
176
+ if (nodes.has(flow.from) && nodes.has(flow.to)) {
177
+ const routed = pathFor(flow);
178
+ const [start, end] = [routed.points[0], routed.points[routed.points.length - 1]];
179
+ const distance = Math.hypot(end[0] - start[0], end[1] - start[1]);
180
+ if (distance < 34) problems.push(`Flow "${flow.label}" is too short (${Math.round(distance)}px; minimum 34px) — route it through a channel or spread its nodes.`);
181
+ if (Array.isArray(flow.via)) {
182
+ for (let segmentIndex = 0; segmentIndex < routed.points.length - 1; segmentIndex += 1) {
183
+ const segmentStart = routed.points[segmentIndex];
184
+ const segmentEnd = routed.points[segmentIndex + 1];
185
+ const isDiagonal = Math.abs(segmentStart[0] - segmentEnd[0]) > 0.01
186
+ && Math.abs(segmentStart[1] - segmentEnd[1]) > 0.01;
187
+ if (!isDiagonal) continue;
188
+ const viaIndex = Math.min(segmentIndex, flow.via.length - 1);
189
+ problems.push(`Flow "${flow.label}" has a diagonal segment from (${segmentStart.join(', ')}) to (${segmentEnd.join(', ')}) — align via[${viaIndex}] with its adjacent point by sharing the same x or y coordinate.`);
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ problems.push(...cleanEndpointSideProblems({
196
+ relations: dataflow.flows,
197
+ endpointIds: new Set(nodes.keys()),
198
+ pathFor,
199
+ diagramType: 'dataflow',
200
+ relationCollection: 'flows',
201
+ fromSideFor: (flow) => flowSides(flow).fromSide,
202
+ toSideFor: (flow) => flowSides(flow).toSide,
203
+ routeHint: 'keep automatic routing, or choose fromSide/toSide and via points whose first and final segments cross node borders perpendicularly',
204
+ }));
205
+ problems.push(...cleanFlowProblems({
206
+ relations: dataflow.flows,
207
+ obstacles: nodes.values(),
208
+ pathFor,
209
+ diagramType: 'dataflow',
210
+ relationCollection: 'flows',
211
+ obstacleKind: 'node',
212
+ routeHint: 'adjust fromSide/toSide, set route/via or channelX/channelY, or move the node to another stage/row'
213
+ }));
214
+ problems.push(...cleanCrossingProblems({
215
+ relations: dataflow.flows,
216
+ endpointIds: new Set(nodes.keys()),
217
+ pathFor,
218
+ diagramType: 'dataflow',
219
+ relationCollection: 'flows',
220
+ profile: dataflow.meta?.quality_profile,
221
+ routeHint: 'adjust route/via or channelX/channelY so the flows use separate stage corridors'
222
+ }));
223
+ problems.push(...cleanAmbiguousCorridorProblems({
224
+ relations: dataflow.flows,
225
+ endpointIds: new Set(nodes.keys()),
226
+ pathFor,
227
+ diagramType: 'dataflow',
228
+ relationCollection: 'flows',
229
+ profile: dataflow.meta?.quality_profile,
230
+ routeHint: 'adjust route/via or channelX/channelY so unrelated flows do not visually merge'
231
+ }));
232
+ problems.push(...cleanBorderRunProblems({
233
+ relations: dataflow.flows,
234
+ endpointIds: new Set(nodes.keys()),
235
+ frames: compositionFrames,
236
+ pathFor,
237
+ diagramType: 'dataflow',
238
+ relationCollection: 'flows',
239
+ profile: dataflow.meta?.quality_profile,
240
+ routeHint: 'adjust route/via or channelX/channelY so the flow crosses the stage perpendicularly instead of following its border'
241
+ }));
242
+ problems.push(...cleanRouteRhythmProblems({
243
+ relations: dataflow.flows,
244
+ endpointIds: new Set(nodes.keys()),
245
+ pathFor,
246
+ diagramType: 'dataflow',
247
+ relationCollection: 'flows',
248
+ profile: dataflow.meta?.quality_profile,
249
+ routeHint: 'adjust route/via or channelX/channelY so each turn uses a clear inter-stage corridor'
250
+ }));
251
+
252
+ const labelRects = [];
253
+ for (const [flowIndex, flow] of asArray(dataflow.flows).entries()) {
254
+ if (!flow.label || !nodes.has(flow.from) || !nodes.has(flow.to)) continue;
255
+ const [lx, ly] = labelPoint(flow, pathFor(flow).points);
256
+ const { width, height } = flowLabelSize(flow);
257
+ labelRects.push({ relation: flow, relationIndex: flowIndex, label: flow.label, x: lx - width / 2, y: ly - 11, width, height, lx, ly });
258
+ }
259
+ for (const rect of labelRects) {
260
+ for (const node of nodes.values()) {
261
+ if (rectsOverlap(rect, node, -2)) {
262
+ problems.push(`Label "${rect.label}" overlaps node "${node.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, node, 'node')}`);
263
+ }
264
+ }
265
+ }
266
+ for (let i = 0; i < labelRects.length; i += 1) {
267
+ for (let j = i + 1; j < labelRects.length; j += 1) {
268
+ if (rectsOverlap(labelRects[i], labelRects[j], -2)) {
269
+ problems.push(`Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — adjust labelDx/labelDy.\n${suggestLabelPairFix(labelRects[i], labelRects[j])}`);
270
+ }
271
+ }
272
+ }
273
+ problems.push(...cleanLabelRouteClearanceProblems({
274
+ relations: dataflow.flows,
275
+ labels: labelRects,
276
+ endpointIds: new Set(nodes.keys()),
277
+ pathFor,
278
+ diagramType: 'dataflow',
279
+ relationCollection: 'flows',
280
+ profile: dataflow.meta?.quality_profile,
281
+ routeHint: 'adjust labelAt, labelDx, labelDy, or labelSegment; otherwise adjust the other flow route/via/channelX/channelY'
282
+ }));
283
+
284
+ const lastStageX = stageX(asArray(dataflow.stages).length - 1);
285
+ if (lastStageX + layout.stageW / 2 > viewBox[0] - 24) {
286
+ problems.push(`Stages exceed viewBox width — set meta.viewBox[0] to at least ${Math.ceil(lastStageX + layout.stageW / 2 + 24)}.`);
287
+ }
288
+
289
+ if (problems.length) {
290
+ throwDiagnosticProblems('Data-flow layout validation failed', problems, {
291
+ subject: { diagramType: 'dataflow' },
292
+ });
293
+ }
294
+ }
295
+
296
+ function routeVia(flow, from, to, start, end) {
297
+ if (flow.via) return flow.via;
298
+ switch (flow.route || 'auto') {
299
+ case 'straight':
300
+ return [];
301
+ case 'vertical-channel': {
302
+ const x = flow.channelX ?? start[0] + (end[0] > start[0] ? 44 : -44);
303
+ return [[x, start[1]], [x, end[1]]];
304
+ }
305
+ case 'bottom-channel': {
306
+ const y = flow.channelY ?? Math.max(from.y + from.height, to.y + to.height) + 26;
307
+ return [[start[0], y], [end[0], y]];
308
+ }
309
+ case 'top-channel': {
310
+ const y = flow.channelY ?? Math.min(from.y, to.y) - 24;
311
+ return [[start[0], y], [end[0], y]];
312
+ }
313
+ case 'auto':
314
+ default: {
315
+ if (Math.abs(start[1] - end[1]) < 4) return [];
316
+ const midX = start[0] + (end[0] - start[0]) / 2;
317
+ return [[midX, start[1]], [midX, end[1]]];
318
+ }
319
+ }
320
+ }
321
+
322
+ const pathCache = new Map();
323
+
324
+ function flowSides(flow) {
325
+ const from = nodes.get(flow.from);
326
+ const to = nodes.get(flow.to);
327
+ return {
328
+ fromSide: chosenSide(flow.fromSide, defaultFromSide(from, to)),
329
+ toSide: chosenSide(flow.toSide, defaultToSide(from, to)),
330
+ };
331
+ }
332
+
333
+ const automaticPorts = automaticPortSpread(dataflow.flows, nodes, {
334
+ sideFor: (flow, endpoint) => flowSides(flow)[endpoint === 'source' ? 'fromSide' : 'toSide'],
335
+ });
336
+
337
+ function pathFor(flow) {
338
+ if (pathCache.has(flow)) return pathCache.get(flow);
339
+ const from = nodes.get(flow.from);
340
+ const to = nodes.get(flow.to);
341
+ const ports = automaticPorts.get(flow);
342
+ const { fromSide, toSide } = flowSides(flow);
343
+ const start = ports?.from || anchor(from, fromSide);
344
+ const end = ports?.to || anchor(to, toSide);
345
+ // Drop consecutive duplicate points so a purely vertical (or horizontal)
346
+ // auto-route never emits a zero-length final segment — SVG derives
347
+ // marker-end orientation from the last segment, and a degenerate segment
348
+ // leaves the arrowhead angle undefined (see #169).
349
+ const rawPoints = [start, ...routeVia(flow, from, to, start, end), end];
350
+ const points = [];
351
+ for (const p of rawPoints) {
352
+ const prev = points.at(-1);
353
+ if (!prev || Math.abs(p[0] - prev[0]) > 0.0001 || Math.abs(p[1] - prev[1]) > 0.0001) {
354
+ points.push(p);
355
+ }
356
+ }
357
+ // Guard against an all-degenerate route (e.g. start === end): keep both
358
+ // endpoints so the path is still well-formed even if the marker is hidden.
359
+ if (points.length < 2) points.push(end);
360
+ const routed = { d: polylinePath(points), points };
361
+ pathCache.set(flow, routed);
362
+ return routed;
363
+ }
364
+
365
+ function renderStage(stage, index) {
366
+ const frame = compositionFrames[index];
367
+ const cx = stageX(index);
368
+ return ` <rect data-graph-role="structural-frame" data-composition-frame-kind="stage" data-composition-frame-id="${index}" x="${frame.x}" y="${frame.y}" width="${frame.width}" height="${frame.height}" rx="${frame.radius}" class="c-lane" stroke-width="1"/>
369
+ <text x="${cx}" y="${layout.stageY + 22}" class="t-dim" font-size="9" font-weight="600" text-anchor="middle">${String(index + 1).padStart(2, '0')} / ${esc(stage.label)}</text>`;
370
+ }
371
+
372
+ function renderNode(node) {
373
+ const fill = componentFill[node.type] || 'c-external';
374
+ const accent = componentText[node.type] || 't-muted';
375
+ const hasSub = node.sublabel != null && node.sublabel !== '';
376
+ const sub = hasSub
377
+ ? `\n <text data-detail="context" x="${node.cx}" y="${node.y + 37}" class="t-muted" font-size="${fittedNodeFontSize(node.sublabel, node.width, nodeTextFit.sublabelPreferred, nodeTextFit.sublabelMinimum)}" text-anchor="middle">${esc(node.sublabel)}</text>`
378
+ : '';
379
+ const tag = node.tag
380
+ ? `\n <text data-detail="fine" x="${node.cx}" y="${node.y + node.height - 11}" class="${accent}" font-size="${fittedNodeFontSize(node.tag, node.width, nodeTextFit.tagPreferred, nodeTextFit.tagMinimum)}" text-anchor="middle">${esc(node.tag)}</text>`
381
+ : '';
382
+ const stage = asArray(dataflow.stages)[node.stage];
383
+ const context = stage
384
+ ? `${String(node.stage + 1).padStart(2, '0')} / ${stage.label}`
385
+ : i18nText(dataflow.meta.locale, 'node.context.dataflow');
386
+ const brand = renderBrandMark(node, { x: node.x + node.width - 22, y: node.y + 6 });
387
+ const labelFontSize = fittedNodeFontSize(node.label, brandLabelFitWidth(node, node.width), 10, 8);
388
+ const passport = { kind: node.type, sublabel: node.sublabel, tag: node.tag, context, ...brandMetadataFor(node) };
389
+ return ` <g ${focusNodeAttrs(node.id, node.label, passport, dataflow.meta.locale)}>
390
+ ${focusNodeTitle(node.label, passport)}
391
+ <rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="6" class="c-mask"/>
392
+ <rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="6" class="${fill}"${animateAttr(dataflow.meta, 'node', nodeSteps.get(node.id))} stroke-width="1.5"/>
393
+ ${renderSemanticSigil(node.type, { x: node.x + 6, y: node.y + 6 })}${brand ? `\n ${brand}` : ''}
394
+ <text data-node-label=""${hasSub ? ' data-detail-anchor=""' : ''} x="${node.cx}" y="${node.y + 21}" class="t-primary" font-size="${labelFontSize}" font-weight="600" text-anchor="middle">${esc(node.label)}</text>${sub}${tag}
395
+ </g>`;
396
+ }
397
+
398
+ function renderFlowPath(flow, index) {
399
+ const [cls, marker] = arrowClassMap[flow.variant || 'default'] || arrowClassMap.default;
400
+ const routed = pathFor(flow);
401
+ const strokeWidth = flow.width || (flow.variant === 'emphasis' ? 1.8 : 1.4);
402
+ return ` <path ${focusEdgeAttrs(flow.from, flow.to, flow.label, index, flow.id)} data-composition-points="${routePointsValue(routed.points)}" d="${routed.d}" class="${cls}"${animateAttr(dataflow.meta, 'edge', index)} stroke-width="${strokeWidth}" marker-end="url(#${marker})"/>`;
403
+ }
404
+
405
+ function renderFlowLabel(flow, index) {
406
+ const routed = pathFor(flow);
407
+ const [lx, ly] = labelPoint(flow, routed.points);
408
+ const { width: labelW, height: labelH } = flowLabelSize(flow);
409
+ const classification = flow.classification
410
+ ? `\n <text data-detail="fine" x="${lx}" y="${ly + 11}" class="t-dim" font-size="7" text-anchor="middle">${esc(flow.classification)}</text>`
411
+ : '';
412
+ return ` <g data-detail="context" ${focusEdgeAttrs(flow.from, flow.to, flow.label, index, flow.id)}>
413
+ <rect x="${lx - labelW / 2}" y="${ly - 11}" width="${labelW}" height="${labelH}" rx="4" class="c-mask"/>
414
+ <text x="${lx}" y="${ly}" class="${variantAccent(flow.variant)}" font-size="8" text-anchor="middle">${esc(flow.label)}</text>${classification}
415
+ </g>`;
416
+ }
417
+
418
+ const LEGEND_CATALOG = [
419
+ { kind: 'emphasis', className: 'a-emphasis', marker: 'arrowhead-emphasis', strokeWidth: 1.8, swatchWidth: 34, swatchGap: 9, interactive: false },
420
+ { kind: 'security', className: 'a-security', marker: 'arrowhead-security', swatchWidth: 34, swatchGap: 9, interactive: false },
421
+ { kind: 'dashed', className: 'a-dashed', marker: 'arrowhead-dashed', swatchWidth: 34, swatchGap: 9, interactive: false },
422
+ { kind: 'database' },
423
+ { kind: 'default', className: 'a-default', marker: 'arrowhead', swatchWidth: 34, swatchGap: 9, interactive: false },
424
+ ].map((entry) => ({
425
+ ...entry,
426
+ label: i18nText(dataflow.meta.locale, `legend.dataflow.${entry.kind}`),
427
+ }));
428
+
429
+ function renderLegend() {
430
+ const presentKinds = new Set(asArray(dataflow.flows).map((flow) => flow.variant || 'default'));
431
+ if ([...nodes.values()].some((node) => node.type === 'database')) presentKinds.add('database');
432
+ const entries = resolveLegend(dataflow.meta?.legend, LEGEND_CATALOG, presentKinds);
433
+ return renderResolvedLegend({
434
+ entries,
435
+ locale: dataflow.meta.locale,
436
+ layout: {
437
+ x: 40,
438
+ baselineY: viewBox[1] - 36,
439
+ width: viewBox[0] - 80,
440
+ minTitleY: viewBox[1] - 66,
441
+ unfit: dataflow.meta?.legend === undefined ? 'hide' : 'error',
442
+ diagramType: 'dataflow',
443
+ },
444
+ renderSwatch: (entry) => entry.kind === 'database'
445
+ ? `<rect x="${entry.x}" y="${entry.baseline - 8}" width="14" height="9" rx="2" class="c-database" stroke-width="1"/>`
446
+ : `<path d="M ${entry.x} ${entry.baseline - 3} L ${entry.x + 34} ${entry.baseline - 3}" class="${entry.className}" stroke-width="${entry.strokeWidth || 1.4}" marker-end="url(#${entry.marker})"/>`,
447
+ });
448
+ }
449
+
450
+ function renderSvg() {
451
+ return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(dataflow.meta)}>
452
+ ${svgAccessibleText(dataflow.meta, 'dataflow')}
453
+ ${renderDefinitions()}
454
+
455
+ <!-- Background Grid -->
456
+ <rect width="100%" height="100%" fill="url(#grid)" />
457
+
458
+ <!-- Data Stages -->
459
+ ${dataflow.stages.map(renderStage).join('\n\n')}
460
+
461
+ <!-- Flow paths -->
462
+ ${asArray(dataflow.flows).map(renderFlowPath).join('\n')}
463
+
464
+ <!-- Nodes -->
465
+ ${[...nodes.values()].map(renderNode).join('\n\n')}
466
+
467
+ <!-- Flow labels -->
468
+ ${asArray(dataflow.flows).map(renderFlowLabel).join('\n')}
469
+
470
+ <!-- Legend -->
471
+ ${renderLegend()}
472
+ </svg>`;
473
+ }
474
+
475
+ validateDataflow();
476
+ writeDiagram({
477
+ outPath,
478
+ template,
479
+ diagramType: 'dataflow',
480
+ meta: dataflow.meta,
481
+ svg: renderSvg(),
482
+ cards: dataflow.cards,
483
+ });