@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,538 @@
|
|
|
1
|
+
"""Deterministic symbol indexing and conservative cross-file resolution helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import re
|
|
7
|
+
import unicodedata
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from graphify.security import sanitize_metadata
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class ImportedSymbol:
|
|
19
|
+
"""A Python imported name that can be used as deterministic resolution evidence."""
|
|
20
|
+
|
|
21
|
+
local_name: str
|
|
22
|
+
imported_name: str
|
|
23
|
+
module_stem: str
|
|
24
|
+
source_file: str
|
|
25
|
+
source_location: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def normalise_callable_label(label: str) -> str:
|
|
29
|
+
"""Normalize a node label into the key used for call resolution."""
|
|
30
|
+
|
|
31
|
+
return label.strip().strip("()").lstrip(".").lower()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def node_is_resolvable_symbol(node: dict[str, Any]) -> bool:
|
|
35
|
+
"""Return True when a node is suitable for deterministic symbol lookup.
|
|
36
|
+
|
|
37
|
+
Requires ``file_type == "code"`` as the positive gate — only code-class
|
|
38
|
+
nodes participate as call targets. ``_EXCLUDED_FILE_TYPES`` is kept as
|
|
39
|
+
defensive-in-depth against legacy data, but the primary guard is the
|
|
40
|
+
positive code check. Document/paper/image/concept nodes (e.g. a Markdown
|
|
41
|
+
heading whose label happens to match a code identifier) MUST NOT become
|
|
42
|
+
callees for a raw code call.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
if node.get("file_type") != "code":
|
|
46
|
+
return False
|
|
47
|
+
label = str(node.get("label", "")).strip()
|
|
48
|
+
if not label:
|
|
49
|
+
return False
|
|
50
|
+
if label.endswith((".py", ".js", ".ts", ".tsx", ".java", ".go", ".rs")):
|
|
51
|
+
return False
|
|
52
|
+
return bool(normalise_callable_label(label))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_label_index(nodes: list[dict[str, Any]]) -> dict[str, list[str]]:
|
|
56
|
+
"""Build label -> node id list for conservative cross-file resolution."""
|
|
57
|
+
|
|
58
|
+
index: dict[str, list[str]] = {}
|
|
59
|
+
for node in nodes:
|
|
60
|
+
if not node_is_resolvable_symbol(node):
|
|
61
|
+
continue
|
|
62
|
+
node_id = node.get("id")
|
|
63
|
+
if not node_id:
|
|
64
|
+
continue
|
|
65
|
+
key = normalise_callable_label(str(node.get("label", "")))
|
|
66
|
+
if not key:
|
|
67
|
+
continue
|
|
68
|
+
index.setdefault(key, []).append(str(node_id))
|
|
69
|
+
return index
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def existing_edge_pairs(edges: list[dict[str, Any]]) -> set[tuple[str, str, str]]:
|
|
73
|
+
"""Return all existing source/target/relation edge triples.
|
|
74
|
+
|
|
75
|
+
Includes relation so that a prior "contains" or "method" edge does not
|
|
76
|
+
suppress a semantically distinct "calls" edge between the same endpoints (#F5).
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
triples: set[tuple[str, str, str]] = set()
|
|
80
|
+
for edge in edges:
|
|
81
|
+
source = edge.get("source")
|
|
82
|
+
target = edge.get("target")
|
|
83
|
+
relation = edge.get("relation", "")
|
|
84
|
+
if source and target:
|
|
85
|
+
triples.add((str(source), str(target), str(relation)))
|
|
86
|
+
return triples
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def iter_raw_calls(per_file: Sequence[object]) -> list[dict[str, Any]]:
|
|
90
|
+
"""Return raw calls from all per-file extraction fragments.
|
|
91
|
+
|
|
92
|
+
Parameter is ``Sequence[object]`` (not ``Sequence[dict[str, Any] | None]``)
|
|
93
|
+
because external extraction output may contain arbitrary deserialized
|
|
94
|
+
JSON. Defensive against malformed fragments: non-dict per-file entries
|
|
95
|
+
are skipped, non-list ``raw_calls`` are treated as empty, and non-dict
|
|
96
|
+
items inside the list are silently dropped. The downstream resolvers
|
|
97
|
+
assume every returned item is a dict and they expect this guarantee.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
calls: list[dict[str, Any]] = []
|
|
101
|
+
for result in per_file:
|
|
102
|
+
if not isinstance(result, dict):
|
|
103
|
+
continue
|
|
104
|
+
raw_calls = result.get("raw_calls", [])
|
|
105
|
+
if not isinstance(raw_calls, list):
|
|
106
|
+
continue
|
|
107
|
+
for raw_call in raw_calls:
|
|
108
|
+
if isinstance(raw_call, dict):
|
|
109
|
+
calls.append(raw_call)
|
|
110
|
+
return calls
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _module_stem(module_name: str | None) -> str:
|
|
114
|
+
"""Return the final module component used to match Graphify source stems."""
|
|
115
|
+
|
|
116
|
+
if not module_name:
|
|
117
|
+
return ""
|
|
118
|
+
return module_name.strip(".").split(".")[-1]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def parse_python_import_aliases(path: Path) -> dict[str, ImportedSymbol]:
|
|
122
|
+
"""Parse deterministic Python import aliases from one source file.
|
|
123
|
+
|
|
124
|
+
Supported forms:
|
|
125
|
+
from helper import transform
|
|
126
|
+
from helper import transform as tx
|
|
127
|
+
from .helper import transform
|
|
128
|
+
|
|
129
|
+
The function deliberately does not resolve plain ``import helper`` member
|
|
130
|
+
calls because current raw call records do not preserve the receiver name from
|
|
131
|
+
``helper.transform()``. That can be added later only after raw call facts are
|
|
132
|
+
extended to include the receiver expression.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
source = path.read_text(encoding="utf-8", errors="replace")
|
|
137
|
+
tree = ast.parse(source)
|
|
138
|
+
except (OSError, SyntaxError):
|
|
139
|
+
return {}
|
|
140
|
+
|
|
141
|
+
aliases: dict[str, ImportedSymbol] = {}
|
|
142
|
+
source_file = str(path)
|
|
143
|
+
|
|
144
|
+
# Only top-level `from ... import ...` statements count as file-wide
|
|
145
|
+
# evidence. Nested/function-local imports do NOT — they're only valid
|
|
146
|
+
# inside their lexical scope, and our raw-call records don't currently
|
|
147
|
+
# carry enough scope info to match the import site safely. Walking
|
|
148
|
+
# ast.walk(tree) would incorrectly justify calls in other scopes.
|
|
149
|
+
for node in tree.body:
|
|
150
|
+
if not isinstance(node, ast.ImportFrom):
|
|
151
|
+
continue
|
|
152
|
+
module_stem = _module_stem(node.module)
|
|
153
|
+
if not module_stem:
|
|
154
|
+
continue
|
|
155
|
+
for alias in node.names:
|
|
156
|
+
if alias.name == "*":
|
|
157
|
+
continue
|
|
158
|
+
local_name = alias.asname or alias.name
|
|
159
|
+
aliases[local_name] = ImportedSymbol(
|
|
160
|
+
local_name=local_name,
|
|
161
|
+
imported_name=alias.name,
|
|
162
|
+
module_stem=module_stem,
|
|
163
|
+
source_file=source_file,
|
|
164
|
+
source_location=f"L{getattr(node, 'lineno', 1)}",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return aliases
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _node_source_stem(node: dict[str, Any]) -> str:
|
|
171
|
+
"""Return the stem of a node's source file."""
|
|
172
|
+
|
|
173
|
+
source_file = str(node.get("source_file", ""))
|
|
174
|
+
if not source_file:
|
|
175
|
+
return ""
|
|
176
|
+
return Path(source_file).stem
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def build_python_symbol_index(nodes: list[dict[str, Any]]) -> dict[tuple[str, str], list[str]]:
|
|
180
|
+
"""Build ``(module_stem, normalized_symbol_name) -> node_ids``.
|
|
181
|
+
|
|
182
|
+
This index is stricter than the global label index. It uses both the module
|
|
183
|
+
stem and the symbol label, which allows import evidence to resolve calls that
|
|
184
|
+
global label uniqueness alone cannot safely resolve.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
index: dict[tuple[str, str], list[str]] = {}
|
|
188
|
+
for node in nodes:
|
|
189
|
+
if not node_is_resolvable_symbol(node):
|
|
190
|
+
continue
|
|
191
|
+
source_stem = _node_source_stem(node)
|
|
192
|
+
if not source_stem:
|
|
193
|
+
continue
|
|
194
|
+
label = normalise_callable_label(str(node.get("label", "")))
|
|
195
|
+
if not label:
|
|
196
|
+
continue
|
|
197
|
+
node_id = node.get("id")
|
|
198
|
+
if not node_id:
|
|
199
|
+
continue
|
|
200
|
+
index.setdefault((source_stem, label), []).append(str(node_id))
|
|
201
|
+
return index
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def find_unique_python_symbol(
|
|
205
|
+
symbol_index: dict[tuple[str, str], list[str]],
|
|
206
|
+
imported: ImportedSymbol,
|
|
207
|
+
) -> str | None:
|
|
208
|
+
"""Resolve one imported symbol to exactly one Graphify node id."""
|
|
209
|
+
|
|
210
|
+
candidates = symbol_index.get((imported.module_stem, imported.imported_name.lower()), [])
|
|
211
|
+
if len(candidates) == 1:
|
|
212
|
+
return candidates[0]
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def resolve_python_import_guided_calls(
|
|
217
|
+
per_file: Sequence[object],
|
|
218
|
+
paths: Sequence[Path],
|
|
219
|
+
all_nodes: list[dict[str, Any]],
|
|
220
|
+
all_edges: list[dict[str, Any]],
|
|
221
|
+
) -> list[dict[str, Any]]:
|
|
222
|
+
"""Resolve raw Python calls using explicit import evidence.
|
|
223
|
+
|
|
224
|
+
Only ``from module import symbol [as alias]`` forms are handled. Member calls
|
|
225
|
+
remain skipped because the current raw call fact does not carry receiver
|
|
226
|
+
information.
|
|
227
|
+
|
|
228
|
+
Parameter ``per_file`` is ``Sequence[object]`` because external extraction
|
|
229
|
+
output may contain arbitrary deserialized JSON. Non-dict slots are
|
|
230
|
+
treated as empty fragments, and indices past ``len(per_file)`` are also
|
|
231
|
+
treated as empty (paths longer than per_file is tolerated).
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
symbol_index = build_python_symbol_index(all_nodes)
|
|
235
|
+
known_pairs = existing_edge_pairs(all_edges)
|
|
236
|
+
# Build result_by_file defensively:
|
|
237
|
+
# - skip indices past the end of per_file (paths shorter than per_file
|
|
238
|
+
# also OK; the zip-like behavior is what callers expect)
|
|
239
|
+
# - non-dict per_file slots fall back to the empty fragment so the
|
|
240
|
+
# downstream `.get("raw_calls", [])` lookup never raises
|
|
241
|
+
result_by_file: dict[str, dict[str, Any]] = {}
|
|
242
|
+
for index, path in enumerate(paths):
|
|
243
|
+
if path.suffix != ".py":
|
|
244
|
+
continue
|
|
245
|
+
slot: Any = per_file[index] if index < len(per_file) else None
|
|
246
|
+
result_by_file[str(path)] = slot if isinstance(slot, dict) else {"nodes": [], "edges": []}
|
|
247
|
+
resolved_edges: list[dict[str, Any]] = []
|
|
248
|
+
|
|
249
|
+
for path in paths:
|
|
250
|
+
if path.suffix != ".py":
|
|
251
|
+
continue
|
|
252
|
+
source_file = str(path)
|
|
253
|
+
aliases = parse_python_import_aliases(path)
|
|
254
|
+
if not aliases:
|
|
255
|
+
continue
|
|
256
|
+
file_result = result_by_file.get(source_file, {"raw_calls": []})
|
|
257
|
+
raw_calls = file_result.get("raw_calls", [])
|
|
258
|
+
if not isinstance(raw_calls, list):
|
|
259
|
+
continue
|
|
260
|
+
for raw_call in raw_calls:
|
|
261
|
+
if not isinstance(raw_call, dict):
|
|
262
|
+
continue
|
|
263
|
+
if raw_call.get("is_member_call"):
|
|
264
|
+
continue
|
|
265
|
+
callee = str(raw_call.get("callee", "")).strip()
|
|
266
|
+
if not callee:
|
|
267
|
+
continue
|
|
268
|
+
imported = aliases.get(callee)
|
|
269
|
+
if imported is None:
|
|
270
|
+
continue
|
|
271
|
+
target = find_unique_python_symbol(symbol_index, imported)
|
|
272
|
+
if target is None:
|
|
273
|
+
continue
|
|
274
|
+
caller = str(raw_call.get("caller_nid", ""))
|
|
275
|
+
if not caller or caller == target:
|
|
276
|
+
continue
|
|
277
|
+
pair = (caller, target, "calls")
|
|
278
|
+
if pair in known_pairs:
|
|
279
|
+
continue
|
|
280
|
+
known_pairs.add(pair)
|
|
281
|
+
resolved_edges.append(
|
|
282
|
+
{
|
|
283
|
+
"source": caller,
|
|
284
|
+
"target": target,
|
|
285
|
+
"relation": "calls",
|
|
286
|
+
"context": "import_guided_call",
|
|
287
|
+
"confidence": "EXTRACTED",
|
|
288
|
+
"confidence_score": 1.0,
|
|
289
|
+
"source_file": raw_call.get("source_file", source_file),
|
|
290
|
+
"source_location": raw_call.get("source_location") or imported.source_location,
|
|
291
|
+
"weight": 1.0,
|
|
292
|
+
"metadata": sanitize_metadata({
|
|
293
|
+
"resolver": "python_import_guided",
|
|
294
|
+
"local_name": imported.local_name,
|
|
295
|
+
"imported_name": imported.imported_name,
|
|
296
|
+
"module_stem": imported.module_stem,
|
|
297
|
+
"import_source_location": imported.source_location,
|
|
298
|
+
}),
|
|
299
|
+
}
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
return resolved_edges
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def resolve_cross_file_raw_calls(
|
|
306
|
+
per_file: Sequence[dict[str, Any] | None],
|
|
307
|
+
all_nodes: list[dict[str, Any]],
|
|
308
|
+
all_edges: list[dict[str, Any]],
|
|
309
|
+
) -> list[dict[str, Any]]:
|
|
310
|
+
"""Resolve unqualified raw calls conservatively after all files are known.
|
|
311
|
+
|
|
312
|
+
This intentionally preserves Graphify's existing behavior:
|
|
313
|
+
- member calls are skipped;
|
|
314
|
+
- ambiguous labels are skipped;
|
|
315
|
+
- only a single unique candidate is emitted;
|
|
316
|
+
- emitted edges are INFERRED because the raw call alone is not import proof.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
label_index = build_label_index(all_nodes)
|
|
320
|
+
known_pairs = existing_edge_pairs(all_edges)
|
|
321
|
+
resolved: list[dict[str, Any]] = []
|
|
322
|
+
|
|
323
|
+
for raw_call in iter_raw_calls(per_file):
|
|
324
|
+
callee = str(raw_call.get("callee", "")).strip()
|
|
325
|
+
if not callee:
|
|
326
|
+
continue
|
|
327
|
+
if raw_call.get("is_member_call"):
|
|
328
|
+
continue
|
|
329
|
+
candidates = label_index.get(callee.lower(), [])
|
|
330
|
+
if len(candidates) != 1:
|
|
331
|
+
continue
|
|
332
|
+
target = candidates[0]
|
|
333
|
+
caller = str(raw_call.get("caller_nid", ""))
|
|
334
|
+
if not caller:
|
|
335
|
+
continue
|
|
336
|
+
if target == caller:
|
|
337
|
+
continue
|
|
338
|
+
pair = (caller, target, "calls")
|
|
339
|
+
if pair in known_pairs:
|
|
340
|
+
continue
|
|
341
|
+
known_pairs.add(pair)
|
|
342
|
+
resolved.append(
|
|
343
|
+
{
|
|
344
|
+
"source": caller,
|
|
345
|
+
"target": target,
|
|
346
|
+
"relation": "calls",
|
|
347
|
+
"context": "call",
|
|
348
|
+
"confidence": "INFERRED",
|
|
349
|
+
"confidence_score": 0.8,
|
|
350
|
+
"source_file": raw_call.get("source_file", ""),
|
|
351
|
+
"source_location": raw_call.get("source_location"),
|
|
352
|
+
"weight": 1.0,
|
|
353
|
+
}
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
return resolved
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _bash_make_id(*parts: str) -> str:
|
|
360
|
+
"""Exact copy of extract._make_id — kept here to avoid an import cycle."""
|
|
361
|
+
combined = "_".join(p.strip("_.") for p in parts if p)
|
|
362
|
+
combined = unicodedata.normalize("NFKC", combined)
|
|
363
|
+
cleaned = re.sub(r"[^\w]+", "_", combined, flags=re.UNICODE)
|
|
364
|
+
cleaned = re.sub(r"_+", "_", cleaned)
|
|
365
|
+
return cleaned.strip("_").casefold()
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _bash_file_stem(rel_path: Path) -> str:
|
|
369
|
+
"""Exact copy of extract._file_stem — kept here to avoid an import cycle."""
|
|
370
|
+
parent = rel_path.parent.name
|
|
371
|
+
if parent and parent not in (".", ""):
|
|
372
|
+
return f"{parent}.{rel_path.stem}"
|
|
373
|
+
return rel_path.stem
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _file_node_id_for_path(path: Path, root: Path) -> str:
|
|
377
|
+
# Produce the canonical {parent_dir}_{stem} file-node ID that extract()'s
|
|
378
|
+
# id_remap generates (#1033), so bash `source` edges land on the real file
|
|
379
|
+
# node instead of an orphan. _bash_make_id / _bash_file_stem are exact copies
|
|
380
|
+
# of extract._make_id / extract._file_stem, so IDs match.
|
|
381
|
+
try:
|
|
382
|
+
rel = path.resolve().relative_to(root.resolve())
|
|
383
|
+
except ValueError:
|
|
384
|
+
return _bash_make_id(str(path)) # path outside root: hash absolute path as fallback
|
|
385
|
+
return _bash_make_id(_bash_file_stem(rel))
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def resolve_bash_source_edges(
|
|
389
|
+
per_file: Sequence[dict | None],
|
|
390
|
+
paths: Sequence[Path],
|
|
391
|
+
root: Path,
|
|
392
|
+
existing_edges: list[dict] | None = None,
|
|
393
|
+
) -> list[dict]:
|
|
394
|
+
"""Resolve Bash source/import edges and source-backed function calls.
|
|
395
|
+
|
|
396
|
+
Defensive against malformed extraction fragments: non-dict ``per_file``
|
|
397
|
+
entries, missing ``bash_sources``/``raw_calls`` keys, non-dict items in
|
|
398
|
+
those lists, and missing/empty ``id`` / ``target_path`` / ``caller_nid``
|
|
399
|
+
fields all yield silent skips rather than ``KeyError``.
|
|
400
|
+
|
|
401
|
+
``bash_sources[].target_path`` contract (Graphify static-analysis policy):
|
|
402
|
+
- Absolute paths: resolved as-is.
|
|
403
|
+
- Relative paths: resolved against the *source file's* directory
|
|
404
|
+
(i.e. ``Path(path).parent / target_path``).
|
|
405
|
+
NOTE: this is a deterministic static-analysis policy chosen by
|
|
406
|
+
Graphify, NOT bash runtime semantics. At runtime, ``source ./X``
|
|
407
|
+
is resolved against the shell's current working directory. We
|
|
408
|
+
prefer source-file-relative because static analysis cannot know
|
|
409
|
+
the future CWD; resolving against the file being analyzed gives
|
|
410
|
+
deterministic, reproducible edges across runs.
|
|
411
|
+
- Inputs of type ``str`` and ``pathlib.Path`` are processed.
|
|
412
|
+
Anything else is silently skipped.
|
|
413
|
+
"""
|
|
414
|
+
path_by_index = [Path(p).resolve() for p in paths]
|
|
415
|
+
file_nid_by_path = {p: _file_node_id_for_path(p, root) for p in path_by_index} # resolved paths only
|
|
416
|
+
|
|
417
|
+
functions_by_file: dict[str, dict[str, str]] = {}
|
|
418
|
+
for result, path in zip(per_file, path_by_index):
|
|
419
|
+
if not isinstance(result, dict):
|
|
420
|
+
continue
|
|
421
|
+
file_nid = file_nid_by_path[path]
|
|
422
|
+
nodes = result.get("nodes", [])
|
|
423
|
+
if not isinstance(nodes, list):
|
|
424
|
+
continue
|
|
425
|
+
for node in nodes:
|
|
426
|
+
if not isinstance(node, dict):
|
|
427
|
+
continue
|
|
428
|
+
metadata = node.get("metadata", {})
|
|
429
|
+
if not isinstance(metadata, dict):
|
|
430
|
+
continue
|
|
431
|
+
if metadata.get("kind") != "bash_function":
|
|
432
|
+
continue
|
|
433
|
+
name = str(node.get("label", "")).removesuffix("()").strip()
|
|
434
|
+
node_id = node.get("id")
|
|
435
|
+
if not name or not node_id:
|
|
436
|
+
continue
|
|
437
|
+
functions_by_file.setdefault(file_nid, {})[name] = str(node_id)
|
|
438
|
+
|
|
439
|
+
sourced_files: dict[str, set[str]] = {}
|
|
440
|
+
resolved_edges: list[dict] = []
|
|
441
|
+
existing = existing_edge_pairs(existing_edges or [])
|
|
442
|
+
|
|
443
|
+
for result, path in zip(per_file, path_by_index):
|
|
444
|
+
if not isinstance(result, dict):
|
|
445
|
+
continue
|
|
446
|
+
src_file_nid = file_nid_by_path[path]
|
|
447
|
+
bash_sources = result.get("bash_sources", [])
|
|
448
|
+
if not isinstance(bash_sources, list):
|
|
449
|
+
continue
|
|
450
|
+
for source in bash_sources:
|
|
451
|
+
if not isinstance(source, dict):
|
|
452
|
+
continue
|
|
453
|
+
raw_target = source.get("target_path")
|
|
454
|
+
if not isinstance(raw_target, (str, Path)) or not str(raw_target).strip():
|
|
455
|
+
continue
|
|
456
|
+
# Relative paths resolve against the source file's directory —
|
|
457
|
+
# Graphify static-analysis policy (NOT bash runtime semantics;
|
|
458
|
+
# at runtime `source ./X` is CWD-relative, but static analysis
|
|
459
|
+
# can't know the future CWD, so we resolve relative to the
|
|
460
|
+
# file being analyzed for deterministic, reproducible edges).
|
|
461
|
+
candidate = Path(raw_target)
|
|
462
|
+
if not candidate.is_absolute():
|
|
463
|
+
candidate = path.parent / candidate
|
|
464
|
+
try:
|
|
465
|
+
target_path = candidate.resolve()
|
|
466
|
+
except (OSError, RuntimeError):
|
|
467
|
+
continue
|
|
468
|
+
target_file_nid = file_nid_by_path.get(target_path)
|
|
469
|
+
if target_file_nid is None:
|
|
470
|
+
continue
|
|
471
|
+
sourced_files.setdefault(src_file_nid, set()).add(target_file_nid)
|
|
472
|
+
key = (src_file_nid, target_file_nid, "imports_from")
|
|
473
|
+
if key in existing:
|
|
474
|
+
continue
|
|
475
|
+
existing.add(key)
|
|
476
|
+
resolved_edges.append(
|
|
477
|
+
{
|
|
478
|
+
"source": src_file_nid,
|
|
479
|
+
"target": target_file_nid,
|
|
480
|
+
"relation": "imports_from",
|
|
481
|
+
"context": "import",
|
|
482
|
+
"confidence": "EXTRACTED",
|
|
483
|
+
"confidence_score": 1.0,
|
|
484
|
+
"source_file": source.get("source_file", str(path)),
|
|
485
|
+
"source_location": source.get("source_location", ""),
|
|
486
|
+
"weight": 1.0,
|
|
487
|
+
}
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
for result, path in zip(per_file, path_by_index):
|
|
491
|
+
if not isinstance(result, dict):
|
|
492
|
+
continue
|
|
493
|
+
caller_file_nid = file_nid_by_path[path]
|
|
494
|
+
imported_file_ids = sourced_files.get(caller_file_nid, set())
|
|
495
|
+
if not imported_file_ids:
|
|
496
|
+
continue
|
|
497
|
+
raw_calls = result.get("raw_calls", [])
|
|
498
|
+
if not isinstance(raw_calls, list):
|
|
499
|
+
continue
|
|
500
|
+
for raw_call in raw_calls:
|
|
501
|
+
if not isinstance(raw_call, dict):
|
|
502
|
+
continue
|
|
503
|
+
if raw_call.get("language") != "bash":
|
|
504
|
+
continue
|
|
505
|
+
callee = raw_call.get("callee")
|
|
506
|
+
caller_nid = raw_call.get("caller_nid")
|
|
507
|
+
# callee must be a non-empty string — anything else (list, dict,
|
|
508
|
+
# int, None, …) is silently skipped to avoid TypeError on the
|
|
509
|
+
# `in functions_by_file[...]` membership check below.
|
|
510
|
+
if not isinstance(callee, str) or not callee or not caller_nid:
|
|
511
|
+
continue
|
|
512
|
+
matches = [
|
|
513
|
+
functions_by_file[file_nid][callee]
|
|
514
|
+
for file_nid in imported_file_ids
|
|
515
|
+
if callee in functions_by_file.get(file_nid, {})
|
|
516
|
+
]
|
|
517
|
+
if len(matches) != 1:
|
|
518
|
+
continue
|
|
519
|
+
target = matches[0]
|
|
520
|
+
key = (str(caller_nid), target, "calls")
|
|
521
|
+
if key in existing:
|
|
522
|
+
continue
|
|
523
|
+
existing.add(key)
|
|
524
|
+
resolved_edges.append(
|
|
525
|
+
{
|
|
526
|
+
"source": str(caller_nid),
|
|
527
|
+
"target": target,
|
|
528
|
+
"relation": "calls",
|
|
529
|
+
"context": "call",
|
|
530
|
+
"confidence": "EXTRACTED",
|
|
531
|
+
"confidence_score": 1.0,
|
|
532
|
+
"source_file": raw_call.get("source_file", str(path)),
|
|
533
|
+
"source_location": raw_call.get("source_location", ""),
|
|
534
|
+
"weight": 1.0,
|
|
535
|
+
}
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
return resolved_edges
|