@oriro/orirocli 0.1.7 → 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.
- package/ATTRIBUTION.md +8 -0
- package/LICENSE +21 -0
- package/dist/cli.js +35 -5
- package/package.json +1 -1
- package/skills/21stdev/SKILL.md +64 -0
- package/skills/graphify/SKILL.md +619 -0
- package/skills/graphify/__init__.py +28 -0
- package/skills/graphify/__main__.py +4582 -0
- package/skills/graphify/affected.py +154 -0
- package/skills/graphify/always_on/agents-md.md +12 -0
- package/skills/graphify/always_on/antigravity-rules.md +14 -0
- package/skills/graphify/always_on/claude-md.md +9 -0
- package/skills/graphify/always_on/gemini-md.md +9 -0
- package/skills/graphify/always_on/kiro-steering.md +5 -0
- package/skills/graphify/always_on/vscode-instructions.md +17 -0
- package/skills/graphify/analyze.py +724 -0
- package/skills/graphify/benchmark.py +155 -0
- package/skills/graphify/build.py +487 -0
- package/skills/graphify/cache.py +417 -0
- package/skills/graphify/callflow_html.py +2020 -0
- package/skills/graphify/cluster.py +272 -0
- package/skills/graphify/command-kilo.md +15 -0
- package/skills/graphify/dedup.py +429 -0
- package/skills/graphify/detect.py +1379 -0
- package/skills/graphify/diagnostics.py +390 -0
- package/skills/graphify/export.py +1408 -0
- package/skills/graphify/extract.py +11570 -0
- package/skills/graphify/global_graph.py +159 -0
- package/skills/graphify/google_workspace.py +223 -0
- package/skills/graphify/hooks.py +457 -0
- package/skills/graphify/ingest.py +331 -0
- package/skills/graphify/llm.py +1896 -0
- package/skills/graphify/manifest.py +4 -0
- package/skills/graphify/mcp_ingest.py +392 -0
- package/skills/graphify/multigraph_compat.py +212 -0
- package/skills/graphify/pg_introspect.py +142 -0
- package/skills/graphify/prs.py +748 -0
- package/skills/graphify/querylog.py +70 -0
- package/skills/graphify/report.py +218 -0
- package/skills/graphify/scip_ingest.py +363 -0
- package/skills/graphify/security.py +336 -0
- package/skills/graphify/semantic_cleanup.py +319 -0
- package/skills/graphify/serve.py +1309 -0
- package/skills/graphify/skill-aider.md +1246 -0
- package/skills/graphify/skill-amp.md +613 -0
- package/skills/graphify/skill-claw.md +616 -0
- package/skills/graphify/skill-codex.md +613 -0
- package/skills/graphify/skill-copilot.md +616 -0
- package/skills/graphify/skill-devin.md +1372 -0
- package/skills/graphify/skill-droid.md +613 -0
- package/skills/graphify/skill-kilo.md +625 -0
- package/skills/graphify/skill-kiro.md +615 -0
- package/skills/graphify/skill-opencode.md +608 -0
- package/skills/graphify/skill-pi.md +615 -0
- package/skills/graphify/skill-trae.md +614 -0
- package/skills/graphify/skill-vscode.md +612 -0
- package/skills/graphify/skill-windows.md +651 -0
- package/skills/graphify/skills/amp/references/add-watch.md +56 -0
- package/skills/graphify/skills/amp/references/exports.md +71 -0
- package/skills/graphify/skills/amp/references/extraction-spec.md +68 -0
- package/skills/graphify/skills/amp/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/amp/references/hooks.md +33 -0
- package/skills/graphify/skills/amp/references/query.md +249 -0
- package/skills/graphify/skills/amp/references/transcribe.md +48 -0
- package/skills/graphify/skills/amp/references/update.md +179 -0
- package/skills/graphify/skills/claude/references/add-watch.md +56 -0
- package/skills/graphify/skills/claude/references/exports.md +71 -0
- package/skills/graphify/skills/claude/references/extraction-spec.md +68 -0
- package/skills/graphify/skills/claude/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/claude/references/hooks.md +33 -0
- package/skills/graphify/skills/claude/references/query.md +103 -0
- package/skills/graphify/skills/claude/references/transcribe.md +48 -0
- package/skills/graphify/skills/claude/references/update.md +179 -0
- package/skills/graphify/skills/claw/references/add-watch.md +56 -0
- package/skills/graphify/skills/claw/references/exports.md +71 -0
- package/skills/graphify/skills/claw/references/extraction-spec.md +29 -0
- package/skills/graphify/skills/claw/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/claw/references/hooks.md +33 -0
- package/skills/graphify/skills/claw/references/query.md +249 -0
- package/skills/graphify/skills/claw/references/transcribe.md +48 -0
- package/skills/graphify/skills/claw/references/update.md +179 -0
- package/skills/graphify/skills/codex/references/add-watch.md +56 -0
- package/skills/graphify/skills/codex/references/exports.md +71 -0
- package/skills/graphify/skills/codex/references/extraction-spec.md +29 -0
- package/skills/graphify/skills/codex/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/codex/references/hooks.md +33 -0
- package/skills/graphify/skills/codex/references/query.md +249 -0
- package/skills/graphify/skills/codex/references/transcribe.md +48 -0
- package/skills/graphify/skills/codex/references/update.md +179 -0
- package/skills/graphify/skills/copilot/references/add-watch.md +56 -0
- package/skills/graphify/skills/copilot/references/exports.md +71 -0
- package/skills/graphify/skills/copilot/references/extraction-spec.md +68 -0
- package/skills/graphify/skills/copilot/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/copilot/references/hooks.md +33 -0
- package/skills/graphify/skills/copilot/references/query.md +249 -0
- package/skills/graphify/skills/copilot/references/transcribe.md +48 -0
- package/skills/graphify/skills/copilot/references/update.md +179 -0
- package/skills/graphify/skills/droid/references/add-watch.md +56 -0
- package/skills/graphify/skills/droid/references/exports.md +71 -0
- package/skills/graphify/skills/droid/references/extraction-spec.md +68 -0
- package/skills/graphify/skills/droid/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/droid/references/hooks.md +33 -0
- package/skills/graphify/skills/droid/references/query.md +249 -0
- package/skills/graphify/skills/droid/references/transcribe.md +48 -0
- package/skills/graphify/skills/droid/references/update.md +179 -0
- package/skills/graphify/skills/kilo/references/add-watch.md +56 -0
- package/skills/graphify/skills/kilo/references/exports.md +71 -0
- package/skills/graphify/skills/kilo/references/extraction-spec.md +68 -0
- package/skills/graphify/skills/kilo/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/kilo/references/hooks.md +33 -0
- package/skills/graphify/skills/kilo/references/query.md +249 -0
- package/skills/graphify/skills/kilo/references/transcribe.md +48 -0
- package/skills/graphify/skills/kilo/references/update.md +179 -0
- package/skills/graphify/skills/kiro/references/add-watch.md +56 -0
- package/skills/graphify/skills/kiro/references/exports.md +71 -0
- package/skills/graphify/skills/kiro/references/extraction-spec.md +29 -0
- package/skills/graphify/skills/kiro/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/kiro/references/hooks.md +33 -0
- package/skills/graphify/skills/kiro/references/query.md +249 -0
- package/skills/graphify/skills/kiro/references/transcribe.md +48 -0
- package/skills/graphify/skills/kiro/references/update.md +179 -0
- package/skills/graphify/skills/opencode/references/add-watch.md +56 -0
- package/skills/graphify/skills/opencode/references/exports.md +71 -0
- package/skills/graphify/skills/opencode/references/extraction-spec.md +68 -0
- package/skills/graphify/skills/opencode/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/opencode/references/hooks.md +33 -0
- package/skills/graphify/skills/opencode/references/query.md +249 -0
- package/skills/graphify/skills/opencode/references/transcribe.md +48 -0
- package/skills/graphify/skills/opencode/references/update.md +179 -0
- package/skills/graphify/skills/pi/references/add-watch.md +56 -0
- package/skills/graphify/skills/pi/references/exports.md +71 -0
- package/skills/graphify/skills/pi/references/extraction-spec.md +29 -0
- package/skills/graphify/skills/pi/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/pi/references/hooks.md +33 -0
- package/skills/graphify/skills/pi/references/query.md +249 -0
- package/skills/graphify/skills/pi/references/transcribe.md +48 -0
- package/skills/graphify/skills/pi/references/update.md +179 -0
- package/skills/graphify/skills/trae/references/add-watch.md +56 -0
- package/skills/graphify/skills/trae/references/exports.md +71 -0
- package/skills/graphify/skills/trae/references/extraction-spec.md +68 -0
- package/skills/graphify/skills/trae/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/trae/references/hooks.md +35 -0
- package/skills/graphify/skills/trae/references/query.md +249 -0
- package/skills/graphify/skills/trae/references/transcribe.md +48 -0
- package/skills/graphify/skills/trae/references/update.md +179 -0
- package/skills/graphify/skills/vscode/references/add-watch.md +56 -0
- package/skills/graphify/skills/vscode/references/exports.md +71 -0
- package/skills/graphify/skills/vscode/references/extraction-spec.md +68 -0
- package/skills/graphify/skills/vscode/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/vscode/references/hooks.md +33 -0
- package/skills/graphify/skills/vscode/references/query.md +249 -0
- package/skills/graphify/skills/vscode/references/transcribe.md +48 -0
- package/skills/graphify/skills/vscode/references/update.md +179 -0
- package/skills/graphify/skills/windows/references/add-watch.md +56 -0
- package/skills/graphify/skills/windows/references/exports.md +71 -0
- package/skills/graphify/skills/windows/references/extraction-spec.md +68 -0
- package/skills/graphify/skills/windows/references/github-and-merge.md +46 -0
- package/skills/graphify/skills/windows/references/hooks.md +33 -0
- package/skills/graphify/skills/windows/references/query.md +249 -0
- package/skills/graphify/skills/windows/references/transcribe.md +48 -0
- package/skills/graphify/skills/windows/references/update.md +179 -0
- package/skills/graphify/symbol_resolution.py +538 -0
- package/skills/graphify/transcribe.py +184 -0
- package/skills/graphify/tree_html.py +582 -0
- package/skills/graphify/validate.py +72 -0
- package/skills/graphify/watch.py +898 -0
- package/skills/graphify/wiki.py +282 -0
- package/skills/impeccable/SKILL.md +186 -0
- package/skills/impeccable/agents/impeccable_asset_producer.toml +92 -0
- package/skills/impeccable/agents/impeccable_manual_edit_applier.toml +95 -0
- package/skills/impeccable/agents/openai.yaml +4 -0
- package/skills/impeccable/reference/adapt.md +311 -0
- package/skills/impeccable/reference/animate.md +201 -0
- package/skills/impeccable/reference/audit.md +133 -0
- package/skills/impeccable/reference/bolder.md +113 -0
- package/skills/impeccable/reference/brand.md +108 -0
- package/skills/impeccable/reference/clarify.md +288 -0
- package/skills/impeccable/reference/codex.md +105 -0
- package/skills/impeccable/reference/colorize.md +257 -0
- package/skills/impeccable/reference/craft.md +123 -0
- package/skills/impeccable/reference/critique.md +790 -0
- package/skills/impeccable/reference/delight.md +302 -0
- package/skills/impeccable/reference/distill.md +111 -0
- package/skills/impeccable/reference/document.md +429 -0
- package/skills/impeccable/reference/extract.md +69 -0
- package/skills/impeccable/reference/harden.md +347 -0
- package/skills/impeccable/reference/init.md +172 -0
- package/skills/impeccable/reference/interaction-design.md +189 -0
- package/skills/impeccable/reference/layout.md +161 -0
- package/skills/impeccable/reference/live.md +720 -0
- package/skills/impeccable/reference/onboard.md +234 -0
- package/skills/impeccable/reference/optimize.md +258 -0
- package/skills/impeccable/reference/overdrive.md +130 -0
- package/skills/impeccable/reference/polish.md +241 -0
- package/skills/impeccable/reference/product.md +60 -0
- package/skills/impeccable/reference/quieter.md +99 -0
- package/skills/impeccable/reference/shape.md +165 -0
- package/skills/impeccable/reference/typeset.md +279 -0
- package/skills/impeccable/scripts/cleanup-deprecated.mjs +284 -0
- package/skills/impeccable/scripts/command-metadata.json +94 -0
- package/skills/impeccable/scripts/context-signals.mjs +225 -0
- package/skills/impeccable/scripts/context.mjs +266 -0
- package/skills/impeccable/scripts/critique-storage.mjs +242 -0
- package/skills/impeccable/scripts/design-parser.mjs +835 -0
- package/skills/impeccable/scripts/detect-csp.mjs +198 -0
- package/skills/impeccable/scripts/detect.mjs +21 -0
- package/skills/impeccable/scripts/detector/browser/injected/index.mjs +1733 -0
- package/skills/impeccable/scripts/detector/cli/main.mjs +244 -0
- package/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +4618 -0
- package/skills/impeccable/scripts/detector/detect-antipatterns.mjs +43 -0
- package/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +252 -0
- package/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +535 -0
- package/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +986 -0
- package/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +208 -0
- package/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +189 -0
- package/skills/impeccable/scripts/detector/findings.mjs +12 -0
- package/skills/impeccable/scripts/detector/node/file-system.mjs +198 -0
- package/skills/impeccable/scripts/detector/profile/profiler.mjs +166 -0
- package/skills/impeccable/scripts/detector/registry/antipatterns.mjs +419 -0
- package/skills/impeccable/scripts/detector/rules/checks.mjs +2384 -0
- package/skills/impeccable/scripts/detector/shared/color.mjs +124 -0
- package/skills/impeccable/scripts/detector/shared/constants.mjs +101 -0
- package/skills/impeccable/scripts/detector/shared/page.mjs +7 -0
- package/skills/impeccable/scripts/impeccable-paths.mjs +126 -0
- package/skills/impeccable/scripts/is-generated.mjs +69 -0
- package/skills/impeccable/scripts/live-accept.mjs +812 -0
- package/skills/impeccable/scripts/live-browser-session.js +123 -0
- package/skills/impeccable/scripts/live-browser.js +10295 -0
- package/skills/impeccable/scripts/live-commit-manual-edits.mjs +1241 -0
- package/skills/impeccable/scripts/live-complete.mjs +75 -0
- package/skills/impeccable/scripts/live-completion.mjs +19 -0
- package/skills/impeccable/scripts/live-copy-edit-agent.mjs +683 -0
- package/skills/impeccable/scripts/live-discard-manual-edits.mjs +51 -0
- package/skills/impeccable/scripts/live-event-validation.mjs +137 -0
- package/skills/impeccable/scripts/live-inject.mjs +557 -0
- package/skills/impeccable/scripts/live-insert-ui.mjs +458 -0
- package/skills/impeccable/scripts/live-insert.mjs +272 -0
- package/skills/impeccable/scripts/live-manual-edit-evidence.mjs +363 -0
- package/skills/impeccable/scripts/live-manual-edits-buffer.mjs +152 -0
- package/skills/impeccable/scripts/live-poll.mjs +379 -0
- package/skills/impeccable/scripts/live-resume.mjs +94 -0
- package/skills/impeccable/scripts/live-server.mjs +2326 -0
- package/skills/impeccable/scripts/live-session-store.mjs +289 -0
- package/skills/impeccable/scripts/live-status.mjs +61 -0
- package/skills/impeccable/scripts/live-svelte-component.mjs +826 -0
- package/skills/impeccable/scripts/live-sveltekit-adapter.mjs +274 -0
- package/skills/impeccable/scripts/live-ui-core.mjs +179 -0
- package/skills/impeccable/scripts/live-vocabulary.mjs +36 -0
- package/skills/impeccable/scripts/live-wrap.mjs +894 -0
- package/skills/impeccable/scripts/live.mjs +246 -0
- package/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
- package/skills/impeccable/scripts/palette.mjs +633 -0
- package/skills/impeccable/scripts/pin.mjs +214 -0
- package/skills/uipm-ui-styling/LICENSE.txt +202 -0
- package/skills/uipm-ui-styling/SKILL.md +328 -0
- package/skills/uipm-ui-styling/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/DMMono-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/skills/uipm-ui-styling/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Gloock-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Italiana-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Jura-Light.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Jura-Medium.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Jura-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Lora-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Lora-Italic.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Lora-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/Lora-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Outfit-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/Tektur-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/skills/uipm-ui-styling/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/skills/uipm-ui-styling/references/canvas-design-system.md +320 -0
- package/skills/uipm-ui-styling/references/shadcn-accessibility.md +471 -0
- package/skills/uipm-ui-styling/references/shadcn-components.md +424 -0
- package/skills/uipm-ui-styling/references/shadcn-theming.md +373 -0
- package/skills/uipm-ui-styling/references/tailwind-customization.md +483 -0
- package/skills/uipm-ui-styling/references/tailwind-responsive.md +382 -0
- package/skills/uipm-ui-styling/references/tailwind-utilities.md +455 -0
- package/skills/uipm-ui-styling/scripts/.coverage +0 -0
- package/skills/uipm-ui-styling/scripts/requirements.txt +17 -0
- package/skills/uipm-ui-styling/scripts/shadcn_add.py +292 -0
- package/skills/uipm-ui-styling/scripts/tailwind_config_gen.py +456 -0
- package/skills/uipm-ui-styling/scripts/tests/coverage-ui.json +1 -0
- package/skills/uipm-ui-styling/scripts/tests/requirements.txt +3 -0
- package/skills/uipm-ui-styling/scripts/tests/test_shadcn_add.py +266 -0
- 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)
|