@oriro/orirocli 0.1.8 → 0.1.9

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 (350) hide show
  1. package/ATTRIBUTION.md +8 -0
  2. package/LICENSE +21 -0
  3. package/package.json +1 -1
  4. package/skills/21stdev/SKILL.md +64 -0
  5. package/skills/graphify/SKILL.md +619 -0
  6. package/skills/graphify/__init__.py +28 -0
  7. package/skills/graphify/__main__.py +4582 -0
  8. package/skills/graphify/affected.py +154 -0
  9. package/skills/graphify/always_on/agents-md.md +12 -0
  10. package/skills/graphify/always_on/antigravity-rules.md +14 -0
  11. package/skills/graphify/always_on/claude-md.md +9 -0
  12. package/skills/graphify/always_on/gemini-md.md +9 -0
  13. package/skills/graphify/always_on/kiro-steering.md +5 -0
  14. package/skills/graphify/always_on/vscode-instructions.md +17 -0
  15. package/skills/graphify/analyze.py +724 -0
  16. package/skills/graphify/benchmark.py +155 -0
  17. package/skills/graphify/build.py +487 -0
  18. package/skills/graphify/cache.py +417 -0
  19. package/skills/graphify/callflow_html.py +2020 -0
  20. package/skills/graphify/cluster.py +272 -0
  21. package/skills/graphify/command-kilo.md +15 -0
  22. package/skills/graphify/dedup.py +429 -0
  23. package/skills/graphify/detect.py +1379 -0
  24. package/skills/graphify/diagnostics.py +390 -0
  25. package/skills/graphify/export.py +1408 -0
  26. package/skills/graphify/extract.py +11570 -0
  27. package/skills/graphify/global_graph.py +159 -0
  28. package/skills/graphify/google_workspace.py +223 -0
  29. package/skills/graphify/hooks.py +457 -0
  30. package/skills/graphify/ingest.py +331 -0
  31. package/skills/graphify/llm.py +1896 -0
  32. package/skills/graphify/manifest.py +4 -0
  33. package/skills/graphify/mcp_ingest.py +392 -0
  34. package/skills/graphify/multigraph_compat.py +212 -0
  35. package/skills/graphify/pg_introspect.py +142 -0
  36. package/skills/graphify/prs.py +748 -0
  37. package/skills/graphify/querylog.py +70 -0
  38. package/skills/graphify/report.py +218 -0
  39. package/skills/graphify/scip_ingest.py +363 -0
  40. package/skills/graphify/security.py +336 -0
  41. package/skills/graphify/semantic_cleanup.py +319 -0
  42. package/skills/graphify/serve.py +1309 -0
  43. package/skills/graphify/skill-aider.md +1246 -0
  44. package/skills/graphify/skill-amp.md +613 -0
  45. package/skills/graphify/skill-claw.md +616 -0
  46. package/skills/graphify/skill-codex.md +613 -0
  47. package/skills/graphify/skill-copilot.md +616 -0
  48. package/skills/graphify/skill-devin.md +1372 -0
  49. package/skills/graphify/skill-droid.md +613 -0
  50. package/skills/graphify/skill-kilo.md +625 -0
  51. package/skills/graphify/skill-kiro.md +615 -0
  52. package/skills/graphify/skill-opencode.md +608 -0
  53. package/skills/graphify/skill-pi.md +615 -0
  54. package/skills/graphify/skill-trae.md +614 -0
  55. package/skills/graphify/skill-vscode.md +612 -0
  56. package/skills/graphify/skill-windows.md +651 -0
  57. package/skills/graphify/skills/amp/references/add-watch.md +56 -0
  58. package/skills/graphify/skills/amp/references/exports.md +71 -0
  59. package/skills/graphify/skills/amp/references/extraction-spec.md +68 -0
  60. package/skills/graphify/skills/amp/references/github-and-merge.md +46 -0
  61. package/skills/graphify/skills/amp/references/hooks.md +33 -0
  62. package/skills/graphify/skills/amp/references/query.md +249 -0
  63. package/skills/graphify/skills/amp/references/transcribe.md +48 -0
  64. package/skills/graphify/skills/amp/references/update.md +179 -0
  65. package/skills/graphify/skills/claude/references/add-watch.md +56 -0
  66. package/skills/graphify/skills/claude/references/exports.md +71 -0
  67. package/skills/graphify/skills/claude/references/extraction-spec.md +68 -0
  68. package/skills/graphify/skills/claude/references/github-and-merge.md +46 -0
  69. package/skills/graphify/skills/claude/references/hooks.md +33 -0
  70. package/skills/graphify/skills/claude/references/query.md +103 -0
  71. package/skills/graphify/skills/claude/references/transcribe.md +48 -0
  72. package/skills/graphify/skills/claude/references/update.md +179 -0
  73. package/skills/graphify/skills/claw/references/add-watch.md +56 -0
  74. package/skills/graphify/skills/claw/references/exports.md +71 -0
  75. package/skills/graphify/skills/claw/references/extraction-spec.md +29 -0
  76. package/skills/graphify/skills/claw/references/github-and-merge.md +46 -0
  77. package/skills/graphify/skills/claw/references/hooks.md +33 -0
  78. package/skills/graphify/skills/claw/references/query.md +249 -0
  79. package/skills/graphify/skills/claw/references/transcribe.md +48 -0
  80. package/skills/graphify/skills/claw/references/update.md +179 -0
  81. package/skills/graphify/skills/codex/references/add-watch.md +56 -0
  82. package/skills/graphify/skills/codex/references/exports.md +71 -0
  83. package/skills/graphify/skills/codex/references/extraction-spec.md +29 -0
  84. package/skills/graphify/skills/codex/references/github-and-merge.md +46 -0
  85. package/skills/graphify/skills/codex/references/hooks.md +33 -0
  86. package/skills/graphify/skills/codex/references/query.md +249 -0
  87. package/skills/graphify/skills/codex/references/transcribe.md +48 -0
  88. package/skills/graphify/skills/codex/references/update.md +179 -0
  89. package/skills/graphify/skills/copilot/references/add-watch.md +56 -0
  90. package/skills/graphify/skills/copilot/references/exports.md +71 -0
  91. package/skills/graphify/skills/copilot/references/extraction-spec.md +68 -0
  92. package/skills/graphify/skills/copilot/references/github-and-merge.md +46 -0
  93. package/skills/graphify/skills/copilot/references/hooks.md +33 -0
  94. package/skills/graphify/skills/copilot/references/query.md +249 -0
  95. package/skills/graphify/skills/copilot/references/transcribe.md +48 -0
  96. package/skills/graphify/skills/copilot/references/update.md +179 -0
  97. package/skills/graphify/skills/droid/references/add-watch.md +56 -0
  98. package/skills/graphify/skills/droid/references/exports.md +71 -0
  99. package/skills/graphify/skills/droid/references/extraction-spec.md +68 -0
  100. package/skills/graphify/skills/droid/references/github-and-merge.md +46 -0
  101. package/skills/graphify/skills/droid/references/hooks.md +33 -0
  102. package/skills/graphify/skills/droid/references/query.md +249 -0
  103. package/skills/graphify/skills/droid/references/transcribe.md +48 -0
  104. package/skills/graphify/skills/droid/references/update.md +179 -0
  105. package/skills/graphify/skills/kilo/references/add-watch.md +56 -0
  106. package/skills/graphify/skills/kilo/references/exports.md +71 -0
  107. package/skills/graphify/skills/kilo/references/extraction-spec.md +68 -0
  108. package/skills/graphify/skills/kilo/references/github-and-merge.md +46 -0
  109. package/skills/graphify/skills/kilo/references/hooks.md +33 -0
  110. package/skills/graphify/skills/kilo/references/query.md +249 -0
  111. package/skills/graphify/skills/kilo/references/transcribe.md +48 -0
  112. package/skills/graphify/skills/kilo/references/update.md +179 -0
  113. package/skills/graphify/skills/kiro/references/add-watch.md +56 -0
  114. package/skills/graphify/skills/kiro/references/exports.md +71 -0
  115. package/skills/graphify/skills/kiro/references/extraction-spec.md +29 -0
  116. package/skills/graphify/skills/kiro/references/github-and-merge.md +46 -0
  117. package/skills/graphify/skills/kiro/references/hooks.md +33 -0
  118. package/skills/graphify/skills/kiro/references/query.md +249 -0
  119. package/skills/graphify/skills/kiro/references/transcribe.md +48 -0
  120. package/skills/graphify/skills/kiro/references/update.md +179 -0
  121. package/skills/graphify/skills/opencode/references/add-watch.md +56 -0
  122. package/skills/graphify/skills/opencode/references/exports.md +71 -0
  123. package/skills/graphify/skills/opencode/references/extraction-spec.md +68 -0
  124. package/skills/graphify/skills/opencode/references/github-and-merge.md +46 -0
  125. package/skills/graphify/skills/opencode/references/hooks.md +33 -0
  126. package/skills/graphify/skills/opencode/references/query.md +249 -0
  127. package/skills/graphify/skills/opencode/references/transcribe.md +48 -0
  128. package/skills/graphify/skills/opencode/references/update.md +179 -0
  129. package/skills/graphify/skills/pi/references/add-watch.md +56 -0
  130. package/skills/graphify/skills/pi/references/exports.md +71 -0
  131. package/skills/graphify/skills/pi/references/extraction-spec.md +29 -0
  132. package/skills/graphify/skills/pi/references/github-and-merge.md +46 -0
  133. package/skills/graphify/skills/pi/references/hooks.md +33 -0
  134. package/skills/graphify/skills/pi/references/query.md +249 -0
  135. package/skills/graphify/skills/pi/references/transcribe.md +48 -0
  136. package/skills/graphify/skills/pi/references/update.md +179 -0
  137. package/skills/graphify/skills/trae/references/add-watch.md +56 -0
  138. package/skills/graphify/skills/trae/references/exports.md +71 -0
  139. package/skills/graphify/skills/trae/references/extraction-spec.md +68 -0
  140. package/skills/graphify/skills/trae/references/github-and-merge.md +46 -0
  141. package/skills/graphify/skills/trae/references/hooks.md +35 -0
  142. package/skills/graphify/skills/trae/references/query.md +249 -0
  143. package/skills/graphify/skills/trae/references/transcribe.md +48 -0
  144. package/skills/graphify/skills/trae/references/update.md +179 -0
  145. package/skills/graphify/skills/vscode/references/add-watch.md +56 -0
  146. package/skills/graphify/skills/vscode/references/exports.md +71 -0
  147. package/skills/graphify/skills/vscode/references/extraction-spec.md +68 -0
  148. package/skills/graphify/skills/vscode/references/github-and-merge.md +46 -0
  149. package/skills/graphify/skills/vscode/references/hooks.md +33 -0
  150. package/skills/graphify/skills/vscode/references/query.md +249 -0
  151. package/skills/graphify/skills/vscode/references/transcribe.md +48 -0
  152. package/skills/graphify/skills/vscode/references/update.md +179 -0
  153. package/skills/graphify/skills/windows/references/add-watch.md +56 -0
  154. package/skills/graphify/skills/windows/references/exports.md +71 -0
  155. package/skills/graphify/skills/windows/references/extraction-spec.md +68 -0
  156. package/skills/graphify/skills/windows/references/github-and-merge.md +46 -0
  157. package/skills/graphify/skills/windows/references/hooks.md +33 -0
  158. package/skills/graphify/skills/windows/references/query.md +249 -0
  159. package/skills/graphify/skills/windows/references/transcribe.md +48 -0
  160. package/skills/graphify/skills/windows/references/update.md +179 -0
  161. package/skills/graphify/symbol_resolution.py +538 -0
  162. package/skills/graphify/transcribe.py +184 -0
  163. package/skills/graphify/tree_html.py +582 -0
  164. package/skills/graphify/validate.py +72 -0
  165. package/skills/graphify/watch.py +898 -0
  166. package/skills/graphify/wiki.py +282 -0
  167. package/skills/impeccable/SKILL.md +186 -0
  168. package/skills/impeccable/agents/impeccable_asset_producer.toml +92 -0
  169. package/skills/impeccable/agents/impeccable_manual_edit_applier.toml +95 -0
  170. package/skills/impeccable/agents/openai.yaml +4 -0
  171. package/skills/impeccable/reference/adapt.md +311 -0
  172. package/skills/impeccable/reference/animate.md +201 -0
  173. package/skills/impeccable/reference/audit.md +133 -0
  174. package/skills/impeccable/reference/bolder.md +113 -0
  175. package/skills/impeccable/reference/brand.md +108 -0
  176. package/skills/impeccable/reference/clarify.md +288 -0
  177. package/skills/impeccable/reference/codex.md +105 -0
  178. package/skills/impeccable/reference/colorize.md +257 -0
  179. package/skills/impeccable/reference/craft.md +123 -0
  180. package/skills/impeccable/reference/critique.md +790 -0
  181. package/skills/impeccable/reference/delight.md +302 -0
  182. package/skills/impeccable/reference/distill.md +111 -0
  183. package/skills/impeccable/reference/document.md +429 -0
  184. package/skills/impeccable/reference/extract.md +69 -0
  185. package/skills/impeccable/reference/harden.md +347 -0
  186. package/skills/impeccable/reference/init.md +172 -0
  187. package/skills/impeccable/reference/interaction-design.md +189 -0
  188. package/skills/impeccable/reference/layout.md +161 -0
  189. package/skills/impeccable/reference/live.md +720 -0
  190. package/skills/impeccable/reference/onboard.md +234 -0
  191. package/skills/impeccable/reference/optimize.md +258 -0
  192. package/skills/impeccable/reference/overdrive.md +130 -0
  193. package/skills/impeccable/reference/polish.md +241 -0
  194. package/skills/impeccable/reference/product.md +60 -0
  195. package/skills/impeccable/reference/quieter.md +99 -0
  196. package/skills/impeccable/reference/shape.md +165 -0
  197. package/skills/impeccable/reference/typeset.md +279 -0
  198. package/skills/impeccable/scripts/cleanup-deprecated.mjs +284 -0
  199. package/skills/impeccable/scripts/command-metadata.json +94 -0
  200. package/skills/impeccable/scripts/context-signals.mjs +225 -0
  201. package/skills/impeccable/scripts/context.mjs +266 -0
  202. package/skills/impeccable/scripts/critique-storage.mjs +242 -0
  203. package/skills/impeccable/scripts/design-parser.mjs +835 -0
  204. package/skills/impeccable/scripts/detect-csp.mjs +198 -0
  205. package/skills/impeccable/scripts/detect.mjs +21 -0
  206. package/skills/impeccable/scripts/detector/browser/injected/index.mjs +1733 -0
  207. package/skills/impeccable/scripts/detector/cli/main.mjs +244 -0
  208. package/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +4618 -0
  209. package/skills/impeccable/scripts/detector/detect-antipatterns.mjs +43 -0
  210. package/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +252 -0
  211. package/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +535 -0
  212. package/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +986 -0
  213. package/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +208 -0
  214. package/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +189 -0
  215. package/skills/impeccable/scripts/detector/findings.mjs +12 -0
  216. package/skills/impeccable/scripts/detector/node/file-system.mjs +198 -0
  217. package/skills/impeccable/scripts/detector/profile/profiler.mjs +166 -0
  218. package/skills/impeccable/scripts/detector/registry/antipatterns.mjs +419 -0
  219. package/skills/impeccable/scripts/detector/rules/checks.mjs +2384 -0
  220. package/skills/impeccable/scripts/detector/shared/color.mjs +124 -0
  221. package/skills/impeccable/scripts/detector/shared/constants.mjs +101 -0
  222. package/skills/impeccable/scripts/detector/shared/page.mjs +7 -0
  223. package/skills/impeccable/scripts/impeccable-paths.mjs +126 -0
  224. package/skills/impeccable/scripts/is-generated.mjs +69 -0
  225. package/skills/impeccable/scripts/live-accept.mjs +812 -0
  226. package/skills/impeccable/scripts/live-browser-session.js +123 -0
  227. package/skills/impeccable/scripts/live-browser.js +10295 -0
  228. package/skills/impeccable/scripts/live-commit-manual-edits.mjs +1241 -0
  229. package/skills/impeccable/scripts/live-complete.mjs +75 -0
  230. package/skills/impeccable/scripts/live-completion.mjs +19 -0
  231. package/skills/impeccable/scripts/live-copy-edit-agent.mjs +683 -0
  232. package/skills/impeccable/scripts/live-discard-manual-edits.mjs +51 -0
  233. package/skills/impeccable/scripts/live-event-validation.mjs +137 -0
  234. package/skills/impeccable/scripts/live-inject.mjs +557 -0
  235. package/skills/impeccable/scripts/live-insert-ui.mjs +458 -0
  236. package/skills/impeccable/scripts/live-insert.mjs +272 -0
  237. package/skills/impeccable/scripts/live-manual-edit-evidence.mjs +363 -0
  238. package/skills/impeccable/scripts/live-manual-edits-buffer.mjs +152 -0
  239. package/skills/impeccable/scripts/live-poll.mjs +379 -0
  240. package/skills/impeccable/scripts/live-resume.mjs +94 -0
  241. package/skills/impeccable/scripts/live-server.mjs +2326 -0
  242. package/skills/impeccable/scripts/live-session-store.mjs +289 -0
  243. package/skills/impeccable/scripts/live-status.mjs +61 -0
  244. package/skills/impeccable/scripts/live-svelte-component.mjs +826 -0
  245. package/skills/impeccable/scripts/live-sveltekit-adapter.mjs +274 -0
  246. package/skills/impeccable/scripts/live-ui-core.mjs +179 -0
  247. package/skills/impeccable/scripts/live-vocabulary.mjs +36 -0
  248. package/skills/impeccable/scripts/live-wrap.mjs +894 -0
  249. package/skills/impeccable/scripts/live.mjs +246 -0
  250. package/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
  251. package/skills/impeccable/scripts/palette.mjs +633 -0
  252. package/skills/impeccable/scripts/pin.mjs +214 -0
  253. package/skills/uipm-ui-styling/LICENSE.txt +202 -0
  254. package/skills/uipm-ui-styling/SKILL.md +328 -0
  255. package/skills/uipm-ui-styling/canvas-fonts/ArsenalSC-OFL.txt +93 -0
  256. package/skills/uipm-ui-styling/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  257. package/skills/uipm-ui-styling/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  258. package/skills/uipm-ui-styling/canvas-fonts/BigShoulders-OFL.txt +93 -0
  259. package/skills/uipm-ui-styling/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  260. package/skills/uipm-ui-styling/canvas-fonts/Boldonse-OFL.txt +93 -0
  261. package/skills/uipm-ui-styling/canvas-fonts/Boldonse-Regular.ttf +0 -0
  262. package/skills/uipm-ui-styling/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  263. package/skills/uipm-ui-styling/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  264. package/skills/uipm-ui-styling/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  265. package/skills/uipm-ui-styling/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  266. package/skills/uipm-ui-styling/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  267. package/skills/uipm-ui-styling/canvas-fonts/CrimsonPro-OFL.txt +93 -0
  268. package/skills/uipm-ui-styling/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  269. package/skills/uipm-ui-styling/canvas-fonts/DMMono-OFL.txt +93 -0
  270. package/skills/uipm-ui-styling/canvas-fonts/DMMono-Regular.ttf +0 -0
  271. package/skills/uipm-ui-styling/canvas-fonts/EricaOne-OFL.txt +94 -0
  272. package/skills/uipm-ui-styling/canvas-fonts/EricaOne-Regular.ttf +0 -0
  273. package/skills/uipm-ui-styling/canvas-fonts/GeistMono-Bold.ttf +0 -0
  274. package/skills/uipm-ui-styling/canvas-fonts/GeistMono-OFL.txt +93 -0
  275. package/skills/uipm-ui-styling/canvas-fonts/GeistMono-Regular.ttf +0 -0
  276. package/skills/uipm-ui-styling/canvas-fonts/Gloock-OFL.txt +93 -0
  277. package/skills/uipm-ui-styling/canvas-fonts/Gloock-Regular.ttf +0 -0
  278. package/skills/uipm-ui-styling/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  279. package/skills/uipm-ui-styling/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
  280. package/skills/uipm-ui-styling/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  281. package/skills/uipm-ui-styling/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  282. package/skills/uipm-ui-styling/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  283. package/skills/uipm-ui-styling/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  284. package/skills/uipm-ui-styling/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  285. package/skills/uipm-ui-styling/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  286. package/skills/uipm-ui-styling/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  287. package/skills/uipm-ui-styling/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  288. package/skills/uipm-ui-styling/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  289. package/skills/uipm-ui-styling/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  290. package/skills/uipm-ui-styling/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  291. package/skills/uipm-ui-styling/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  292. package/skills/uipm-ui-styling/canvas-fonts/Italiana-OFL.txt +93 -0
  293. package/skills/uipm-ui-styling/canvas-fonts/Italiana-Regular.ttf +0 -0
  294. package/skills/uipm-ui-styling/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  295. package/skills/uipm-ui-styling/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  296. package/skills/uipm-ui-styling/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  297. package/skills/uipm-ui-styling/canvas-fonts/Jura-Light.ttf +0 -0
  298. package/skills/uipm-ui-styling/canvas-fonts/Jura-Medium.ttf +0 -0
  299. package/skills/uipm-ui-styling/canvas-fonts/Jura-OFL.txt +93 -0
  300. package/skills/uipm-ui-styling/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
  301. package/skills/uipm-ui-styling/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  302. package/skills/uipm-ui-styling/canvas-fonts/Lora-Bold.ttf +0 -0
  303. package/skills/uipm-ui-styling/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  304. package/skills/uipm-ui-styling/canvas-fonts/Lora-Italic.ttf +0 -0
  305. package/skills/uipm-ui-styling/canvas-fonts/Lora-OFL.txt +93 -0
  306. package/skills/uipm-ui-styling/canvas-fonts/Lora-Regular.ttf +0 -0
  307. package/skills/uipm-ui-styling/canvas-fonts/NationalPark-Bold.ttf +0 -0
  308. package/skills/uipm-ui-styling/canvas-fonts/NationalPark-OFL.txt +93 -0
  309. package/skills/uipm-ui-styling/canvas-fonts/NationalPark-Regular.ttf +0 -0
  310. package/skills/uipm-ui-styling/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  311. package/skills/uipm-ui-styling/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  312. package/skills/uipm-ui-styling/canvas-fonts/Outfit-Bold.ttf +0 -0
  313. package/skills/uipm-ui-styling/canvas-fonts/Outfit-OFL.txt +93 -0
  314. package/skills/uipm-ui-styling/canvas-fonts/Outfit-Regular.ttf +0 -0
  315. package/skills/uipm-ui-styling/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  316. package/skills/uipm-ui-styling/canvas-fonts/PixelifySans-OFL.txt +93 -0
  317. package/skills/uipm-ui-styling/canvas-fonts/PoiretOne-OFL.txt +93 -0
  318. package/skills/uipm-ui-styling/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  319. package/skills/uipm-ui-styling/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  320. package/skills/uipm-ui-styling/canvas-fonts/RedHatMono-OFL.txt +93 -0
  321. package/skills/uipm-ui-styling/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  322. package/skills/uipm-ui-styling/canvas-fonts/Silkscreen-OFL.txt +93 -0
  323. package/skills/uipm-ui-styling/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  324. package/skills/uipm-ui-styling/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  325. package/skills/uipm-ui-styling/canvas-fonts/SmoochSans-OFL.txt +93 -0
  326. package/skills/uipm-ui-styling/canvas-fonts/Tektur-Medium.ttf +0 -0
  327. package/skills/uipm-ui-styling/canvas-fonts/Tektur-OFL.txt +93 -0
  328. package/skills/uipm-ui-styling/canvas-fonts/Tektur-Regular.ttf +0 -0
  329. package/skills/uipm-ui-styling/canvas-fonts/WorkSans-Bold.ttf +0 -0
  330. package/skills/uipm-ui-styling/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  331. package/skills/uipm-ui-styling/canvas-fonts/WorkSans-Italic.ttf +0 -0
  332. package/skills/uipm-ui-styling/canvas-fonts/WorkSans-OFL.txt +93 -0
  333. package/skills/uipm-ui-styling/canvas-fonts/WorkSans-Regular.ttf +0 -0
  334. package/skills/uipm-ui-styling/canvas-fonts/YoungSerif-OFL.txt +93 -0
  335. package/skills/uipm-ui-styling/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  336. package/skills/uipm-ui-styling/references/canvas-design-system.md +320 -0
  337. package/skills/uipm-ui-styling/references/shadcn-accessibility.md +471 -0
  338. package/skills/uipm-ui-styling/references/shadcn-components.md +424 -0
  339. package/skills/uipm-ui-styling/references/shadcn-theming.md +373 -0
  340. package/skills/uipm-ui-styling/references/tailwind-customization.md +483 -0
  341. package/skills/uipm-ui-styling/references/tailwind-responsive.md +382 -0
  342. package/skills/uipm-ui-styling/references/tailwind-utilities.md +455 -0
  343. package/skills/uipm-ui-styling/scripts/.coverage +0 -0
  344. package/skills/uipm-ui-styling/scripts/requirements.txt +17 -0
  345. package/skills/uipm-ui-styling/scripts/shadcn_add.py +292 -0
  346. package/skills/uipm-ui-styling/scripts/tailwind_config_gen.py +456 -0
  347. package/skills/uipm-ui-styling/scripts/tests/coverage-ui.json +1 -0
  348. package/skills/uipm-ui-styling/scripts/tests/requirements.txt +3 -0
  349. package/skills/uipm-ui-styling/scripts/tests/test_shadcn_add.py +266 -0
  350. package/skills/uipm-ui-styling/scripts/tests/test_tailwind_config_gen.py +336 -0
@@ -0,0 +1,582 @@
1
+ """tree_html — emit a D3 v7 collapsible-tree HTML view of a graph.
2
+
3
+ A self-contained printable / browseable tree-of-modules view
4
+ intended to complement the existing force-directed ``graph.html``.
5
+ Key visual elements:
6
+
7
+ * Expand-all / collapse-all / reset-view buttons.
8
+ * Multi-line label wrapping (``wrapText``) with separately-coloured
9
+ name and descendant-count.
10
+ * Depth-based colour palette (top-level directories get distinct
11
+ accent colours; deeper levels follow a level-specific palette).
12
+ * Click-to-toggle subtree.
13
+
14
+ Tree-data shape:
15
+
16
+ {
17
+ "name": "<root label>",
18
+ "total_count": <int>,
19
+ "children": [ { "name", "total_count", "children": [...] }, ... ]
20
+ }
21
+
22
+ CLI: ``graphify tree [--graph PATH] [--output HTML] [--root PATH]
23
+ [--max-children N] [--label NAME]``.
24
+
25
+ Implementation notes:
26
+ - ``total_count`` is the descendant leaf count, so collapsed nodes
27
+ can show ``(Total Count: 95)`` without needing the children loaded.
28
+ - ``--max-children`` (default 200) caps how many children render
29
+ under any one node; a synthetic ``(+N more)`` leaf appears when the
30
+ cap fires so very wide directories stay usable.
31
+ - The first-level palette is auto-populated from the live top-level
32
+ directories so each gets a stable accent colour.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import html as _html
38
+ import json
39
+ from collections import defaultdict
40
+ from pathlib import Path
41
+ from typing import Any, Dict, List, Optional
42
+
43
+ DEFAULT_MAX_CHILDREN = 200
44
+
45
+
46
+ # ── Tree builder (filesystem hierarchy → JSON) ──────────────────
47
+
48
+
49
+ def _common_root(paths: List[str]) -> str:
50
+ if not paths:
51
+ return ""
52
+ parts = [Path(p).parts for p in paths if p]
53
+ if not parts:
54
+ return ""
55
+ common = parts[0]
56
+ for p in parts[1:]:
57
+ i = 0
58
+ while i < len(common) and i < len(p) and common[i] == p[i]:
59
+ i += 1
60
+ common = common[:i]
61
+ return str(Path(*common)) if common else ""
62
+
63
+
64
+ def _make_truncation_leaf(extra: int) -> Dict[str, Any]:
65
+ return {"name": f"(+{extra} more)", "total_count": extra, "children": []}
66
+
67
+
68
+ def build_tree(
69
+ graph: Dict[str, Any],
70
+ *,
71
+ root: Optional[str] = None,
72
+ max_children: int = DEFAULT_MAX_CHILDREN,
73
+ project_label: Optional[str] = None,
74
+ ) -> Dict[str, Any]:
75
+ """Build a ``{name, total_count, children}`` hierarchy.
76
+
77
+ Each leaf is either a code symbol (class / top-level function) or
78
+ a synthetic "(+N more)" placeholder for truncated wide directories.
79
+ Each interior node carries ``total_count = sum of leaf counts``.
80
+ """
81
+ nodes: List[Dict[str, Any]] = list(graph.get("nodes", []))
82
+ file_nodes = [n for n in nodes if n.get("source_file")]
83
+ if not file_nodes:
84
+ return {"name": "(empty graph)", "total_count": 0, "children": []}
85
+
86
+ if root is None:
87
+ root = _common_root([n["source_file"] for n in file_nodes])
88
+ root_path = Path(root)
89
+
90
+ by_file: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
91
+ for n in file_nodes:
92
+ by_file[n["source_file"]].append(n)
93
+
94
+ # Build dir tree.
95
+ dir_index: Dict[str, Dict[str, Any]] = {}
96
+ label_root = project_label or root_path.name or root or "/"
97
+ root_node: Dict[str, Any] = {
98
+ "name": label_root, "total_count": 0, "children": [],
99
+ }
100
+ dir_index[str(root_path)] = root_node
101
+
102
+ def _ensure_dir(abs_path: Path) -> Dict[str, Any]:
103
+ key = str(abs_path)
104
+ if key in dir_index:
105
+ return dir_index[key]
106
+ if abs_path == abs_path.parent:
107
+ return root_node
108
+ parent = (_ensure_dir(abs_path.parent)
109
+ if abs_path.parent != abs_path else root_node)
110
+ node = {"name": abs_path.name, "total_count": 0, "children": []}
111
+ dir_index[key] = node
112
+ parent["children"].append(node)
113
+ return node
114
+
115
+ for src_file, syms in sorted(by_file.items()):
116
+ src_path = Path(src_file)
117
+ try:
118
+ rel = src_path.relative_to(root_path)
119
+ parent_path = (root_path / rel).parent
120
+ except ValueError:
121
+ parent_path = root_path
122
+ parent_dir = _ensure_dir(parent_path)
123
+
124
+ # File node — children are the symbols.
125
+ sym_children: List[Dict[str, Any]] = []
126
+ for n in syms:
127
+ label = n.get("label", n.get("id", "?"))
128
+ # Skip the redundant file-name node graphify emits.
129
+ if label == src_path.name and n.get("file_type") == "code":
130
+ continue
131
+ sym_children.append({
132
+ "name": label,
133
+ "total_count": 1,
134
+ "children": [],
135
+ })
136
+ # Sort: code symbols first by name, then anything else.
137
+ sym_children.sort(key=lambda c: (
138
+ c["name"].startswith("_"),
139
+ c["name"].lower(),
140
+ ))
141
+ if len(sym_children) > max_children:
142
+ extra = len(sym_children) - max_children
143
+ sym_children = sym_children[:max_children] + [
144
+ _make_truncation_leaf(extra),
145
+ ]
146
+ file_node = {
147
+ "name": src_path.name,
148
+ "total_count": len(sym_children) or 1,
149
+ "children": sym_children,
150
+ }
151
+ parent_dir["children"].append(file_node)
152
+
153
+ # Sort each dir's children + propagate total_count up.
154
+ def _finalise(d: Dict[str, Any]) -> int:
155
+ kids = d.get("children") or []
156
+ kids.sort(key=lambda c: (
157
+ 0 if (c.get("children") and len(c["children"]) > 0) else 1,
158
+ c["name"].lower(),
159
+ ))
160
+ if not kids:
161
+ return d.get("total_count") or 1
162
+ n = 0
163
+ for c in kids:
164
+ n += _finalise(c)
165
+ d["total_count"] = n or 1
166
+ return d["total_count"]
167
+
168
+ _finalise(root_node)
169
+ return root_node
170
+
171
+
172
+ # ── HTML emitter (single-data-blob substitution) ──────────────────
173
+
174
+
175
+ # We emit a Python f-string with literal CSS/JS braces escaped as {{ }}.
176
+ _HTML_TEMPLATE = r"""<!DOCTYPE html>
177
+ <html lang="en">
178
+ <head>
179
+ <meta charset="UTF-8">
180
+ <title>{title}</title>
181
+ <style>
182
+ body {{
183
+ font-family: 'Segoe UI', sans-serif;
184
+ margin: 0;
185
+ padding: 0;
186
+ background: #f9f9f9;
187
+ color: #333;
188
+ }}
189
+ h1 {{
190
+ margin: 20px 0 0 24px;
191
+ font-size: 2.2rem;
192
+ font-weight: bold;
193
+ color: #1e3a56;
194
+ }}
195
+ .controls {{
196
+ margin: 20px 0 15px 24px;
197
+ }}
198
+ button {{
199
+ margin-right: 10px;
200
+ padding: 8px 18px;
201
+ background: #007bff;
202
+ color: #fff;
203
+ border: none;
204
+ border-radius: 5px;
205
+ font-size: 0.95rem;
206
+ cursor: pointer;
207
+ transition: background 0.2s ease-in-out;
208
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
209
+ }}
210
+ button:hover {{
211
+ background: #0056b3;
212
+ }}
213
+ button:active {{
214
+ background: #004085;
215
+ }}
216
+ #tree-container {{
217
+ width: calc(100vw - 48px); /* Adjust for body margin/padding */
218
+ height: 85vh;
219
+ overflow: auto;
220
+ border-radius: 8px;
221
+ background: #fff;
222
+ margin-left: 24px;
223
+ margin-right: 24px;
224
+ box-shadow: 0 4px 12px rgba(0,0,0,0.08);
225
+ border: 1px solid #ddd;
226
+ }}
227
+ svg {{
228
+ background: #fff;
229
+ border-radius: 8px;
230
+ display: block; /* Important for D3 */
231
+ }}
232
+ .node circle {{
233
+ stroke-width: 2.5px;
234
+ }}
235
+ .node text {{ /* Base style for the <text> container */
236
+ font: 13px 'Segoe UI', sans-serif;
237
+ paint-order: stroke fill; /* Ensures text is readable over lines */
238
+ stroke: #fff; /* White halo */
239
+ stroke-width: 3px; /* Halo thickness */
240
+ stroke-linejoin: round;
241
+ stroke-opacity: 0.85; /* Halo opacity */
242
+ }}
243
+ .link {{
244
+ fill: none;
245
+ stroke-opacity: 0.7;
246
+ stroke-width: 2px;
247
+ }}
248
+ </style>
249
+ </head>
250
+ <body>
251
+ <h1>{header}</h1>
252
+ <div class="controls">
253
+ <button onclick="expandAll()">Expand All</button>
254
+ <button onclick="collapseAll()">Collapse All</button>
255
+ <button onclick="resetView()">Reset View</button>
256
+ </div>
257
+ <div id="tree-container">
258
+ <svg id="tree-svg" width="{svg_width}" height="{svg_height}"></svg>
259
+ </div>
260
+
261
+ <script src="https://d3js.org/d3.v7.min.js"></script>
262
+ <script>
263
+ const initialJsonData = {data_json};
264
+
265
+ function transformData(jsonData) {{
266
+ // Helper function to recursively build the children structure
267
+ function processNode(node, parentL1StageName) {{
268
+ let displayName = node.name;
269
+ // Append total_count if it exists and is not already in the name
270
+ if (node.total_count !== undefined) {{
271
+ if (!/\(Total Count: \d+\)$/.test(displayName)) {{
272
+ displayName += ` (Total Count: ${{node.total_count}})`;
273
+ }}
274
+ }}
275
+
276
+ const newNode = {{ name: displayName }};
277
+
278
+ if (parentL1StageName === "Root") {{
279
+ newNode.originalStageName = node.name;
280
+ }} else {{
281
+ newNode.originalStageName = parentL1StageName;
282
+ }}
283
+
284
+ if (node.children && node.children.length > 0) {{
285
+ const stageNameToPass = (parentL1StageName === "Root") ? node.name : parentL1StageName;
286
+ newNode.children = node.children.map(child => processNode(child, stageNameToPass));
287
+ }}
288
+
289
+ return newNode;
290
+ }}
291
+
292
+ let rootDisplayName = jsonData.name;
293
+ if (jsonData.total_count !== undefined && !/\(Total Count: \d+\)$/.test(rootDisplayName)) {{
294
+ rootDisplayName += ` (Total Count: ${{jsonData.total_count}})`;
295
+ }}
296
+
297
+ return {{
298
+ name: rootDisplayName,
299
+ originalStageName: "Root",
300
+ children: (jsonData.children || []).map(child => processNode(child, "Root"))
301
+ }};
302
+ }}
303
+
304
+ const treeData = transformData(initialJsonData);
305
+
306
+ // Auto-populated phaseColors: every depth-1 child of the root gets
307
+ // a stable colour from a bigger palette so all top-level dirs are
308
+ // distinguishable.
309
+ const PALETTE = [
310
+ ["#3498DB","#2980B9","#AED6F1"], ["#2ECC71","#27AE60","#A9DFBF"],
311
+ ["#E74C3C","#C0392B","#F5B7B1"], ["#9B59B6","#8E44AD","#D7BDE2"],
312
+ ["#F39C12","#D68910","#FAD7A0"], ["#1ABC9C","#117864","#A2D9CE"],
313
+ ["#34495E","#1B2631","#ABB2B9"], ["#E67E22","#BA4A00","#F5CBA7"],
314
+ ["#16A085","#0E6655","#A2D9CE"], ["#D35400","#A04000","#EDBB99"],
315
+ ["#7F8C8D","#566573","#D5DBDB"], ["#C0392B","#7B241C","#F5B7B1"],
316
+ ["#2E86C1","#1B4F72","#A9CCE3"], ["#28B463","#196F3D","#A9DFBF"],
317
+ ["#AF7AC5","#6C3483","#D2B4DE"],
318
+ ];
319
+ const phaseColors = {{ "Root": {{ fill: "#4A4A4A", stroke: "#333333", collapsedFill: "#6C757D" }},
320
+ "Default": {{ fill: "#BDC3C7", stroke: "#95A5A6", collapsedFill: "#ECF0F1" }} }};
321
+ (initialJsonData.children || []).forEach((c, i) => {{
322
+ const pal = PALETTE[i % PALETTE.length];
323
+ phaseColors[c.name] = {{ fill: pal[0], stroke: pal[1], collapsedFill: pal[2] }};
324
+ }});
325
+
326
+ const levelSpecificPalettes = {{
327
+ 0: {{ fill: "#4A4A4A", stroke: "#333333", collapsedFill: "#6C757D" }},
328
+ 2: {{ fill: "#6ab04c", stroke: "#508a38", collapsedFill: "#a3d391" }},
329
+ 3: {{ fill: "#f0932b", stroke: "#d0730f", collapsedFill: "#f6c07e" }},
330
+ 4: {{ fill: "#be2edd", stroke: "#a01cb3", collapsedFill: "#e08bf2" }},
331
+ 5: {{ fill: "#00a8ff", stroke: "#007ac1", collapsedFill: "#74d2ff" }},
332
+ 6: {{ fill: "#e55039", stroke: "#c23620", collapsedFill: "#f09a8d" }},
333
+ default: {{ fill: "#747d8c", stroke: "#57606f", collapsedFill: "#a4b0be" }}
334
+ }};
335
+
336
+ const svgElement = d3.select("#tree-svg");
337
+ const initialSvgWidth = +svgElement.attr("width");
338
+ const initialSvgHeight = +svgElement.attr("height");
339
+ const margin = {{ top: 40, right: 120, bottom: 80, left: 450 }};
340
+ let width = initialSvgWidth - margin.left - margin.right;
341
+ let height = initialSvgHeight - margin.top - margin.bottom;
342
+ const duration = 500;
343
+ let nodeCounter = 0;
344
+ const g = svgElement.append("g").attr("transform", `translate(${{margin.left}},${{margin.top}})`);
345
+ const treemap = d3.tree().nodeSize([40, 0]);
346
+ let rootNode = d3.hierarchy(treeData, d => d.children);
347
+ rootNode.x0 = 0;
348
+ rootNode.y0 = 0;
349
+
350
+ if (rootNode.children) {{
351
+ rootNode.children.forEach(d_child => {{
352
+ if (d_child.children) {{ collapseBranch(d_child); }}
353
+ }});
354
+ }}
355
+ updateTree(rootNode);
356
+
357
+ function collapseBranch(d) {{ if (d.children) {{ d._children = d.children; d._children.forEach(collapseBranch); d.children = null; }} }}
358
+ function expandBranch(d) {{ if (d._children) {{ d.children = d._children; d._children = null; }} if (d.children) {{ d.children.forEach(expandBranch); }} }}
359
+ window.expandAll = () => {{ expandBranch(rootNode); updateTree(rootNode); }};
360
+ window.collapseAll = () => {{ if (rootNode.children) {{ rootNode.children.forEach(collapseBranch); }} updateTree(rootNode); }};
361
+ window.resetView = () => {{ if (rootNode.children) {{ rootNode.children.forEach(d_child => {{ if (d_child.children || d_child._children) {{ collapseBranch(d_child); }} }}); }} if (rootNode._children && !rootNode.children) {{ rootNode.children = rootNode._children; rootNode._children = null; }} updateTree(rootNode); }};
362
+
363
+ function updateTree(source) {{
364
+ const treeLayoutData = treemap(rootNode);
365
+ let nodes = treeLayoutData.descendants();
366
+ let links = treeLayoutData.descendants().slice(1);
367
+
368
+ let minX = 0;
369
+ let maxX = 0;
370
+ if (nodes.length > 0) {{
371
+ minX = d3.min(nodes, d => d.x);
372
+ maxX = d3.max(nodes, d => d.x);
373
+ }}
374
+
375
+ let neededHeight = Math.max(initialSvgHeight, maxX - minX + margin.top + margin.bottom + 100);
376
+ svgElement.transition().duration(duration / 2).attr("height", neededHeight);
377
+ g.transition().duration(duration / 2).attr("transform", `translate(${{margin.left}},${{margin.top - minX + 40}})`);
378
+
379
+ nodes.forEach(d => {{ d.y = d.depth * 400; }}); // Adjust horizontal separation if needed
380
+
381
+ const node = g.selectAll('g.node').data(nodes, d => d.id || (d.id = ++nodeCounter));
382
+ const nodeEnter = node.enter().append('g')
383
+ .attr('class', d => "node" + (d.children || d._children ? " node--internal" : " node--leaf") + (d._children ? " _children" : ""))
384
+ .attr('transform', d => `translate(${{source.y0}},${{source.x0}})`)
385
+ .on('click', (event, d) => {{ if (d.children) {{ d._children = d.children; d.children = null; }} else if (d._children) {{ d.children = d._children; d._children = null; }} updateTree(d); }})
386
+ .style('cursor', d => (d.children || d._children) ? 'pointer' : 'default');
387
+
388
+ nodeEnter.append('circle').attr('r', 1e-6);
389
+
390
+ nodeEnter.append('text')
391
+ .attr('dy', '.35em')
392
+ .attr('x', d => d.children || d._children ? -14 : 14)
393
+ .attr('text-anchor', d => d.children || d._children ? 'end' : 'start')
394
+ .style("fill-opacity", 1e-6)
395
+ .call(wrapText, 380);
396
+
397
+ const nodeUpdate = nodeEnter.merge(node);
398
+ nodeUpdate.transition().duration(duration)
399
+ .attr('transform', d => `translate(${{d.y}},${{d.x}})`)
400
+ .attr('class', d => "node" + (d.children ? " node--internal" : " node--leaf") + (d._children ? " node--internal _children" : ""));
401
+
402
+ nodeUpdate.select('circle').attr('r', 8.5)
403
+ .style('fill', d => {{
404
+ let palette;
405
+ if (d.depth === 0) {{
406
+ palette = levelSpecificPalettes[0];
407
+ }} else if (d.depth === 1) {{
408
+ palette = phaseColors[d.data.originalStageName] || phaseColors.Default;
409
+ }} else {{
410
+ palette = levelSpecificPalettes[d.depth] || levelSpecificPalettes.default;
411
+ }}
412
+ if (d._children) return palette.collapsedFill;
413
+ if (d.children) return palette.fill;
414
+ return "#fff";
415
+ }})
416
+ .style('stroke', d => {{
417
+ let palette;
418
+ if (d.depth === 0) {{
419
+ palette = levelSpecificPalettes[0];
420
+ }} else if (d.depth === 1) {{
421
+ palette = phaseColors[d.data.originalStageName] || phaseColors.Default;
422
+ }} else {{
423
+ palette = levelSpecificPalettes[d.depth] || levelSpecificPalettes.default;
424
+ }}
425
+ return palette.stroke;
426
+ }});
427
+ nodeUpdate.select('text').style("fill-opacity", 1).call(wrapText, 380);
428
+
429
+ const nodeExit = node.exit().transition().duration(duration).attr('transform', d => `translate(${{source.y}},${{source.x}})`).remove();
430
+ nodeExit.select('circle').attr('r', 1e-6);
431
+ nodeExit.select('text').style('fill-opacity', 1e-6);
432
+
433
+ const link = g.selectAll('path.link').data(links, d => d.id);
434
+ const linkEnter = link.enter().insert('path', "g").attr('class', 'link').attr('d', d => {{ const o = {{ x: source.x0, y: source.y0 }}; return diagonal(o, o); }});
435
+
436
+ linkEnter.merge(link).transition().duration(duration).attr('d', d => diagonal(d, d.parent))
437
+ .style('stroke', d => {{
438
+ const sourceNode = d.parent;
439
+ if (!sourceNode) return phaseColors.Default.stroke;
440
+ const l1AncestorName = sourceNode.data.originalStageName;
441
+ const colorPalette = phaseColors[l1AncestorName] || phaseColors.Default;
442
+ return colorPalette.stroke;
443
+ }});
444
+ link.exit().transition().duration(duration).attr('d', d => {{ const o = {{ x: source.x, y: source.y }}; return diagonal(o, o); }}).remove();
445
+ nodes.forEach(d => {{ d.x0 = d.x; d.y0 = d.y; }});
446
+ }}
447
+
448
+ function diagonal(s, d) {{ return `M ${{s.y}} ${{s.x}} C ${{(s.y + d.y) / 2}} ${{s.x}}, ${{(s.y + d.y) / 2}} ${{d.x}}, ${{d.y}} ${{d.x}}`; }}
449
+
450
+ function wrapText(textElements, maxWidth) {{
451
+ const textPartColors = {{
452
+ name: '#343a40',
453
+ count: '#0056b3'
454
+ }};
455
+ const countRegex = /(\s\(Total Count: \d+\))$/;
456
+
457
+ textElements.each(function () {{
458
+ const textD3 = d3.select(this);
459
+ const originalNodeText = textD3.datum().data.name;
460
+ const x = parseFloat(textD3.attr("x") || 0);
461
+ const initialDy = textD3.attr("dy");
462
+ const textAnchor = textD3.attr("text-anchor");
463
+ const lineHeight = 1.1;
464
+
465
+ textD3.text(null);
466
+
467
+ let namePart = originalNodeText;
468
+ let countPartText = "";
469
+
470
+ const countMatch = originalNodeText.match(countRegex);
471
+ if (countMatch && originalNodeText.endsWith(countMatch[0])) {{
472
+ namePart = originalNodeText.substring(0, originalNodeText.length - countMatch[0].length).trim();
473
+ countPartText = countMatch[0].trim();
474
+ }}
475
+
476
+ const tokens = [];
477
+ namePart.split(/\s+/).filter(Boolean).forEach(word => {{
478
+ tokens.push({{ text: word, type: 'name' }});
479
+ }});
480
+ if (countPartText) {{
481
+ tokens.push({{ text: countPartText, type: 'count' }});
482
+ }}
483
+
484
+ if (tokens.length === 0 && originalNodeText) {{
485
+ tokens.push({{ text: originalNodeText, type: 'name' }});
486
+ }}
487
+
488
+ let currentTspan = textD3.append("tspan").attr("x", x).attr("dy", initialDy);
489
+ if (textAnchor === "end") currentTspan.attr("text-anchor", "end");
490
+
491
+ let lineTokens = [];
492
+
493
+ for (let i = 0; i < tokens.length; i++) {{
494
+ const tokenObj = tokens[i];
495
+
496
+ lineTokens.push(tokenObj);
497
+ currentTspan.text(lineTokens.map(t => t.text).join(" "));
498
+
499
+ if (currentTspan.node().getComputedTextLength() > maxWidth && lineTokens.length > 1) {{
500
+ lineTokens.pop();
501
+
502
+ currentTspan.text(null);
503
+ lineTokens.forEach((prevToken, idx) => {{
504
+ currentTspan.append("tspan")
505
+ .text((idx > 0 ? " " : "") + prevToken.text)
506
+ .style("fill", textPartColors[prevToken.type] || textPartColors.name)
507
+ .style("font-weight", prevToken.type === 'count' ? "bold" : "normal");
508
+ }});
509
+
510
+ lineTokens = [tokenObj];
511
+ currentTspan = textD3.append("tspan").attr("x", x).attr("dy", lineHeight + "em");
512
+ if (textAnchor === "end") currentTspan.attr("text-anchor", "end");
513
+ }}
514
+ }}
515
+
516
+ currentTspan.text(null);
517
+ lineTokens.forEach((token, idx) => {{
518
+ currentTspan.append("tspan")
519
+ .text((idx > 0 ? " " : "") + token.text)
520
+ .style("fill", textPartColors[token.type] || textPartColors.name)
521
+ .style("font-weight", token.type === 'count' ? "bold" : "normal");
522
+ }});
523
+
524
+ if (textD3.selectAll("tspan > tspan").empty() && textD3.select("tspan").text().length === 0 && originalNodeText) {{
525
+ let t = textD3.select("tspan");
526
+ let displayText = originalNodeText;
527
+ t.text(displayText).style("fill", textPartColors.name);
528
+ if (t.node() && t.node().getComputedTextLength() > maxWidth && displayText.length > 20) {{
529
+ let estimatedChars = Math.floor(maxWidth / (t.node().getComputedTextLength()/displayText.length) );
530
+ displayText = displayText.substring(0, Math.max(0, estimatedChars - 3)) + "...";
531
+ t.text(displayText);
532
+ }}
533
+ }}
534
+ }});
535
+ }}
536
+ </script>
537
+ </body>
538
+ </html>
539
+ """
540
+
541
+
542
+ def emit_html(
543
+ tree: Dict[str, Any],
544
+ *,
545
+ title: str,
546
+ header: str,
547
+ svg_width: int = 6000,
548
+ svg_height: int = 8000,
549
+ ) -> str:
550
+ # Escape </script> sequences so embedded JSON cannot break out of the
551
+ # <script> tag, and HTML-escape values that land in <title>/<h1>.
552
+ data_json = json.dumps(tree, ensure_ascii=False, separators=(",", ":")).replace("</", "<\\/")
553
+ return _HTML_TEMPLATE.format(
554
+ title=_html.escape(title),
555
+ header=_html.escape(header),
556
+ svg_width=svg_width,
557
+ svg_height=svg_height,
558
+ data_json=data_json,
559
+ )
560
+
561
+
562
+ def write_tree_html(
563
+ graph_path: Path,
564
+ output_path: Path,
565
+ *,
566
+ root: Optional[str] = None,
567
+ max_children: int = DEFAULT_MAX_CHILDREN,
568
+ project_label: Optional[str] = None,
569
+ # kept for CLI compatibility with the older signature; ignored now
570
+ top_k_edges: int = 0,
571
+ ) -> Path:
572
+ from graphify.security import check_graph_file_size_cap
573
+ check_graph_file_size_cap(graph_path)
574
+ graph = json.loads(graph_path.read_text(encoding="utf-8"))
575
+ tree = build_tree(graph, root=root, max_children=max_children,
576
+ project_label=project_label)
577
+ title = f"{tree['name']} — graphify tree viewer"
578
+ header = f"{tree['name']} — Knowledge Graph"
579
+ html = emit_html(tree, title=title, header=header)
580
+ output_path.parent.mkdir(parents=True, exist_ok=True)
581
+ output_path.write_text(html, encoding="utf-8")
582
+ return output_path
@@ -0,0 +1,72 @@
1
+ # validate extraction JSON against the graphify schema before graph assembly
2
+ from __future__ import annotations
3
+
4
+ VALID_FILE_TYPES = {"code", "document", "paper", "image", "rationale", "concept"}
5
+ VALID_CONFIDENCES = {"EXTRACTED", "INFERRED", "AMBIGUOUS"}
6
+ REQUIRED_NODE_FIELDS = {"id", "label", "file_type", "source_file"}
7
+ REQUIRED_EDGE_FIELDS = {"source", "target", "relation", "confidence", "source_file"}
8
+
9
+
10
+ def validate_extraction(data: dict) -> list[str]:
11
+ """
12
+ Validate an extraction JSON dict against the graphify schema.
13
+ Returns a list of error strings - empty list means valid.
14
+ """
15
+ if not isinstance(data, dict):
16
+ return ["Extraction must be a JSON object"]
17
+
18
+ errors: list[str] = []
19
+
20
+ # Nodes
21
+ if "nodes" not in data:
22
+ errors.append("Missing required key 'nodes'")
23
+ elif not isinstance(data["nodes"], list):
24
+ errors.append("'nodes' must be a list")
25
+ else:
26
+ for i, node in enumerate(data["nodes"]):
27
+ if not isinstance(node, dict):
28
+ errors.append(f"Node {i} must be an object")
29
+ continue
30
+ for field in REQUIRED_NODE_FIELDS:
31
+ if field not in node:
32
+ errors.append(f"Node {i} (id={node.get('id', '?')!r}) missing required field '{field}'")
33
+ if "file_type" in node and node["file_type"] not in VALID_FILE_TYPES:
34
+ errors.append(
35
+ f"Node {i} (id={node.get('id', '?')!r}) has invalid file_type "
36
+ f"'{node['file_type']}' - must be one of {sorted(VALID_FILE_TYPES)}"
37
+ )
38
+
39
+ # Edges - accept "links" (NetworkX <= 3.1) as fallback for "edges"
40
+ edge_list = data.get("edges") if "edges" in data else data.get("links")
41
+ if edge_list is None:
42
+ errors.append("Missing required key 'edges'")
43
+ elif not isinstance(edge_list, list):
44
+ errors.append("'edges' must be a list")
45
+ else:
46
+ node_ids = {n["id"] for n in data.get("nodes", []) if isinstance(n, dict) and "id" in n}
47
+ for i, edge in enumerate(edge_list):
48
+ if not isinstance(edge, dict):
49
+ errors.append(f"Edge {i} must be an object")
50
+ continue
51
+ for field in REQUIRED_EDGE_FIELDS:
52
+ if field not in edge:
53
+ errors.append(f"Edge {i} missing required field '{field}'")
54
+ if "confidence" in edge and edge["confidence"] not in VALID_CONFIDENCES:
55
+ errors.append(
56
+ f"Edge {i} has invalid confidence '{edge['confidence']}' "
57
+ f"- must be one of {sorted(VALID_CONFIDENCES)}"
58
+ )
59
+ if "source" in edge and node_ids and edge["source"] not in node_ids:
60
+ errors.append(f"Edge {i} source '{edge['source']}' does not match any node id")
61
+ if "target" in edge and node_ids and edge["target"] not in node_ids:
62
+ errors.append(f"Edge {i} target '{edge['target']}' does not match any node id")
63
+
64
+ return errors
65
+
66
+
67
+ def assert_valid(data: dict) -> None:
68
+ """Raise ValueError with all errors if extraction is invalid."""
69
+ errors = validate_extraction(data)
70
+ if errors:
71
+ msg = f"Extraction JSON has {len(errors)} error(s):\n" + "\n".join(f" • {e}" for e in errors)
72
+ raise ValueError(msg)