@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,70 @@
|
|
|
1
|
+
"""Query logging for graphify — append-only JSONL, fail-silent."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import time
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
_NODES_RE = re.compile(r"(\d+)\s+nodes?\s+found")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _log_path() -> Path | None:
|
|
16
|
+
if os.environ.get("GRAPHIFY_QUERY_LOG_DISABLE", "").lower() in ("1", "true", "yes"):
|
|
17
|
+
return None
|
|
18
|
+
override = os.environ.get("GRAPHIFY_QUERY_LOG", "").strip()
|
|
19
|
+
if override:
|
|
20
|
+
return Path(override).expanduser()
|
|
21
|
+
return Path.home() / ".cache" / "graphify-queries.log"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _log_responses() -> bool:
|
|
25
|
+
return os.environ.get("GRAPHIFY_QUERY_LOG_RESPONSES", "").lower() in ("1", "true", "yes")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def nodes_from_result(result: str) -> int | None:
|
|
29
|
+
m = _NODES_RE.search(result or "")
|
|
30
|
+
return int(m.group(1)) if m else None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def log_query(
|
|
34
|
+
*,
|
|
35
|
+
kind: str,
|
|
36
|
+
question: str,
|
|
37
|
+
corpus: str,
|
|
38
|
+
result: str | None = None,
|
|
39
|
+
nodes_returned: int | None = None,
|
|
40
|
+
duration_ms: float | None = None,
|
|
41
|
+
**extra: Any,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Append one JSONL record to the query log. Never raises."""
|
|
44
|
+
try:
|
|
45
|
+
path = _log_path()
|
|
46
|
+
if path is None:
|
|
47
|
+
return
|
|
48
|
+
if nodes_returned is None and result is not None:
|
|
49
|
+
nodes_returned = nodes_from_result(result)
|
|
50
|
+
rec: dict[str, Any] = {
|
|
51
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
52
|
+
"kind": kind,
|
|
53
|
+
"question": question,
|
|
54
|
+
"corpus": corpus,
|
|
55
|
+
"nodes_returned": nodes_returned,
|
|
56
|
+
}
|
|
57
|
+
if result is not None:
|
|
58
|
+
rec["result_chars"] = len(result)
|
|
59
|
+
if duration_ms is not None:
|
|
60
|
+
rec["duration_ms"] = round(duration_ms, 3)
|
|
61
|
+
for k, v in extra.items():
|
|
62
|
+
if v is not None:
|
|
63
|
+
rec[k] = v
|
|
64
|
+
if result is not None and _log_responses():
|
|
65
|
+
rec["response"] = result
|
|
66
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
with path.open("a", encoding="utf-8") as fh:
|
|
68
|
+
fh.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# generate GRAPH_REPORT.md - the human-readable audit trail
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import re
|
|
4
|
+
from datetime import date
|
|
5
|
+
import networkx as nx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _safe_community_name(label: str) -> str:
|
|
9
|
+
"""Mirrors export.safe_name so community hub filenames and report wikilinks always agree."""
|
|
10
|
+
cleaned = re.sub(r'[\\/*?:"<>|#^[\]]', "", label.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")).strip()
|
|
11
|
+
cleaned = re.sub(r"\.(md|mdx|markdown)$", "", cleaned, flags=re.IGNORECASE)
|
|
12
|
+
return cleaned or "unnamed"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def generate(
|
|
16
|
+
G: nx.Graph,
|
|
17
|
+
communities: dict[int, list[str]],
|
|
18
|
+
cohesion_scores: dict[int, float],
|
|
19
|
+
community_labels: dict[int, str],
|
|
20
|
+
god_node_list: list[dict],
|
|
21
|
+
surprise_list: list[dict],
|
|
22
|
+
detection_result: dict,
|
|
23
|
+
token_cost: dict,
|
|
24
|
+
root: str,
|
|
25
|
+
suggested_questions: list[dict] | None = None,
|
|
26
|
+
min_community_size: int = 3,
|
|
27
|
+
built_at_commit: str | None = None,
|
|
28
|
+
) -> str:
|
|
29
|
+
today = date.today().isoformat()
|
|
30
|
+
|
|
31
|
+
# JSON deserialization produces string keys; normalize to int so .get(cid) works.
|
|
32
|
+
if community_labels:
|
|
33
|
+
community_labels = {int(k) if isinstance(k, str) else k: v for k, v in community_labels.items()}
|
|
34
|
+
|
|
35
|
+
confidences = [d.get("confidence", "EXTRACTED") for _, _, d in G.edges(data=True)]
|
|
36
|
+
total = len(confidences) or 1
|
|
37
|
+
ext_pct = round(confidences.count("EXTRACTED") / total * 100)
|
|
38
|
+
inf_pct = round(confidences.count("INFERRED") / total * 100)
|
|
39
|
+
amb_pct = round(confidences.count("AMBIGUOUS") / total * 100)
|
|
40
|
+
|
|
41
|
+
inf_edges = [(u, v, d) for u, v, d in G.edges(data=True) if d.get("confidence") == "INFERRED"]
|
|
42
|
+
inf_scores = [d.get("confidence_score", 0.5) for _, _, d in inf_edges]
|
|
43
|
+
inf_avg = round(sum(inf_scores) / len(inf_scores), 2) if inf_scores else None
|
|
44
|
+
|
|
45
|
+
lines = [
|
|
46
|
+
f"# Graph Report - {root} ({today})",
|
|
47
|
+
"",
|
|
48
|
+
"## Corpus Check",
|
|
49
|
+
]
|
|
50
|
+
if detection_result.get("warning"):
|
|
51
|
+
lines.append(f"- {detection_result['warning']}")
|
|
52
|
+
else:
|
|
53
|
+
lines += [
|
|
54
|
+
f"- {detection_result['total_files']} files · ~{detection_result['total_words']:,} words",
|
|
55
|
+
"- Verdict: corpus is large enough that graph structure adds value.",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
from .analyze import _is_file_node as _ifn
|
|
59
|
+
non_empty = {cid: nodes for cid, nodes in communities.items()
|
|
60
|
+
if any(not _ifn(G, n) for n in nodes)}
|
|
61
|
+
thin_count_summary = sum(
|
|
62
|
+
1 for nodes in communities.values()
|
|
63
|
+
if 0 < sum(1 for n in nodes if not _ifn(G, n)) < min_community_size
|
|
64
|
+
)
|
|
65
|
+
shown_count = len(communities) - thin_count_summary
|
|
66
|
+
|
|
67
|
+
lines += [
|
|
68
|
+
"",
|
|
69
|
+
"## Summary",
|
|
70
|
+
f"- {G.number_of_nodes()} nodes · {G.number_of_edges()} edges · {len(communities)} communities"
|
|
71
|
+
+ (f" ({shown_count} shown, {thin_count_summary} thin omitted)" if thin_count_summary else ""),
|
|
72
|
+
f"- Extraction: {ext_pct}% EXTRACTED · {inf_pct}% INFERRED · {amb_pct}% AMBIGUOUS"
|
|
73
|
+
+ (f" · INFERRED: {len(inf_edges)} edges (avg confidence: {inf_avg})" if inf_avg is not None else ""),
|
|
74
|
+
f"- Token cost: {token_cost.get('input', 0):,} input · {token_cost.get('output', 0):,} output",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
if built_at_commit:
|
|
78
|
+
lines += [
|
|
79
|
+
"",
|
|
80
|
+
"## Graph Freshness",
|
|
81
|
+
f"- Built from commit: `{built_at_commit[:8]}`",
|
|
82
|
+
"- Run `git rev-parse HEAD` and compare to check if the graph is stale.",
|
|
83
|
+
"- Run `graphify update .` after code changes (no API cost).",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# Community hub navigation - links to _COMMUNITY_*.md files in the Obsidian vault.
|
|
87
|
+
# Without these, GRAPH_REPORT.md is a dead-end and the vault splits into disconnected components.
|
|
88
|
+
if non_empty:
|
|
89
|
+
lines += ["", "## Community Hubs (Navigation)"]
|
|
90
|
+
for cid in non_empty:
|
|
91
|
+
label = community_labels.get(cid, f"Community {cid}")
|
|
92
|
+
safe = _safe_community_name(label)
|
|
93
|
+
lines.append(f"- [[_COMMUNITY_{safe}|{label}]]")
|
|
94
|
+
|
|
95
|
+
lines += [
|
|
96
|
+
"",
|
|
97
|
+
"## God Nodes (most connected - your core abstractions)",
|
|
98
|
+
]
|
|
99
|
+
for i, node in enumerate(god_node_list, 1):
|
|
100
|
+
lines.append(f"{i}. `{node['label']}` - {node['degree']} edges")
|
|
101
|
+
|
|
102
|
+
lines += ["", "## Surprising Connections (you probably didn't know these)"]
|
|
103
|
+
if surprise_list:
|
|
104
|
+
for s in surprise_list:
|
|
105
|
+
relation = s.get("relation", "related_to")
|
|
106
|
+
note = s.get("note", "")
|
|
107
|
+
files = s.get("source_files", ["", ""])
|
|
108
|
+
conf = s.get("confidence", "EXTRACTED")
|
|
109
|
+
cscore = s.get("confidence_score")
|
|
110
|
+
if conf == "INFERRED" and cscore is not None:
|
|
111
|
+
conf_tag = f"INFERRED {cscore:.2f}"
|
|
112
|
+
else:
|
|
113
|
+
conf_tag = conf
|
|
114
|
+
sem_tag = " [semantically similar]" if relation == "semantically_similar_to" else ""
|
|
115
|
+
lines += [
|
|
116
|
+
f"- `{s['source']}` --{relation}--> `{s['target']}` [{conf_tag}]{sem_tag}",
|
|
117
|
+
f" {files[0]} → {files[1]}" + (f" _{note}_" if note else ""),
|
|
118
|
+
]
|
|
119
|
+
else:
|
|
120
|
+
lines.append("- None detected - all connections are within the same source files.")
|
|
121
|
+
|
|
122
|
+
# Circular imports surfaced from file-level dependency graph.
|
|
123
|
+
from .analyze import find_import_cycles
|
|
124
|
+
cycles = find_import_cycles(G)
|
|
125
|
+
lines += ["", "## Import Cycles"]
|
|
126
|
+
if cycles:
|
|
127
|
+
for c in cycles:
|
|
128
|
+
cycle = c.get("cycle", [])
|
|
129
|
+
length = c.get("length", len(cycle))
|
|
130
|
+
if not cycle:
|
|
131
|
+
continue
|
|
132
|
+
cycle_path = " -> ".join(cycle + [cycle[0]])
|
|
133
|
+
lines.append(f"- {length}-file cycle: `{cycle_path}`")
|
|
134
|
+
else:
|
|
135
|
+
lines.append("- None detected.")
|
|
136
|
+
|
|
137
|
+
hyperedges = G.graph.get("hyperedges", [])
|
|
138
|
+
if hyperedges:
|
|
139
|
+
lines += ["", "## Hyperedges (group relationships)"]
|
|
140
|
+
for h in hyperedges:
|
|
141
|
+
node_labels = ", ".join(h.get("nodes", []))
|
|
142
|
+
conf = h.get("confidence", "INFERRED")
|
|
143
|
+
cscore = h.get("confidence_score")
|
|
144
|
+
conf_tag = f"{conf} {cscore:.2f}" if cscore is not None else conf
|
|
145
|
+
lines.append(f"- **{h.get('label', h.get('id', ''))}** — {node_labels} [{conf_tag}]")
|
|
146
|
+
|
|
147
|
+
lines += ["", f"## Communities ({len(communities)} total, {thin_count_summary} thin omitted)"]
|
|
148
|
+
for cid, nodes in communities.items():
|
|
149
|
+
label = community_labels.get(cid, f"Community {cid}")
|
|
150
|
+
score = cohesion_scores.get(cid, 0.0)
|
|
151
|
+
# Filter method/function stubs from display - they're structural noise
|
|
152
|
+
real_nodes = [n for n in nodes if not _ifn(G, n)]
|
|
153
|
+
if not real_nodes:
|
|
154
|
+
continue
|
|
155
|
+
if len(real_nodes) < min_community_size:
|
|
156
|
+
continue
|
|
157
|
+
display = [G.nodes[n].get("label", n) for n in real_nodes[:8]]
|
|
158
|
+
suffix = f" (+{len(real_nodes)-8} more)" if len(real_nodes) > 8 else ""
|
|
159
|
+
lines += [
|
|
160
|
+
"",
|
|
161
|
+
f"### Community {cid} - \"{label}\"",
|
|
162
|
+
f"Cohesion: {score:.2f}",
|
|
163
|
+
f"Nodes ({len(real_nodes)}): {', '.join(display)}{suffix}",
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
ambiguous = [(u, v, d) for u, v, d in G.edges(data=True) if d.get("confidence") == "AMBIGUOUS"]
|
|
167
|
+
if ambiguous:
|
|
168
|
+
lines += ["", "## Ambiguous Edges - Review These"]
|
|
169
|
+
for u, v, d in ambiguous:
|
|
170
|
+
ul = G.nodes[u].get("label", u)
|
|
171
|
+
vl = G.nodes[v].get("label", v)
|
|
172
|
+
lines += [
|
|
173
|
+
f"- `{ul}` → `{vl}` [AMBIGUOUS]",
|
|
174
|
+
f" {d.get('source_file', '')} · relation: {d.get('relation', 'unknown')}",
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
# --- Gaps section ---
|
|
178
|
+
from .analyze import _is_file_node, _is_concept_node
|
|
179
|
+
|
|
180
|
+
isolated = [
|
|
181
|
+
n for n in G.nodes()
|
|
182
|
+
if G.degree(n) <= 1
|
|
183
|
+
and not _is_file_node(G, n)
|
|
184
|
+
and not _is_concept_node(G, n)
|
|
185
|
+
and G.nodes[n].get("file_type") != "rationale"
|
|
186
|
+
]
|
|
187
|
+
thin_communities = {
|
|
188
|
+
cid: nodes for cid, nodes in communities.items()
|
|
189
|
+
if 0 < sum(1 for n in nodes if not _is_file_node(G, n)) < 3
|
|
190
|
+
}
|
|
191
|
+
gap_count = len(isolated) + len(thin_communities)
|
|
192
|
+
|
|
193
|
+
if gap_count > 0 or amb_pct > 20:
|
|
194
|
+
lines += ["", "## Knowledge Gaps"]
|
|
195
|
+
if isolated:
|
|
196
|
+
isolated_labels = [G.nodes[n].get("label", n) for n in isolated[:5]]
|
|
197
|
+
suffix = f" (+{len(isolated)-5} more)" if len(isolated) > 5 else ""
|
|
198
|
+
lines.append(f"- **{len(isolated)} isolated node(s):** {', '.join(f'`{l}`' for l in isolated_labels)}{suffix}")
|
|
199
|
+
lines.append(" These have ≤1 connection - possible missing edges or undocumented components.")
|
|
200
|
+
if thin_communities:
|
|
201
|
+
lines.append(f"- **{len(thin_communities)} thin communities (<{min_community_size} nodes) omitted from report** — run `graphify query` to explore isolated nodes.")
|
|
202
|
+
if amb_pct > 20:
|
|
203
|
+
lines.append(f"- **High ambiguity: {amb_pct}% of edges are AMBIGUOUS.** Review the Ambiguous Edges section above.")
|
|
204
|
+
|
|
205
|
+
if suggested_questions:
|
|
206
|
+
lines += ["", "## Suggested Questions"]
|
|
207
|
+
no_signal = len(suggested_questions) == 1 and suggested_questions[0].get("type") == "no_signal"
|
|
208
|
+
if no_signal:
|
|
209
|
+
lines.append(f"_{suggested_questions[0]['why']}_")
|
|
210
|
+
else:
|
|
211
|
+
lines.append("_Questions this graph is uniquely positioned to answer:_")
|
|
212
|
+
lines.append("")
|
|
213
|
+
for q in suggested_questions:
|
|
214
|
+
if q.get("question"):
|
|
215
|
+
lines.append(f"- **{q['question']}**")
|
|
216
|
+
lines.append(f" _{q['why']}_")
|
|
217
|
+
|
|
218
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""scip_ingest.py — SCIP JSON ingestion (simplified subset).
|
|
2
|
+
|
|
3
|
+
Reads a simplified SCIP-style JSON structure and converts it into
|
|
4
|
+
Graphify nodes and edges. NOT a full SCIP protobuf implementation —
|
|
5
|
+
this is a skeleton that consumes the simplified shape described below.
|
|
6
|
+
|
|
7
|
+
Not wired to the CLI in this phase.
|
|
8
|
+
|
|
9
|
+
Entry point:
|
|
10
|
+
ingest_scip_json(doc: object, source_file: str = "",
|
|
11
|
+
language: str = "python") -> dict[str, Any]
|
|
12
|
+
|
|
13
|
+
Returns {"nodes": [...], "edges": [...]} compatible with Graphify's
|
|
14
|
+
extraction result format. All edges emitted are endpoint-safe — the
|
|
15
|
+
function builds a symbol → node_id index in a first pass and either
|
|
16
|
+
resolves relationship targets via that index or creates a stub
|
|
17
|
+
external node so `build_from_json()` will keep the edge.
|
|
18
|
+
|
|
19
|
+
Supported (simplified) JSON shape:
|
|
20
|
+
documents[]: { relative_path, language, symbols[] }
|
|
21
|
+
symbols[]: { symbol, kind, display_name, documentation[],
|
|
22
|
+
relationships[], occurrences[] }
|
|
23
|
+
relationships[]: { symbol, is_reference, is_implementation,
|
|
24
|
+
is_type_definition, is_definition }
|
|
25
|
+
occurrences[]: { range[], symbol, symbol_roles }
|
|
26
|
+
|
|
27
|
+
This shape diverges from the official SCIP protobuf (where occurrences
|
|
28
|
+
live on the document, not on each symbol). We consume the simplified
|
|
29
|
+
shape that LLM-generated SCIP-style JSON commonly produces. Future
|
|
30
|
+
cycles may add document-level occurrence support.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import hashlib
|
|
36
|
+
import re
|
|
37
|
+
from typing import Any
|
|
38
|
+
|
|
39
|
+
from graphify.security import sanitize_metadata
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def ingest_scip_json(
|
|
43
|
+
doc: object,
|
|
44
|
+
source_file: str = "",
|
|
45
|
+
language: str = "python",
|
|
46
|
+
) -> dict[str, Any]:
|
|
47
|
+
"""Convert a SCIP-style JSON document into Graphify nodes and edges.
|
|
48
|
+
|
|
49
|
+
Parameter ``doc`` is ``object`` (not ``dict[str, Any]``) because SCIP
|
|
50
|
+
documents come from external tools — we may be handed arbitrary
|
|
51
|
+
deserialized JSON. The first check rejects anything that isn't a dict
|
|
52
|
+
and returns the empty result.
|
|
53
|
+
|
|
54
|
+
Two-pass design:
|
|
55
|
+
1. Build a ``symbol_str → node_id`` index across every valid symbol
|
|
56
|
+
in every valid document, plus collect per-symbol metadata.
|
|
57
|
+
2. Emit nodes for every indexed symbol and then emit relationship
|
|
58
|
+
edges. Relationship targets are resolved via the index when
|
|
59
|
+
present; otherwise a stub ``scip_external`` node is added so
|
|
60
|
+
edges never dangle.
|
|
61
|
+
"""
|
|
62
|
+
nodes: list[dict[str, Any]] = []
|
|
63
|
+
edges: list[dict[str, Any]] = []
|
|
64
|
+
seen_node_ids: set[str] = set()
|
|
65
|
+
seen_edges: set[tuple[str, str, str, str | None]] = set()
|
|
66
|
+
|
|
67
|
+
if not isinstance(doc, dict):
|
|
68
|
+
return {"nodes": nodes, "edges": edges}
|
|
69
|
+
|
|
70
|
+
documents = doc.get("documents", [])
|
|
71
|
+
if not isinstance(documents, list):
|
|
72
|
+
return {"nodes": nodes, "edges": edges}
|
|
73
|
+
|
|
74
|
+
# ---- pass 1: build symbol → node_id indices -----------------------------
|
|
75
|
+
# Two indices so relationship resolution can be document-aware:
|
|
76
|
+
# per_doc: (symbol_id, doc_path) → node_id (same-document precedence)
|
|
77
|
+
# global: symbol_id → list[node_id] (cross-document fallback,
|
|
78
|
+
# used only when unambiguous)
|
|
79
|
+
per_doc_index: dict[tuple[str, str], str] = {}
|
|
80
|
+
global_index: dict[str, list[str]] = {}
|
|
81
|
+
# Per-symbol metadata kept for pass-2 node emission (avoids re-walking
|
|
82
|
+
# the document tree).
|
|
83
|
+
symbol_records: list[dict[str, Any]] = []
|
|
84
|
+
for document in documents:
|
|
85
|
+
if not isinstance(document, dict):
|
|
86
|
+
continue
|
|
87
|
+
doc_path = _coerce_str(document.get("relative_path"), source_file)
|
|
88
|
+
doc_language = _coerce_str(document.get("language"), language)
|
|
89
|
+
symbols = document.get("symbols", [])
|
|
90
|
+
if not isinstance(symbols, list):
|
|
91
|
+
continue
|
|
92
|
+
for symbol in symbols:
|
|
93
|
+
if not isinstance(symbol, dict):
|
|
94
|
+
continue
|
|
95
|
+
symbol_id = _coerce_str(symbol.get("symbol"), "")
|
|
96
|
+
if not symbol_id:
|
|
97
|
+
continue
|
|
98
|
+
node_id = _make_scip_node_id(symbol_id, doc_path)
|
|
99
|
+
per_doc_index.setdefault((symbol_id, doc_path), node_id)
|
|
100
|
+
# Dedupe node_ids in the global index — duplicate symbol records
|
|
101
|
+
# within the SAME document produce identical node_ids, and we
|
|
102
|
+
# don't want them to look like cross-document ambiguity.
|
|
103
|
+
candidates = global_index.setdefault(symbol_id, [])
|
|
104
|
+
if node_id not in candidates:
|
|
105
|
+
candidates.append(node_id)
|
|
106
|
+
symbol_records.append(
|
|
107
|
+
{
|
|
108
|
+
"node_id": node_id,
|
|
109
|
+
"symbol_id": symbol_id,
|
|
110
|
+
"doc_path": doc_path,
|
|
111
|
+
"language": doc_language,
|
|
112
|
+
"raw": symbol,
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# ---- pass 2: emit nodes + relationship edges -----------------------------
|
|
117
|
+
for record in symbol_records:
|
|
118
|
+
_emit_symbol_node(record, nodes, seen_node_ids)
|
|
119
|
+
_emit_relationships(
|
|
120
|
+
record,
|
|
121
|
+
per_doc_index,
|
|
122
|
+
global_index,
|
|
123
|
+
nodes,
|
|
124
|
+
edges,
|
|
125
|
+
seen_node_ids,
|
|
126
|
+
seen_edges,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return {"nodes": nodes, "edges": edges}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _emit_symbol_node(
|
|
133
|
+
record: dict[str, Any],
|
|
134
|
+
nodes: list[dict[str, Any]],
|
|
135
|
+
seen_node_ids: set[str],
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Append the canonical node for a SCIP symbol record."""
|
|
138
|
+
node_id = record["node_id"]
|
|
139
|
+
if node_id in seen_node_ids:
|
|
140
|
+
return
|
|
141
|
+
raw = record["raw"]
|
|
142
|
+
symbol_id = record["symbol_id"]
|
|
143
|
+
doc_path = record["doc_path"]
|
|
144
|
+
kind = _coerce_str(raw.get("kind"), "unknown")
|
|
145
|
+
display_name = _coerce_str(raw.get("display_name"), "")
|
|
146
|
+
documentation = raw.get("documentation", [])
|
|
147
|
+
description = ""
|
|
148
|
+
if isinstance(documentation, list) and documentation:
|
|
149
|
+
first = documentation[0]
|
|
150
|
+
if isinstance(first, str):
|
|
151
|
+
description = first
|
|
152
|
+
occurrences = raw.get("occurrences", [])
|
|
153
|
+
sourceline = _first_occurrence_line(occurrences)
|
|
154
|
+
suffix = symbol_id.split("#")[-1] if "#" in symbol_id else symbol_id
|
|
155
|
+
label = display_name or suffix or symbol_id
|
|
156
|
+
seen_node_ids.add(node_id) # label uses display_name or suffix (never empty for valid symbols)
|
|
157
|
+
nodes.append(
|
|
158
|
+
{
|
|
159
|
+
"id": node_id,
|
|
160
|
+
"label": label,
|
|
161
|
+
"file_type": _scip_kind_to_file_type(kind),
|
|
162
|
+
"source_file": doc_path,
|
|
163
|
+
"source_location": f"L{sourceline}" if sourceline else "",
|
|
164
|
+
"metadata": sanitize_metadata(_build_scip_metadata(symbol_id, kind, description)),
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _emit_relationships(
|
|
170
|
+
record: dict[str, Any],
|
|
171
|
+
per_doc_index: dict[tuple[str, str], str],
|
|
172
|
+
global_index: dict[str, list[str]],
|
|
173
|
+
nodes: list[dict[str, Any]],
|
|
174
|
+
edges: list[dict[str, Any]],
|
|
175
|
+
seen_node_ids: set[str],
|
|
176
|
+
seen_edges: set[tuple[str, str, str, str | None]],
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Append edges (and stub nodes when needed) for a symbol's relationships.
|
|
179
|
+
|
|
180
|
+
Relationship target resolution order:
|
|
181
|
+
1. Same-document `(target_symbol, doc_path)` — duplicate local symbol
|
|
182
|
+
names across files route to THIS file's symbol, not another's.
|
|
183
|
+
2. Unique cross-document match — when the symbol exists in exactly
|
|
184
|
+
one document and that document is different from the source.
|
|
185
|
+
3. Stub external node — for symbols not declared in any document
|
|
186
|
+
OR ambiguous duplicates across multiple documents (refusing to
|
|
187
|
+
guess silently).
|
|
188
|
+
"""
|
|
189
|
+
raw = record["raw"]
|
|
190
|
+
source_node_id = record["node_id"]
|
|
191
|
+
doc_path = record["doc_path"]
|
|
192
|
+
occurrences = raw.get("occurrences", [])
|
|
193
|
+
sourceline = _first_occurrence_line(occurrences)
|
|
194
|
+
relationships = raw.get("relationships")
|
|
195
|
+
if not isinstance(relationships, list):
|
|
196
|
+
return
|
|
197
|
+
for rel in relationships:
|
|
198
|
+
if not isinstance(rel, dict):
|
|
199
|
+
continue
|
|
200
|
+
target_symbol = _coerce_str(rel.get("symbol"), "")
|
|
201
|
+
if not target_symbol:
|
|
202
|
+
continue
|
|
203
|
+
target_node_id = _resolve_relationship_target(
|
|
204
|
+
target_symbol,
|
|
205
|
+
doc_path,
|
|
206
|
+
per_doc_index,
|
|
207
|
+
global_index,
|
|
208
|
+
)
|
|
209
|
+
if target_node_id is None:
|
|
210
|
+
# External relationship target: emit a stub node so the edge
|
|
211
|
+
# is never dangling. The stub uses the source document's path
|
|
212
|
+
# as its host context.
|
|
213
|
+
target_node_id = _make_scip_node_id(target_symbol, doc_path)
|
|
214
|
+
if target_node_id not in seen_node_ids:
|
|
215
|
+
seen_node_ids.add(target_node_id)
|
|
216
|
+
suffix = target_symbol.split("#")[-1] if "#" in target_symbol else target_symbol
|
|
217
|
+
nodes.append(
|
|
218
|
+
{
|
|
219
|
+
"id": target_node_id,
|
|
220
|
+
"label": suffix or target_symbol,
|
|
221
|
+
"file_type": "code",
|
|
222
|
+
"source_file": doc_path,
|
|
223
|
+
"source_location": "",
|
|
224
|
+
"metadata": sanitize_metadata(
|
|
225
|
+
_build_scip_metadata(target_symbol, "external", "")
|
|
226
|
+
),
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
relation = _scip_relation_for(rel)
|
|
230
|
+
source_location = f"L{sourceline}" if sourceline else ""
|
|
231
|
+
key = (source_node_id, target_node_id, relation, source_location)
|
|
232
|
+
if key in seen_edges:
|
|
233
|
+
continue
|
|
234
|
+
seen_edges.add(key)
|
|
235
|
+
edges.append(
|
|
236
|
+
{
|
|
237
|
+
"source": source_node_id,
|
|
238
|
+
"target": target_node_id,
|
|
239
|
+
"relation": relation,
|
|
240
|
+
"confidence": "EXTRACTED",
|
|
241
|
+
"confidence_score": 1.0,
|
|
242
|
+
"source_file": doc_path,
|
|
243
|
+
"source_location": source_location,
|
|
244
|
+
"weight": 1.0,
|
|
245
|
+
"context": "scip",
|
|
246
|
+
"metadata": sanitize_metadata({"scip_relationship": rel}),
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _resolve_relationship_target(
|
|
252
|
+
target_symbol: str,
|
|
253
|
+
source_doc_path: str,
|
|
254
|
+
per_doc_index: dict[tuple[str, str], str],
|
|
255
|
+
global_index: dict[str, list[str]],
|
|
256
|
+
) -> str | None:
|
|
257
|
+
"""Resolve a SCIP relationship target to an emitted node id, or None.
|
|
258
|
+
|
|
259
|
+
Resolution order:
|
|
260
|
+
1. Same-document match — `(target_symbol, source_doc_path)`.
|
|
261
|
+
2. Unique cross-document match — exactly one node id in the global
|
|
262
|
+
index for this symbol AND it isn't the same document we already
|
|
263
|
+
tried.
|
|
264
|
+
3. None — symbol is either absent globally OR ambiguous (defined in
|
|
265
|
+
multiple documents). The caller emits a stub external node.
|
|
266
|
+
"""
|
|
267
|
+
same_doc = per_doc_index.get((target_symbol, source_doc_path))
|
|
268
|
+
if same_doc is not None:
|
|
269
|
+
return same_doc
|
|
270
|
+
candidates = global_index.get(target_symbol, [])
|
|
271
|
+
if len(candidates) == 1:
|
|
272
|
+
return candidates[0]
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _is_true(value: object) -> bool:
|
|
277
|
+
"""Return True only when value is exactly the boolean True.
|
|
278
|
+
|
|
279
|
+
Used for SCIP relationship flags. Truthy strings like ``"false"`` are
|
|
280
|
+
common in untrusted external JSON and must NOT count as a set flag.
|
|
281
|
+
"""
|
|
282
|
+
return value is True
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _scip_relation_for(rel: dict[str, Any]) -> str:
|
|
286
|
+
"""Pick the Graphify relation tag for a SCIP relationship dict.
|
|
287
|
+
|
|
288
|
+
Flags are accepted only when the value is exactly ``True`` — protects
|
|
289
|
+
against truthy-but-misleading values like ``"false"`` in external JSON.
|
|
290
|
+
"""
|
|
291
|
+
if _is_true(rel.get("is_implementation")):
|
|
292
|
+
return "scip_impl"
|
|
293
|
+
if _is_true(rel.get("is_type_definition")):
|
|
294
|
+
return "scip_typed"
|
|
295
|
+
if _is_true(rel.get("is_definition")):
|
|
296
|
+
return "scip_def"
|
|
297
|
+
return "scip_ref"
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _first_occurrence_line(occurrences: object) -> int:
|
|
301
|
+
"""Read the 1-based line number from the first occurrence range, defensively.
|
|
302
|
+
|
|
303
|
+
Note: ``bool`` is a subclass of ``int`` in Python — ``isinstance(True, int)``
|
|
304
|
+
is True. We explicitly exclude booleans so a malformed ``range: [True, …]``
|
|
305
|
+
cannot produce ``source_location = "LTrue"``.
|
|
306
|
+
"""
|
|
307
|
+
if not isinstance(occurrences, list) or not occurrences:
|
|
308
|
+
return 0
|
|
309
|
+
first = occurrences[0]
|
|
310
|
+
if not isinstance(first, dict):
|
|
311
|
+
return 0
|
|
312
|
+
rng = first.get("range", [])
|
|
313
|
+
if not isinstance(rng, list) or len(rng) < 1:
|
|
314
|
+
return 0
|
|
315
|
+
line = rng[0]
|
|
316
|
+
if isinstance(line, bool) or not isinstance(line, int) or line < 0:
|
|
317
|
+
return 0
|
|
318
|
+
return line
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _coerce_str(value: object, default: str) -> str:
|
|
322
|
+
"""Return ``value`` if it is a string, else the ``default`` (also a string)."""
|
|
323
|
+
if isinstance(value, str):
|
|
324
|
+
return value
|
|
325
|
+
if isinstance(default, str):
|
|
326
|
+
return default
|
|
327
|
+
return ""
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _make_scip_node_id(symbol: str, source_file: str) -> str:
|
|
331
|
+
"""Derive a stable Graphify node ID from a SCIP symbol identifier.
|
|
332
|
+
|
|
333
|
+
Uses SHA-1 truncated to 12 hex chars (48 bits). This is an identifier,
|
|
334
|
+
not a security boundary — collision risk is acceptable at this scale
|
|
335
|
+
given the per-document scoping prefix.
|
|
336
|
+
"""
|
|
337
|
+
raw = f"{source_file}:{symbol}"
|
|
338
|
+
h = hashlib.sha1(raw.encode(), usedforsecurity=False).hexdigest()[:12]
|
|
339
|
+
parts = symbol.split("#")
|
|
340
|
+
suffix = parts[-1] if parts else symbol
|
|
341
|
+
suffix = re.sub(r"[^a-zA-Z0-9_]", "_", suffix).strip("_").lower()
|
|
342
|
+
if suffix:
|
|
343
|
+
return f"scip_{suffix}_{h}"
|
|
344
|
+
return f"scip_{h}"
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _scip_kind_to_file_type(kind: str) -> str:
|
|
348
|
+
"""Map SCIP symbol kind to a Graphify file_type."""
|
|
349
|
+
# All SCIP symbols are code entities (functions, methods, classes, …);
|
|
350
|
+
# the `kind` is preserved in metadata for downstream consumers.
|
|
351
|
+
_ = kind # acknowledged but not currently used for file_type routing
|
|
352
|
+
return "code"
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _build_scip_metadata(symbol_id: str, kind: str, description: str) -> dict[str, str]:
|
|
356
|
+
"""Build metadata for a SCIP node."""
|
|
357
|
+
meta: dict[str, str] = {
|
|
358
|
+
"scip_symbol": symbol_id,
|
|
359
|
+
"scip_kind": kind,
|
|
360
|
+
}
|
|
361
|
+
if description:
|
|
362
|
+
meta["scip_description"] = description
|
|
363
|
+
return meta
|