@oriro/orirocli 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ATTRIBUTION.md +8 -0
- package/LICENSE +21 -0
- 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,429 @@
|
|
|
1
|
+
"""Entity deduplication pipeline for graphify knowledge graphs.
|
|
2
|
+
|
|
3
|
+
Pipeline: exact normalization → entropy gate → MinHash/LSH blocking →
|
|
4
|
+
Jaro-Winkler verification → same-community boost → union-find merge.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import math
|
|
8
|
+
import re
|
|
9
|
+
import unicodedata
|
|
10
|
+
from collections import defaultdict
|
|
11
|
+
|
|
12
|
+
from datasketch import MinHash, MinHashLSH
|
|
13
|
+
from rapidfuzz.distance import JaroWinkler
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ── helpers ───────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
def _norm(label: str) -> str:
|
|
19
|
+
"""Lowercase + collapse non-alphanumeric runs to space (Unicode-aware)."""
|
|
20
|
+
label = unicodedata.normalize("NFKC", label)
|
|
21
|
+
return re.sub(r"[\W_]+", " ", label.casefold(), flags=re.UNICODE).strip()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _entropy(label: str) -> float:
|
|
25
|
+
"""Shannon entropy in bits/char of the normalised label."""
|
|
26
|
+
s = _norm(label)
|
|
27
|
+
if not s:
|
|
28
|
+
return 0.0
|
|
29
|
+
freq: dict[str, int] = defaultdict(int)
|
|
30
|
+
for ch in s:
|
|
31
|
+
freq[ch] += 1
|
|
32
|
+
n = len(s)
|
|
33
|
+
return -sum((c / n) * math.log2(c / n) for c in freq.values())
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _shingles(text: str, k: int = 3) -> set[str]:
|
|
37
|
+
"""Return k-gram character shingles of text."""
|
|
38
|
+
if len(text) < k:
|
|
39
|
+
return {text}
|
|
40
|
+
return {text[i : i + k] for i in range(len(text) - k + 1)}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _make_minhash(text: str, num_perm: int = 128) -> MinHash:
|
|
44
|
+
# Strip spaces so "graph extractor" and "graphextractor" share shingles
|
|
45
|
+
m = MinHash(num_perm=num_perm)
|
|
46
|
+
for shingle in _shingles(text.replace(" ", "")):
|
|
47
|
+
m.update(shingle.encode("utf-8"))
|
|
48
|
+
return m
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Matches labels whose trailing token is a version/variant suffix:
|
|
52
|
+
# digits optionally followed by letters (chip SKUs: ASR1603, M1, Cortex-A55)
|
|
53
|
+
# or 2+ letters (codename revisions: cranelr vs cranel).
|
|
54
|
+
# Requires the stem to end in a letter so plain words don't accidentally match.
|
|
55
|
+
_VARIANT_SUFFIX = re.compile(r"^(.*[a-z])([0-9]+[a-z]*|[a-z]{2,})$")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _is_variant_pair(a: str, b: str) -> bool:
|
|
59
|
+
"""True if a and b are sibling model/SKU variants (same stem, different suffix).
|
|
60
|
+
|
|
61
|
+
Only applied to short labels (< 12 chars); long labels go through JW normally.
|
|
62
|
+
"""
|
|
63
|
+
if a == b:
|
|
64
|
+
return False
|
|
65
|
+
if max(len(a), len(b)) >= 12:
|
|
66
|
+
return False
|
|
67
|
+
ma, mb = _VARIANT_SUFFIX.match(a), _VARIANT_SUFFIX.match(b)
|
|
68
|
+
if not (ma and mb):
|
|
69
|
+
return False
|
|
70
|
+
return ma.group(1) == mb.group(1) and ma.group(2) != mb.group(2)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _short_label_blocked(a: str, b: str, jw_score: float) -> bool:
|
|
74
|
+
"""Block fuzzy merge for short labels unless it's a same-length single-char substitution.
|
|
75
|
+
|
|
76
|
+
Insertions/deletions on short strings (cranel/cranelr, M1/M1 Pro) produce
|
|
77
|
+
high Jaro-Winkler scores due to the prefix bonus but are almost never true
|
|
78
|
+
duplicates — they're abbreviations or variants.
|
|
79
|
+
"""
|
|
80
|
+
if max(len(a), len(b)) >= 12:
|
|
81
|
+
return False
|
|
82
|
+
from rapidfuzz.distance import DamerauLevenshtein
|
|
83
|
+
# Allow only same-length single-char substitutions (true typos like "Extractor"/"Extractar").
|
|
84
|
+
# Block length-differing pairs regardless of score.
|
|
85
|
+
if jw_score >= 97.0 and len(a) == len(b) and DamerauLevenshtein.distance(a, b) <= 1:
|
|
86
|
+
return False
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── union-find ────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
class _UF:
|
|
93
|
+
def __init__(self) -> None:
|
|
94
|
+
self._parent: dict[str, str] = {}
|
|
95
|
+
|
|
96
|
+
def find(self, x: str) -> str:
|
|
97
|
+
self._parent.setdefault(x, x)
|
|
98
|
+
while self._parent[x] != x:
|
|
99
|
+
self._parent[x] = self._parent[self._parent[x]]
|
|
100
|
+
x = self._parent[x]
|
|
101
|
+
return x
|
|
102
|
+
|
|
103
|
+
def union(self, x: str, y: str) -> None:
|
|
104
|
+
self._parent.setdefault(x, x)
|
|
105
|
+
self._parent.setdefault(y, y)
|
|
106
|
+
rx, ry = self.find(x), self.find(y)
|
|
107
|
+
if rx != ry:
|
|
108
|
+
self._parent[ry] = rx
|
|
109
|
+
|
|
110
|
+
def components(self) -> dict[str, list[str]]:
|
|
111
|
+
groups: dict[str, list[str]] = defaultdict(list)
|
|
112
|
+
for x in self._parent:
|
|
113
|
+
groups[self.find(x)].append(x)
|
|
114
|
+
return dict(groups)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ── constants ─────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
_ENTROPY_THRESHOLD = 2.5
|
|
120
|
+
_LSH_THRESHOLD = 0.7
|
|
121
|
+
_MERGE_THRESHOLD = 92.0 # rapidfuzz normalized_similarity * 100
|
|
122
|
+
_COMMUNITY_BOOST = 5.0 # score bonus when both nodes share community
|
|
123
|
+
_NUM_PERM = 128
|
|
124
|
+
_CHUNK_SUFFIX = re.compile(r"_c\d+$")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ── main entry point ──────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
def deduplicate_entities(
|
|
130
|
+
nodes: list[dict],
|
|
131
|
+
edges: list[dict],
|
|
132
|
+
*,
|
|
133
|
+
communities: dict[str, int],
|
|
134
|
+
dedup_llm_backend: str | None = None,
|
|
135
|
+
) -> tuple[list[dict], list[dict]]:
|
|
136
|
+
"""Deduplicate near-identical entities in a knowledge graph.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
nodes: list of node dicts with at minimum {"id": str, "label": str}
|
|
140
|
+
edges: list of edge dicts with {"source": str, "target": str, ...}
|
|
141
|
+
communities: mapping of node_id -> community_id (from cluster())
|
|
142
|
+
dedup_llm_backend: if set, use LLM to resolve ambiguous pairs
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
(deduped_nodes, deduped_edges) with edges rewired to survivors
|
|
146
|
+
"""
|
|
147
|
+
# Guard: cross-project dedup is not supported — nodes from different repos
|
|
148
|
+
# share label names by coincidence and must never be merged by string similarity.
|
|
149
|
+
# If you need to dedup a global graph, run deduplicate_entities per-repo first.
|
|
150
|
+
repos_seen = {n.get("repo") for n in nodes if n.get("repo")}
|
|
151
|
+
if len(repos_seen) > 1:
|
|
152
|
+
raise ValueError(
|
|
153
|
+
f"deduplicate_entities: nodes span multiple repos {sorted(repos_seen)!r}. "
|
|
154
|
+
f"Cross-project dedup is disabled — run dedup per-repo before merging."
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if len(nodes) <= 1:
|
|
158
|
+
return nodes, edges
|
|
159
|
+
|
|
160
|
+
# Pre-deduplicate: keep first occurrence of each id
|
|
161
|
+
seen_ids: dict[str, dict] = {}
|
|
162
|
+
for node in nodes:
|
|
163
|
+
nid = node.get("id", "")
|
|
164
|
+
if nid and nid not in seen_ids:
|
|
165
|
+
seen_ids[nid] = node
|
|
166
|
+
unique_nodes = list(seen_ids.values())
|
|
167
|
+
|
|
168
|
+
if len(unique_nodes) <= 1:
|
|
169
|
+
return unique_nodes, edges
|
|
170
|
+
|
|
171
|
+
# ── pass 1: exact normalization ───────────────────────────────────────────
|
|
172
|
+
norm_to_nodes: dict[str, list[dict]] = defaultdict(list)
|
|
173
|
+
for node in unique_nodes:
|
|
174
|
+
key = _norm(node.get("label", node.get("id", "")))
|
|
175
|
+
if key:
|
|
176
|
+
norm_to_nodes[key].append(node)
|
|
177
|
+
|
|
178
|
+
uf = _UF()
|
|
179
|
+
exact_merges = 0
|
|
180
|
+
for key, group in norm_to_nodes.items():
|
|
181
|
+
if len(group) <= 1:
|
|
182
|
+
continue
|
|
183
|
+
# Partition by source_file — only merge within the same file in Pass 1.
|
|
184
|
+
# Cross-file matches fall through to Pass 2 fuzzy matching.
|
|
185
|
+
by_file: dict[str, list[dict]] = defaultdict(list)
|
|
186
|
+
for node in group:
|
|
187
|
+
sf = node.get("source_file") or ""
|
|
188
|
+
by_file[sf].append(node)
|
|
189
|
+
for sf, file_group in by_file.items():
|
|
190
|
+
if not sf:
|
|
191
|
+
# No source_file — cannot prove same symbol; skip to avoid
|
|
192
|
+
# collapsing distinct nodes that happen to share a label (#1178).
|
|
193
|
+
continue
|
|
194
|
+
if len(file_group) > 1:
|
|
195
|
+
winner = _pick_winner(file_group)
|
|
196
|
+
for node in file_group:
|
|
197
|
+
uf.union(winner["id"], node["id"])
|
|
198
|
+
exact_merges += len(file_group) - 1
|
|
199
|
+
|
|
200
|
+
# ── pass 2: MinHash/LSH + Jaro-Winkler (high-entropy nodes only) ─────────
|
|
201
|
+
candidates: list[dict] = []
|
|
202
|
+
seen_norms: set[str] = set()
|
|
203
|
+
for node in unique_nodes:
|
|
204
|
+
key = _norm(node.get("label", node.get("id", "")))
|
|
205
|
+
if key and key not in seen_norms:
|
|
206
|
+
seen_norms.add(key)
|
|
207
|
+
if _entropy(node.get("label", "")) >= _ENTROPY_THRESHOLD:
|
|
208
|
+
candidates.append(node)
|
|
209
|
+
|
|
210
|
+
fuzzy_merges = 0
|
|
211
|
+
if len(candidates) >= 2:
|
|
212
|
+
lsh = MinHashLSH(threshold=_LSH_THRESHOLD, num_perm=_NUM_PERM)
|
|
213
|
+
minhashes: dict[str, MinHash] = {}
|
|
214
|
+
|
|
215
|
+
for node in candidates:
|
|
216
|
+
norm_label = _norm(node.get("label", node.get("id", "")))
|
|
217
|
+
m = _make_minhash(norm_label)
|
|
218
|
+
minhashes[node["id"]] = m
|
|
219
|
+
try:
|
|
220
|
+
lsh.insert(node["id"], m)
|
|
221
|
+
except ValueError:
|
|
222
|
+
pass # duplicate key in LSH — already inserted
|
|
223
|
+
|
|
224
|
+
for node in candidates:
|
|
225
|
+
node_id = node["id"]
|
|
226
|
+
norm_label = _norm(node.get("label", node.get("id", "")))
|
|
227
|
+
neighbors = lsh.query(minhashes[node_id])
|
|
228
|
+
|
|
229
|
+
for neighbor_id in neighbors:
|
|
230
|
+
if neighbor_id == node_id:
|
|
231
|
+
continue
|
|
232
|
+
if uf.find(node_id) == uf.find(neighbor_id):
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
neighbor = next((n for n in candidates if n["id"] == neighbor_id), None)
|
|
236
|
+
if neighbor is None:
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
neighbor_norm = _norm(neighbor.get("label", neighbor.get("id", "")))
|
|
240
|
+
score = JaroWinkler.normalized_similarity(norm_label, neighbor_norm) * 100
|
|
241
|
+
|
|
242
|
+
if _is_variant_pair(norm_label, neighbor_norm):
|
|
243
|
+
continue
|
|
244
|
+
if _short_label_blocked(norm_label, neighbor_norm, score):
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
c1 = communities.get(node_id)
|
|
248
|
+
c2 = communities.get(neighbor_id)
|
|
249
|
+
if (c1 is not None and c2 is not None and c1 == c2
|
|
250
|
+
and min(len(norm_label), len(neighbor_norm)) >= 12):
|
|
251
|
+
score += _COMMUNITY_BOOST
|
|
252
|
+
|
|
253
|
+
if score >= _MERGE_THRESHOLD:
|
|
254
|
+
# Identical labels across different source files almost always
|
|
255
|
+
# means same-named-but-different symbols (trait impls, wrapper
|
|
256
|
+
# methods, common type names). Mirror Pass 1's source_file
|
|
257
|
+
# partition for this sub-case. (#1046, leaks #895's fix)
|
|
258
|
+
if norm_label == neighbor_norm:
|
|
259
|
+
sf_a = node.get("source_file") or ""
|
|
260
|
+
sf_b = neighbor.get("source_file") or ""
|
|
261
|
+
if sf_a != sf_b:
|
|
262
|
+
continue
|
|
263
|
+
all_group = norm_to_nodes.get(norm_label, [node]) + \
|
|
264
|
+
norm_to_nodes.get(neighbor_norm, [neighbor])
|
|
265
|
+
winner = _pick_winner(all_group)
|
|
266
|
+
uf.union(winner["id"], node_id)
|
|
267
|
+
uf.union(winner["id"], neighbor_id)
|
|
268
|
+
fuzzy_merges += 1
|
|
269
|
+
|
|
270
|
+
# ── pass 3: LLM tiebreaker for ambiguous pairs (opt-in) ──────────────────
|
|
271
|
+
if dedup_llm_backend is not None:
|
|
272
|
+
_llm_tiebreak(candidates, uf, communities, backend=dedup_llm_backend)
|
|
273
|
+
|
|
274
|
+
# ── build remap table from union-find components ──────────────────────────
|
|
275
|
+
components = uf.components()
|
|
276
|
+
remap: dict[str, str] = {}
|
|
277
|
+
|
|
278
|
+
for root, members in components.items():
|
|
279
|
+
if len(members) == 1:
|
|
280
|
+
continue
|
|
281
|
+
group_nodes = [n for n in unique_nodes if n["id"] in members]
|
|
282
|
+
winner = _pick_winner(group_nodes) if group_nodes else {"id": root}
|
|
283
|
+
winner_id = winner["id"]
|
|
284
|
+
for member in members:
|
|
285
|
+
if member != winner_id:
|
|
286
|
+
remap[member] = winner_id
|
|
287
|
+
|
|
288
|
+
# ── apply remap ───────────────────────────────────────────────────────────
|
|
289
|
+
if not remap:
|
|
290
|
+
return unique_nodes, edges
|
|
291
|
+
|
|
292
|
+
total = len(remap)
|
|
293
|
+
msg = f"[graphify] Deduplicated {total} node(s)"
|
|
294
|
+
if exact_merges:
|
|
295
|
+
msg += f" ({exact_merges} exact"
|
|
296
|
+
if fuzzy_merges:
|
|
297
|
+
msg += f", {fuzzy_merges} fuzzy"
|
|
298
|
+
msg += ")"
|
|
299
|
+
print(msg + ".", flush=True)
|
|
300
|
+
|
|
301
|
+
deduped_nodes = [n for n in unique_nodes if n["id"] not in remap]
|
|
302
|
+
deduped_edges = []
|
|
303
|
+
for edge in edges:
|
|
304
|
+
e = dict(edge)
|
|
305
|
+
# Tolerate "from"/"to" keys from LLM backends that don't follow the
|
|
306
|
+
# schema exactly — build_from_json normalises later but dedup runs
|
|
307
|
+
# first so bracket access would KeyError here (#803).
|
|
308
|
+
# Use explicit key presence check (not `or`) so empty-string src/tgt
|
|
309
|
+
# aren't silently replaced by the fallback key.
|
|
310
|
+
src = e["source"] if "source" in e else e.get("from")
|
|
311
|
+
tgt = e["target"] if "target" in e else e.get("to")
|
|
312
|
+
if src is None or tgt is None:
|
|
313
|
+
continue
|
|
314
|
+
e["source"] = remap.get(src, src)
|
|
315
|
+
e["target"] = remap.get(tgt, tgt)
|
|
316
|
+
# Remove legacy keys so they don't leak into edge attrs in graph.json.
|
|
317
|
+
e.pop("from", None)
|
|
318
|
+
e.pop("to", None)
|
|
319
|
+
if e["source"] != e["target"]:
|
|
320
|
+
deduped_edges.append(e)
|
|
321
|
+
|
|
322
|
+
return deduped_nodes, deduped_edges
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _pick_winner(nodes: list[dict]) -> dict:
|
|
326
|
+
"""Pick the canonical survivor: prefer no chunk suffix, then shorter ID."""
|
|
327
|
+
if not nodes:
|
|
328
|
+
raise ValueError("Cannot pick winner from empty list")
|
|
329
|
+
|
|
330
|
+
def _score(n: dict) -> tuple[int, int]:
|
|
331
|
+
has_suffix = bool(_CHUNK_SUFFIX.search(n["id"]))
|
|
332
|
+
return (1 if has_suffix else 0, len(n["id"]))
|
|
333
|
+
|
|
334
|
+
return min(nodes, key=_score)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _llm_tiebreak(
|
|
338
|
+
candidates: list[dict],
|
|
339
|
+
uf: _UF,
|
|
340
|
+
communities: dict[str, int],
|
|
341
|
+
*,
|
|
342
|
+
backend: str,
|
|
343
|
+
batch_size: int = 30,
|
|
344
|
+
low: float = 75.0,
|
|
345
|
+
high: float = 92.0,
|
|
346
|
+
) -> None:
|
|
347
|
+
"""Batch-resolve ambiguous pairs (score in [low, high)) via LLM."""
|
|
348
|
+
try:
|
|
349
|
+
from graphify.llm import BACKENDS, _format_backend_env_keys, _get_backend_api_key
|
|
350
|
+
if backend not in BACKENDS:
|
|
351
|
+
print(f"[graphify] --dedup-llm: unknown backend {backend!r}, skipping LLM tiebreaker.", flush=True)
|
|
352
|
+
return
|
|
353
|
+
if not _get_backend_api_key(backend):
|
|
354
|
+
env_keys = _format_backend_env_keys(backend)
|
|
355
|
+
print(f"[graphify] --dedup-llm: {env_keys} not set, skipping LLM tiebreaker.", flush=True)
|
|
356
|
+
return
|
|
357
|
+
except ImportError:
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
ambiguous: list[tuple[dict, dict, float]] = []
|
|
361
|
+
for i, node in enumerate(candidates):
|
|
362
|
+
norm_i = _norm(node.get("label", node.get("id", "")))
|
|
363
|
+
for j in range(i + 1, len(candidates)):
|
|
364
|
+
neighbor = candidates[j]
|
|
365
|
+
if uf.find(node["id"]) == uf.find(neighbor["id"]):
|
|
366
|
+
continue
|
|
367
|
+
norm_j = _norm(neighbor.get("label", neighbor.get("id", "")))
|
|
368
|
+
score = JaroWinkler.normalized_similarity(norm_i, norm_j) * 100
|
|
369
|
+
if _is_variant_pair(norm_i, norm_j):
|
|
370
|
+
continue
|
|
371
|
+
if _short_label_blocked(norm_i, norm_j, score):
|
|
372
|
+
continue
|
|
373
|
+
c1 = communities.get(node["id"])
|
|
374
|
+
c2 = communities.get(neighbor["id"])
|
|
375
|
+
if (c1 is not None and c2 is not None and c1 == c2
|
|
376
|
+
and min(len(norm_i), len(norm_j)) >= 12):
|
|
377
|
+
score += _COMMUNITY_BOOST
|
|
378
|
+
if low <= score < high:
|
|
379
|
+
ambiguous.append((node, neighbor, score))
|
|
380
|
+
|
|
381
|
+
if not ambiguous:
|
|
382
|
+
return
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
from graphify.llm import _call_llm
|
|
386
|
+
except ImportError as exc:
|
|
387
|
+
# F-038: previously this silent fallback hid the fact that `_call_llm`
|
|
388
|
+
# didn't exist in `graphify.llm` at all, so `--dedup-llm` was a no-op.
|
|
389
|
+
# Surface the import failure so future regressions are visible.
|
|
390
|
+
print(
|
|
391
|
+
f"[graphify] --dedup-llm: cannot import _call_llm ({exc}); skipping LLM tiebreaker.",
|
|
392
|
+
flush=True,
|
|
393
|
+
)
|
|
394
|
+
return
|
|
395
|
+
|
|
396
|
+
for batch_start in range(0, len(ambiguous), batch_size):
|
|
397
|
+
batch = ambiguous[batch_start : batch_start + batch_size]
|
|
398
|
+
pairs_text = "\n".join(
|
|
399
|
+
f"{i+1}. \"{a['label']}\" vs \"{b['label']}\""
|
|
400
|
+
for i, (a, b, _) in enumerate(batch)
|
|
401
|
+
)
|
|
402
|
+
prompt = (
|
|
403
|
+
"For each pair below, answer only 'yes' or 'no': are they the same real-world concept?\n\n"
|
|
404
|
+
f"{pairs_text}\n\n"
|
|
405
|
+
"Reply with one line per pair: '1. yes', '2. no', etc."
|
|
406
|
+
)
|
|
407
|
+
try:
|
|
408
|
+
response = _call_llm(prompt, backend=backend, max_tokens=200)
|
|
409
|
+
lines = response.strip().splitlines()
|
|
410
|
+
for line in lines:
|
|
411
|
+
line = line.strip()
|
|
412
|
+
if not line:
|
|
413
|
+
continue
|
|
414
|
+
parts = line.split(".", 1)
|
|
415
|
+
if len(parts) != 2:
|
|
416
|
+
continue
|
|
417
|
+
try:
|
|
418
|
+
idx = int(parts[0].strip()) - 1
|
|
419
|
+
except ValueError:
|
|
420
|
+
continue
|
|
421
|
+
if 0 <= idx < len(batch):
|
|
422
|
+
answer = parts[1].strip().lower()
|
|
423
|
+
if answer.startswith("yes"):
|
|
424
|
+
a, b, _ = batch[idx]
|
|
425
|
+
winner = _pick_winner([a, b])
|
|
426
|
+
uf.union(winner["id"], a["id"])
|
|
427
|
+
uf.union(winner["id"], b["id"])
|
|
428
|
+
except Exception as exc:
|
|
429
|
+
print(f"[graphify] --dedup-llm batch failed: {exc}", flush=True)
|