@tyroneross/build-loop 0.30.3
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/.agents/plugins/marketplace.json +10 -0
- package/.claude-plugin/marketplace.json +38 -0
- package/.claude-plugin/plugin.json +30 -0
- package/.codex-plugin/plugin.json +41 -0
- package/.cursor/rules/build-loop-surface.mdc +22 -0
- package/AGENTS.md +498 -0
- package/LICENSE +202 -0
- package/README.md +403 -0
- package/agents/alignment-checker.md +124 -0
- package/agents/api-assessor.md +136 -0
- package/agents/architecture-scout.md +185 -0
- package/agents/assessment-orchestrator.md +163 -0
- package/agents/build-orchestrator.md +198 -0
- package/agents/database-assessor.md +133 -0
- package/agents/design-contract-specialist.md +239 -0
- package/agents/fact-checker.md +69 -0
- package/agents/fix-critique.md +161 -0
- package/agents/frontend-assessor.md +136 -0
- package/agents/implementer.md +185 -0
- package/agents/independent-auditor.md +143 -0
- package/agents/mock-scanner.md +78 -0
- package/agents/optimize-runner.md +112 -0
- package/agents/overfitting-reviewer.md +96 -0
- package/agents/performance-assessor.md +159 -0
- package/agents/plan-critic.md +85 -0
- package/agents/promotion-reviewer.md +121 -0
- package/agents/recurring-pattern-detector.md +248 -0
- package/agents/retrospective-synthesizer.md +109 -0
- package/agents/root-cause-investigator.md +285 -0
- package/agents/scope-auditor.md +121 -0
- package/agents/security-reviewer.md +151 -0
- package/agents/self-improvement-architect.md +125 -0
- package/agents/synthesis-critic.md +99 -0
- package/agents/transcript-pattern-miner.md +165 -0
- package/agents/ui-validator.md +193 -0
- package/codex-skills/build-loop/SKILL.md +20 -0
- package/codex-skills/debug-loop/SKILL.md +18 -0
- package/codex-skills/handoff/SKILL.md +22 -0
- package/codex-skills/knowledge/SKILL.md +21 -0
- package/codex-skills/optimize/SKILL.md +18 -0
- package/codex-skills/research/SKILL.md +18 -0
- package/commands/agent-rally-point.md +301 -0
- package/commands/assess.md +41 -0
- package/commands/debug.md +20 -0
- package/commands/debugger-detail.md +34 -0
- package/commands/debugger-scan.md +15 -0
- package/commands/debugger-status.md +20 -0
- package/commands/debugger.md +32 -0
- package/commands/handoff.md +113 -0
- package/commands/knowledge-review.md +16 -0
- package/commands/optimize.md +31 -0
- package/commands/plan-verify.md +39 -0
- package/commands/promote-experiment.md +129 -0
- package/commands/research.md +22 -0
- package/commands/run.md +12 -0
- package/commands/self-review.md +51 -0
- package/commands/start-prd.md +57 -0
- package/commands/test.md +34 -0
- package/dist/src/assessment-orchestrator.d.ts +78 -0
- package/dist/src/assessment-orchestrator.d.ts.map +1 -0
- package/dist/src/assessment-orchestrator.js +304 -0
- package/dist/src/assessment-orchestrator.js.map +1 -0
- package/dist/src/audit-miner.d.ts +21 -0
- package/dist/src/audit-miner.d.ts.map +1 -0
- package/dist/src/audit-miner.js +421 -0
- package/dist/src/audit-miner.js.map +1 -0
- package/dist/src/batch-operations.d.ts +30 -0
- package/dist/src/batch-operations.d.ts.map +1 -0
- package/dist/src/batch-operations.js +339 -0
- package/dist/src/batch-operations.js.map +1 -0
- package/dist/src/config.d.ts +83 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +176 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/context-engine.d.ts +45 -0
- package/dist/src/context-engine.d.ts.map +1 -0
- package/dist/src/context-engine.js +135 -0
- package/dist/src/context-engine.js.map +1 -0
- package/dist/src/debug-wrapper.d.ts +58 -0
- package/dist/src/debug-wrapper.d.ts.map +1 -0
- package/dist/src/debug-wrapper.js +285 -0
- package/dist/src/debug-wrapper.js.map +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +149 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interactive-verifier.d.ts +26 -0
- package/dist/src/interactive-verifier.d.ts.map +1 -0
- package/dist/src/interactive-verifier.js +435 -0
- package/dist/src/interactive-verifier.js.map +1 -0
- package/dist/src/lessons.d.ts +59 -0
- package/dist/src/lessons.d.ts.map +1 -0
- package/dist/src/lessons.js +172 -0
- package/dist/src/lessons.js.map +1 -0
- package/dist/src/log-reader.d.ts +39 -0
- package/dist/src/log-reader.d.ts.map +1 -0
- package/dist/src/log-reader.js +291 -0
- package/dist/src/log-reader.js.map +1 -0
- package/dist/src/logger.d.ts +59 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +328 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/parallel-retrieval.d.ts +52 -0
- package/dist/src/parallel-retrieval.d.ts.map +1 -0
- package/dist/src/parallel-retrieval.js +309 -0
- package/dist/src/parallel-retrieval.js.map +1 -0
- package/dist/src/pattern-extractor.d.ts +30 -0
- package/dist/src/pattern-extractor.d.ts.map +1 -0
- package/dist/src/pattern-extractor.js +386 -0
- package/dist/src/pattern-extractor.js.map +1 -0
- package/dist/src/result-aggregator.d.ts +44 -0
- package/dist/src/result-aggregator.d.ts.map +1 -0
- package/dist/src/result-aggregator.js +315 -0
- package/dist/src/result-aggregator.js.map +1 -0
- package/dist/src/retrieval.d.ts +139 -0
- package/dist/src/retrieval.d.ts.map +1 -0
- package/dist/src/retrieval.js +724 -0
- package/dist/src/retrieval.js.map +1 -0
- package/dist/src/setup/auto-setup.d.ts +15 -0
- package/dist/src/setup/auto-setup.d.ts.map +1 -0
- package/dist/src/setup/auto-setup.js +157 -0
- package/dist/src/setup/auto-setup.js.map +1 -0
- package/dist/src/setup/configure-hooks.d.ts +8 -0
- package/dist/src/setup/configure-hooks.d.ts.map +1 -0
- package/dist/src/setup/configure-hooks.js +16 -0
- package/dist/src/setup/configure-hooks.js.map +1 -0
- package/dist/src/setup/create-slash-commands.d.ts +8 -0
- package/dist/src/setup/create-slash-commands.d.ts.map +1 -0
- package/dist/src/setup/create-slash-commands.js +101 -0
- package/dist/src/setup/create-slash-commands.js.map +1 -0
- package/dist/src/setup/index.d.ts +8 -0
- package/dist/src/setup/index.d.ts.map +1 -0
- package/dist/src/setup/index.js +17 -0
- package/dist/src/setup/index.js.map +1 -0
- package/dist/src/setup/inject-claude-md.d.ts +10 -0
- package/dist/src/setup/inject-claude-md.d.ts.map +1 -0
- package/dist/src/setup/inject-claude-md.js +150 -0
- package/dist/src/setup/inject-claude-md.js.map +1 -0
- package/dist/src/setup/uninstall.d.ts +22 -0
- package/dist/src/setup/uninstall.d.ts.map +1 -0
- package/dist/src/setup/uninstall.js +235 -0
- package/dist/src/setup/uninstall.js.map +1 -0
- package/dist/src/storage.d.ts +192 -0
- package/dist/src/storage.d.ts.map +1 -0
- package/dist/src/storage.js +1037 -0
- package/dist/src/storage.js.map +1 -0
- package/dist/src/string-similarity.d.ts +8 -0
- package/dist/src/string-similarity.d.ts.map +1 -0
- package/dist/src/string-similarity.js +57 -0
- package/dist/src/string-similarity.js.map +1 -0
- package/dist/src/traces/adapters/browser.d.ts +69 -0
- package/dist/src/traces/adapters/browser.d.ts.map +1 -0
- package/dist/src/traces/adapters/browser.js +280 -0
- package/dist/src/traces/adapters/browser.js.map +1 -0
- package/dist/src/traces/adapters/index.d.ts +23 -0
- package/dist/src/traces/adapters/index.d.ts.map +1 -0
- package/dist/src/traces/adapters/index.js +67 -0
- package/dist/src/traces/adapters/index.js.map +1 -0
- package/dist/src/traces/adapters/langchain.d.ts +42 -0
- package/dist/src/traces/adapters/langchain.d.ts.map +1 -0
- package/dist/src/traces/adapters/langchain.js +151 -0
- package/dist/src/traces/adapters/langchain.js.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts +46 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.js +164 -0
- package/dist/src/traces/adapters/opentelemetry.js.map +1 -0
- package/dist/src/traces/adapters/sentry.d.ts +58 -0
- package/dist/src/traces/adapters/sentry.d.ts.map +1 -0
- package/dist/src/traces/adapters/sentry.js +212 -0
- package/dist/src/traces/adapters/sentry.js.map +1 -0
- package/dist/src/traces/index.d.ts +10 -0
- package/dist/src/traces/index.d.ts.map +1 -0
- package/dist/src/traces/index.js +45 -0
- package/dist/src/traces/index.js.map +1 -0
- package/dist/src/traces/storage.d.ts +78 -0
- package/dist/src/traces/storage.d.ts.map +1 -0
- package/dist/src/traces/storage.js +399 -0
- package/dist/src/traces/storage.js.map +1 -0
- package/dist/src/traces/summarizer.d.ts +83 -0
- package/dist/src/traces/summarizer.d.ts.map +1 -0
- package/dist/src/traces/summarizer.js +247 -0
- package/dist/src/traces/summarizer.js.map +1 -0
- package/dist/src/traces/types.d.ts +365 -0
- package/dist/src/traces/types.d.ts.map +1 -0
- package/dist/src/traces/types.js +14 -0
- package/dist/src/traces/types.js.map +1 -0
- package/dist/src/types.d.ts +495 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +27 -0
- package/dist/src/types.js.map +1 -0
- package/docs/agent-surface-policy.md +45 -0
- package/docs/memory-setup.md +226 -0
- package/hooks/_arch_scan_bg.py +88 -0
- package/hooks/_plugin_drift_check_bg.py +85 -0
- package/hooks/_session_start_lib.sh +42 -0
- package/hooks/git/pre-push +141 -0
- package/hooks/hooks.json +126 -0
- package/hooks/post-commit +30 -0
- package/hooks/pre-commit +29 -0
- package/hooks/pre-edit-apppulse.sh +6 -0
- package/hooks/pre-edit-architecture.sh +106 -0
- package/hooks/pre-edit-rally-point.sh +92 -0
- package/hooks/session-start-apppulse.sh +6 -0
- package/hooks/session-start-architecture.sh +10 -0
- package/hooks/session-start-git-hooks.sh +12 -0
- package/hooks/session-start-plugin-drift.sh +27 -0
- package/hooks/session-start-rally-point.sh +44 -0
- package/hooks/session-start-retrieval.sh +24 -0
- package/hooks/session-start-worktree-gc.sh +105 -0
- package/hooks/test_apppulse_hooks.sh +6 -0
- package/hooks/test_rally_point_hooks.sh +388 -0
- package/hooks/test_sec008_hooks.sh +66 -0
- package/package.json +111 -0
- package/scripts/README.md +301 -0
- package/scripts/_db_url.py +61 -0
- package/scripts/_paths.py +445 -0
- package/scripts/blm.py +162 -0
- package/scripts/blm_api.py +263 -0
- package/scripts/check_cache_sync.py +490 -0
- package/scripts/embed_backend.py +475 -0
- package/scripts/install_memory.py +556 -0
- package/scripts/lessons_index/__init__.py +77 -0
- package/scripts/lessons_index/__main__.py +135 -0
- package/scripts/lessons_index/ingest.py +288 -0
- package/scripts/lessons_index/query.py +285 -0
- package/scripts/lessons_index/schema.py +114 -0
- package/scripts/memory_context/__init__.py +610 -0
- package/scripts/memory_graph/__init__.py +393 -0
- package/scripts/project_resolver.py +172 -0
- package/scripts/prune_codex_plugin_cache.py +23 -0
- package/scripts/prune_plugin_cache.py +359 -0
- package/scripts/semantic_index/__init__.py +482 -0
- package/scripts/semantic_index/_bench_hybrid.py +175 -0
- package/scripts/semantic_index/backfill.py +263 -0
- package/scripts/semantic_index/hybrid.py +174 -0
- package/scripts/semantic_index/test_backfill.py +292 -0
- package/scripts/semantic_index/test_hybrid.py +541 -0
- package/scripts/sync_navgator_lessons.py +666 -0
- package/scripts/sync_plugin_cache.py +388 -0
- package/skills/agent-rally-point/SKILL.md +165 -0
- package/skills/agent-rally-watcher/SKILL.md +83 -0
- package/skills/api-registry-bridge/SKILL.md +59 -0
- package/skills/architecture/dead/SKILL.md +56 -0
- package/skills/architecture/impact/SKILL.md +88 -0
- package/skills/architecture/review/SKILL.md +227 -0
- package/skills/architecture/rules/SKILL.md +72 -0
- package/skills/architecture/scan/SKILL.md +79 -0
- package/skills/architecture/trace/SKILL.md +65 -0
- package/skills/attribution-standard/SKILL.md +122 -0
- package/skills/authentication/SKILL.md +81 -0
- package/skills/authentication/references/better-auth-magic-link.md +135 -0
- package/skills/authentication/references/better-auth-setup.md +224 -0
- package/skills/authentication/references/google-calendar-sync.md +278 -0
- package/skills/authentication/references/google-cloud-console.md +159 -0
- package/skills/authentication/references/google-geocoding-directions.md +126 -0
- package/skills/authentication/references/google-lessons-example-web-app.md +177 -0
- package/skills/authentication/references/google-maps.md +162 -0
- package/skills/authentication/references/google-oauth-setup.md +343 -0
- package/skills/authentication/references/google-places.md +173 -0
- package/skills/authentication/references/lessons-example-web-app-better-auth.md +126 -0
- package/skills/authentication/references/resend-email.md +227 -0
- package/skills/authentication/references/resend-otp-magic-link.md +153 -0
- package/skills/authentication/references/supabase-auth.md +172 -0
- package/skills/auto-decision-capture/SKILL.md +419 -0
- package/skills/build-loop/SKILL.md +516 -0
- package/skills/build-loop/detect-plugins.mjs +82 -0
- package/skills/build-loop/eval-guide.md +65 -0
- package/skills/build-loop/fallbacks.md +549 -0
- package/skills/build-loop/phases/fact-check.md +42 -0
- package/skills/build-loop/phases/ui-validation.md +267 -0
- package/skills/build-loop/references/apple-native-planning.md +439 -0
- package/skills/build-loop/references/capability-routing.md +181 -0
- package/skills/build-loop/references/codex-subagents.md +98 -0
- package/skills/build-loop/references/coordination.md +161 -0
- package/skills/build-loop/references/correction-aware-capture.md +177 -0
- package/skills/build-loop/references/independent-auditor.md +72 -0
- package/skills/build-loop/references/intent-capability-pack.md +170 -0
- package/skills/build-loop/references/intent-exploration-prompts.md +96 -0
- package/skills/build-loop/references/leadership.md +72 -0
- package/skills/build-loop/references/memory.md +284 -0
- package/skills/build-loop/references/modular-systems-pack.md +96 -0
- package/skills/build-loop/references/output-style.md +129 -0
- package/skills/build-loop/references/pay-it-forward-arch.md +98 -0
- package/skills/build-loop/references/phase-1-assess.md +218 -0
- package/skills/build-loop/references/phase-2-plan.md +77 -0
- package/skills/build-loop/references/phase-3-execute.md +47 -0
- package/skills/build-loop/references/phase-4-review.md +310 -0
- package/skills/build-loop/references/phase-5-iterate.md +71 -0
- package/skills/build-loop/references/phase-6-learn.md +56 -0
- package/skills/build-loop/references/recent-design-structures.md +274 -0
- package/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
- package/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
- package/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
- package/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
- package/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
- package/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
- package/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
- package/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
- package/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
- package/skills/build-loop/references/self-recursive-dev.md +77 -0
- package/skills/build-loop/references/self-review.md +232 -0
- package/skills/build-loop/references/ui-io-contract.md +116 -0
- package/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
- package/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
- package/skills/build-loop/templates/backlog-item.md +35 -0
- package/skills/build-loop/templates/codex-worker-prompt.md +89 -0
- package/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
- package/skills/build-loop/templates/ux-fix-plan.md +40 -0
- package/skills/building-with-deepagents/SKILL.md +195 -0
- package/skills/building-with-deepagents/references/anti-patterns.md +141 -0
- package/skills/building-with-deepagents/references/api-quick-reference.md +184 -0
- package/skills/capabilities/SKILL.md +91 -0
- package/skills/debug-loop/SKILL.md +278 -0
- package/skills/debug-loop/references/convergence-rules.md +145 -0
- package/skills/debug-loop/references/swiftui-macos-clunky-ui-debugger.html +823 -0
- package/skills/debugging/assess/SKILL.md +111 -0
- package/skills/debugging/debug-loop/SKILL.md +231 -0
- package/skills/debugging/memory/SKILL.md +147 -0
- package/skills/debugging/store/SKILL.md +153 -0
- package/skills/debugging-memory/SKILL.md +440 -0
- package/skills/debugging-memory/examples/incident-example.json +67 -0
- package/skills/debugging-memory/references/incident-documentation.md +280 -0
- package/skills/debugging-memory/references/ios-notification-alarm-playbook.md +300 -0
- package/skills/debugging-memory/references/pattern-extraction.md +163 -0
- package/skills/debugging-memory/references/subagent-integration.md +312 -0
- package/skills/defenseclaw-bridge/SKILL.md +156 -0
- package/skills/defenseclaw-bridge/references/dc-config-mapping.md +162 -0
- package/skills/defenseclaw-bridge/references/output-format.md +108 -0
- package/skills/handoff/SKILL.md +94 -0
- package/skills/ibr-bridge/SKILL.md +68 -0
- package/skills/knowledge/SKILL.md +127 -0
- package/skills/knowledge/references/recall-integration.md +83 -0
- package/skills/knowledge/templates/madr-minimal.md +56 -0
- package/skills/knowledge-review/SKILL.md +112 -0
- package/skills/logging-tracer/SKILL.md +202 -0
- package/skills/logging-tracer/references/log-analysis.md +128 -0
- package/skills/logging-tracer/references/stack-templates.md +274 -0
- package/skills/logging-tracer-bridge/SKILL.md +70 -0
- package/skills/mcp-builder/SKILL.md +278 -0
- package/skills/mcp-builder/references/mcp-security.md +106 -0
- package/skills/model-tiering/SKILL.md +216 -0
- package/skills/native-ax-driver/SKILL.md +220 -0
- package/skills/native-ax-driver/scripts/layout_fill.py +222 -0
- package/skills/native-ax-driver/scripts/native_driver.py +495 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Package.swift +20 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Sources/main.swift +542 -0
- package/skills/optimize/SKILL.md +202 -0
- package/skills/optimize/profiles.md +61 -0
- package/skills/plan-verify/SKILL.md +115 -0
- package/skills/plan-verify/test-fixtures/example-app-v20-findings.json +27 -0
- package/skills/plan-verify/test-fixtures/example-app-v20.md +42 -0
- package/skills/plan-verify/test-fixtures/example-app-v22-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/example-app-v22.md +1608 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan.md +55 -0
- package/skills/plugin-builder/SKILL.md +329 -0
- package/skills/plugin-builder/references/authoritative-sources.md +104 -0
- package/skills/plugin-builder/references/build-loop-phase-guidance.md +94 -0
- package/skills/plugin-builder/references/components-guide.md +221 -0
- package/skills/plugin-builder/references/distribution.md +317 -0
- package/skills/plugin-builder/references/dual-host-claude-codex.md +220 -0
- package/skills/plugin-builder/references/hooks-reference.md +210 -0
- package/skills/plugin-builder/references/manifest-schema.md +111 -0
- package/skills/plugin-builder/references/plugin-hygiene-lessons.md +307 -0
- package/skills/plugin-tests/SKILL.md +94 -0
- package/skills/prd-bridge/SKILL.md +135 -0
- package/skills/research/SKILL.md +80 -0
- package/skills/security-methodology/SKILL.md +105 -0
- package/skills/security-methodology/references/cross-source-matrix.md +77 -0
- package/skills/security-methodology/references/mitre-atlas-starter.md +83 -0
- package/skills/security-methodology/references/nist-600-1-mapping.md +119 -0
- package/skills/security-methodology/references/owasp-agentic-top-10.md +157 -0
- package/skills/security-methodology/references/owasp-llm-top-10.md +144 -0
- package/skills/security-methodology/references/owasp-web-top-10.md +65 -0
- package/skills/self-improve/SKILL.md +298 -0
- package/skills/spec-writing/SKILL.md +548 -0
- package/skills/spec-writing/scripts/check_checklist.py +478 -0
- package/skills/sync-skills/SKILL.md +132 -0
- package/skills/telemetry/SKILL.md +66 -0
- package/skills/ui-design/SKILL.md +75 -0
- package/skills/ui-design/references/design-patterns-multi.md +165 -0
- package/skills/ui-design/references/design-preferences-from-owned-apps.md +150 -0
- package/skills/ui-design/references/evidence-capture-policy.md +55 -0
- package/skills/ui-design/references/ui-guidance-sources.md +330 -0
- package/skills/ui-design/references/universal-design-principles.alt.md +182 -0
- package/skills/ui-design/references/universal-design-principles.md +419 -0
- package/templates/memory/MEMORY.md.template +55 -0
- package/templates/memory/constitution.md.template +64 -0
- package/templates/memory/manifest.json +47 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
"""Backfill embeddings for semantic_facts rows where ``embedding_json IS NULL``.
|
|
5
|
+
|
|
6
|
+
The P1 auto-embed-on-write change (``upsert_fact(auto_embed=True)``)
|
|
7
|
+
only covers rows written AFTER the upgrade. Pre-upgrade rows — and rows
|
|
8
|
+
written while the embed backend was down — sit with ``embedding_json
|
|
9
|
+
NULL`` and skip the cosine rerank in ``hybrid.rerank_candidates``.
|
|
10
|
+
|
|
11
|
+
This module embeds those NULL rows in place. Idempotent: a second run
|
|
12
|
+
finds zero NULL rows and is a no-op. Resumable: batched commits mean a
|
|
13
|
+
killed process loses at most one batch.
|
|
14
|
+
|
|
15
|
+
Public API:
|
|
16
|
+
backfill_embeddings(...) → BackfillResult library entry point
|
|
17
|
+
main(argv=None) → int CLI: report + exit code
|
|
18
|
+
|
|
19
|
+
Stdlib-only. Defers ``embed_backend.embed`` import so the script remains
|
|
20
|
+
importable in environments without MLX/Ollama (it will just report zero
|
|
21
|
+
work done and the underlying error).
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import json
|
|
27
|
+
import sqlite3
|
|
28
|
+
import sys
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Any, Callable, Optional
|
|
32
|
+
|
|
33
|
+
HERE = Path(__file__).resolve().parent
|
|
34
|
+
SCRIPTS = HERE.parent
|
|
35
|
+
if str(SCRIPTS) not in sys.path:
|
|
36
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
37
|
+
|
|
38
|
+
from semantic_index import ( # noqa: E402
|
|
39
|
+
_db_path,
|
|
40
|
+
_embed_text_for_fact,
|
|
41
|
+
connect,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class BackfillResult:
|
|
47
|
+
"""Outcome of a backfill run. Reportable without re-querying."""
|
|
48
|
+
|
|
49
|
+
db_path: str = ""
|
|
50
|
+
scanned: int = 0
|
|
51
|
+
embedded: int = 0
|
|
52
|
+
skipped_empty_text: int = 0
|
|
53
|
+
failed: int = 0
|
|
54
|
+
backend_unavailable: bool = False
|
|
55
|
+
error: Optional[str] = None
|
|
56
|
+
sample_failures: list[str] = field(default_factory=list)
|
|
57
|
+
|
|
58
|
+
def to_dict(self) -> dict[str, Any]:
|
|
59
|
+
return {
|
|
60
|
+
"db_path": self.db_path,
|
|
61
|
+
"scanned": self.scanned,
|
|
62
|
+
"embedded": self.embedded,
|
|
63
|
+
"skipped_empty_text": self.skipped_empty_text,
|
|
64
|
+
"failed": self.failed,
|
|
65
|
+
"backend_unavailable": self.backend_unavailable,
|
|
66
|
+
"error": self.error,
|
|
67
|
+
"sample_failures": self.sample_failures[:5],
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _resolve_default_embed_fn() -> Optional[Callable[[str], list[float]]]:
|
|
72
|
+
"""Resolve ``embed_backend.embed`` lazily. Returns None on import error.
|
|
73
|
+
|
|
74
|
+
Kept out of module-import so the backfill module is importable in
|
|
75
|
+
environments without optional MLX/Ollama dependencies — the CLI
|
|
76
|
+
surfaces backend-unavailable as a clean error, not a stack trace.
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
from embed_backend import embed as _embed # type: ignore # noqa: PLC0415
|
|
80
|
+
except Exception: # noqa: BLE001
|
|
81
|
+
return None
|
|
82
|
+
return _embed
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _iter_null_rows(
|
|
86
|
+
conn: sqlite3.Connection,
|
|
87
|
+
batch_size: int,
|
|
88
|
+
after_rowid: int = 0,
|
|
89
|
+
) -> list[tuple[int, str, str, str]]:
|
|
90
|
+
"""Fetch up to ``batch_size`` rows with NULL embedding_json AND
|
|
91
|
+
``rowid > after_rowid``.
|
|
92
|
+
|
|
93
|
+
The ``after_rowid`` cursor prevents the inner loop from re-fetching
|
|
94
|
+
a row that failed to embed (which would leave it NULL and re-pick it
|
|
95
|
+
on the next iteration — an infinite loop). The outer call passes 0
|
|
96
|
+
on first batch and advances to the highest rowid seen so far.
|
|
97
|
+
Successfully-embedded rows are no longer NULL, so they fall out of
|
|
98
|
+
the predicate naturally — no separate exclusion set needed.
|
|
99
|
+
|
|
100
|
+
Returns (rowid, subject, predicate, object) tuples — exactly the
|
|
101
|
+
fields ``_embed_text_for_fact`` consumes. Bounded by ``batch_size``
|
|
102
|
+
so memory and commit-window scale predictably with large DBs.
|
|
103
|
+
"""
|
|
104
|
+
cur = conn.execute(
|
|
105
|
+
"""
|
|
106
|
+
SELECT rowid, subject, predicate, object
|
|
107
|
+
FROM semantic_facts
|
|
108
|
+
WHERE embedding_json IS NULL AND rowid > ?
|
|
109
|
+
ORDER BY rowid ASC
|
|
110
|
+
LIMIT ?
|
|
111
|
+
""",
|
|
112
|
+
(after_rowid, batch_size),
|
|
113
|
+
)
|
|
114
|
+
return [
|
|
115
|
+
(int(r[0]), str(r[1] or ""), str(r[2] or ""), str(r[3] or ""))
|
|
116
|
+
for r in cur.fetchall()
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def backfill_embeddings(
|
|
121
|
+
*,
|
|
122
|
+
db_path: str | Path | None = None,
|
|
123
|
+
embed_fn: Optional[Callable[[str], list[float]]] = None,
|
|
124
|
+
batch_size: int = 50,
|
|
125
|
+
max_rows: Optional[int] = None,
|
|
126
|
+
) -> BackfillResult:
|
|
127
|
+
"""Embed every ``embedding_json IS NULL`` row in place.
|
|
128
|
+
|
|
129
|
+
Idempotency: rows with non-NULL ``embedding_json`` are never re-
|
|
130
|
+
embedded. Re-running on a fully-backfilled DB scans zero rows and
|
|
131
|
+
returns ``embedded=0`` — safe to schedule periodically.
|
|
132
|
+
|
|
133
|
+
Per-row failure isolation: when ``embed_fn`` raises on a single row,
|
|
134
|
+
that row stays NULL (counted in ``failed``) and the loop continues.
|
|
135
|
+
A row-level failure does not abort the whole backfill — the embed
|
|
136
|
+
backend may genuinely choke on one pathological text (e.g. all
|
|
137
|
+
whitespace) but be fine on every other row.
|
|
138
|
+
|
|
139
|
+
``max_rows``: cap total rows processed in this call. Useful for
|
|
140
|
+
incremental backfills under a time budget; the next call resumes.
|
|
141
|
+
"""
|
|
142
|
+
result = BackfillResult(db_path=str(_db_path(db_path)))
|
|
143
|
+
path = _db_path(db_path)
|
|
144
|
+
if not path.exists():
|
|
145
|
+
# Nothing to backfill — fresh installs hit this before the first
|
|
146
|
+
# write. Not an error; not a backend-unavailable state either.
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
if embed_fn is None:
|
|
150
|
+
embed_fn = _resolve_default_embed_fn()
|
|
151
|
+
if embed_fn is None:
|
|
152
|
+
result.backend_unavailable = True
|
|
153
|
+
result.error = "embed_backend import failed"
|
|
154
|
+
return result
|
|
155
|
+
|
|
156
|
+
conn = connect(path)
|
|
157
|
+
try:
|
|
158
|
+
rows_left = max_rows
|
|
159
|
+
cursor_rowid = 0 # advances past every row we touch (success or fail)
|
|
160
|
+
while True:
|
|
161
|
+
if rows_left is not None and rows_left <= 0:
|
|
162
|
+
break
|
|
163
|
+
limit = batch_size
|
|
164
|
+
if rows_left is not None:
|
|
165
|
+
limit = min(batch_size, rows_left)
|
|
166
|
+
batch = _iter_null_rows(conn, limit, after_rowid=cursor_rowid)
|
|
167
|
+
if not batch:
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
for rowid, subject, predicate, obj in batch:
|
|
171
|
+
cursor_rowid = max(cursor_rowid, rowid)
|
|
172
|
+
result.scanned += 1
|
|
173
|
+
if rows_left is not None:
|
|
174
|
+
rows_left -= 1
|
|
175
|
+
text = _embed_text_for_fact(subject, predicate, obj)
|
|
176
|
+
if not text:
|
|
177
|
+
result.skipped_empty_text += 1
|
|
178
|
+
continue
|
|
179
|
+
try:
|
|
180
|
+
vec = embed_fn(text)
|
|
181
|
+
except Exception as e: # noqa: BLE001 - per-row isolation
|
|
182
|
+
result.failed += 1
|
|
183
|
+
if len(result.sample_failures) < 5:
|
|
184
|
+
result.sample_failures.append(
|
|
185
|
+
f"rowid={rowid}: {type(e).__name__}: {e}"
|
|
186
|
+
)
|
|
187
|
+
continue
|
|
188
|
+
if not isinstance(vec, list) or not vec:
|
|
189
|
+
result.failed += 1
|
|
190
|
+
if len(result.sample_failures) < 5:
|
|
191
|
+
result.sample_failures.append(
|
|
192
|
+
f"rowid={rowid}: embed returned {type(vec).__name__}"
|
|
193
|
+
)
|
|
194
|
+
continue
|
|
195
|
+
try:
|
|
196
|
+
payload = json.dumps(
|
|
197
|
+
[float(x) for x in vec], ensure_ascii=False, sort_keys=True
|
|
198
|
+
)
|
|
199
|
+
except (TypeError, ValueError) as e:
|
|
200
|
+
result.failed += 1
|
|
201
|
+
if len(result.sample_failures) < 5:
|
|
202
|
+
result.sample_failures.append(
|
|
203
|
+
f"rowid={rowid}: non-numeric vector: {e}"
|
|
204
|
+
)
|
|
205
|
+
continue
|
|
206
|
+
conn.execute(
|
|
207
|
+
"UPDATE semantic_facts SET embedding_json = ? WHERE rowid = ?",
|
|
208
|
+
(payload, rowid),
|
|
209
|
+
)
|
|
210
|
+
result.embedded += 1
|
|
211
|
+
conn.commit() # per-batch commit → resumable if killed
|
|
212
|
+
finally:
|
|
213
|
+
conn.close()
|
|
214
|
+
return result
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
218
|
+
"""CLI: report scanned/embedded/failed counts as JSON.
|
|
219
|
+
|
|
220
|
+
Exit codes:
|
|
221
|
+
0 — completed successfully (including zero-work no-ops).
|
|
222
|
+
2 — embed backend unavailable (MLX broken + Ollama unreachable).
|
|
223
|
+
Distinct from 0 so cron/health checks can alert on degraded
|
|
224
|
+
embedding capability without flagging clean idempotent runs.
|
|
225
|
+
"""
|
|
226
|
+
parser = argparse.ArgumentParser(
|
|
227
|
+
description="Backfill NULL semantic_facts.embedding_json rows."
|
|
228
|
+
)
|
|
229
|
+
parser.add_argument("--db-path", default=None)
|
|
230
|
+
parser.add_argument("--batch-size", type=int, default=50)
|
|
231
|
+
parser.add_argument(
|
|
232
|
+
"--max-rows",
|
|
233
|
+
type=int,
|
|
234
|
+
default=None,
|
|
235
|
+
help="Cap rows processed this call (for time-bounded incremental runs).",
|
|
236
|
+
)
|
|
237
|
+
parser.add_argument("--json", action="store_true")
|
|
238
|
+
args = parser.parse_args(argv)
|
|
239
|
+
|
|
240
|
+
result = backfill_embeddings(
|
|
241
|
+
db_path=args.db_path,
|
|
242
|
+
batch_size=args.batch_size,
|
|
243
|
+
max_rows=args.max_rows,
|
|
244
|
+
)
|
|
245
|
+
if args.json:
|
|
246
|
+
print(json.dumps(result.to_dict(), indent=2))
|
|
247
|
+
else:
|
|
248
|
+
print(
|
|
249
|
+
f"backfill: scanned={result.scanned} embedded={result.embedded} "
|
|
250
|
+
f"failed={result.failed} skipped_empty={result.skipped_empty_text} "
|
|
251
|
+
f"db={result.db_path}"
|
|
252
|
+
)
|
|
253
|
+
if result.backend_unavailable:
|
|
254
|
+
print(f" backend unavailable: {result.error}", file=sys.stderr)
|
|
255
|
+
for sample in result.sample_failures:
|
|
256
|
+
print(f" failure: {sample}", file=sys.stderr)
|
|
257
|
+
if result.backend_unavailable:
|
|
258
|
+
return 2
|
|
259
|
+
return 0
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
if __name__ == "__main__":
|
|
263
|
+
sys.exit(main())
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
"""Hybrid keyword+embedding rerank for the SQLite semantic index.
|
|
5
|
+
|
|
6
|
+
Lifts the documented MemTier ceiling: default recall stops being pure
|
|
7
|
+
keyword (`scripts/semantic_index/__init__.py::query_facts` legacy path)
|
|
8
|
+
and gains an embedding-rerank stage that surfaces synonyms / paraphrases
|
|
9
|
+
the keyword scorer misses.
|
|
10
|
+
|
|
11
|
+
Pipeline (default):
|
|
12
|
+
1. Keyword candidate generation (legacy SQLite scorer, overfetched).
|
|
13
|
+
2. Embed the query via ``embed_backend.embed`` (MLX default, Ollama
|
|
14
|
+
fallback, daemon-aware).
|
|
15
|
+
3. For each candidate that has a persisted embedding (``embedding_json``
|
|
16
|
+
column), compute cosine similarity to the query vector.
|
|
17
|
+
4. Final score = ``keyword_score + cosine_sim``. Rows without a
|
|
18
|
+
persisted embedding keep their keyword-only score.
|
|
19
|
+
|
|
20
|
+
Graceful fallback (NEVER raises):
|
|
21
|
+
- Embed backend down (MLX broken + Ollama unreachable + daemon off):
|
|
22
|
+
return the pure-keyword ranking.
|
|
23
|
+
- No candidate has a persisted embedding: return the pure-keyword
|
|
24
|
+
ranking. Embedding backfill is opportunistic at write time
|
|
25
|
+
(``upsert_fact(embedding=...)``).
|
|
26
|
+
|
|
27
|
+
Reuse: the embedding store IS the existing ``embedding_json`` column on
|
|
28
|
+
``semantic_facts`` — already populated when callers pass ``embedding=``
|
|
29
|
+
to ``upsert_fact``. No new table; no schema migration beyond the
|
|
30
|
+
pre-existing ``CREATE TABLE IF NOT EXISTS`` in ``init()``.
|
|
31
|
+
|
|
32
|
+
Stdlib-only. Math is plain Python (no numpy).
|
|
33
|
+
"""
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import json
|
|
37
|
+
import math
|
|
38
|
+
import sqlite3
|
|
39
|
+
from typing import Any, Callable, Optional, Sequence
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _cosine(a: Sequence[float], b: Sequence[float]) -> float:
|
|
43
|
+
"""Cosine similarity between two equal-length float vectors.
|
|
44
|
+
|
|
45
|
+
Returns 0.0 when either vector is zero-length or zero-norm. Tolerates
|
|
46
|
+
short reads (caller may pass an embedding from a different model
|
|
47
|
+
version with a shorter dimension) by truncating to the shorter length.
|
|
48
|
+
"""
|
|
49
|
+
n = min(len(a), len(b))
|
|
50
|
+
if n == 0:
|
|
51
|
+
return 0.0
|
|
52
|
+
dot = 0.0
|
|
53
|
+
na = 0.0
|
|
54
|
+
nb = 0.0
|
|
55
|
+
for i in range(n):
|
|
56
|
+
x = float(a[i])
|
|
57
|
+
y = float(b[i])
|
|
58
|
+
dot += x * y
|
|
59
|
+
na += x * x
|
|
60
|
+
nb += y * y
|
|
61
|
+
if na == 0.0 or nb == 0.0:
|
|
62
|
+
return 0.0
|
|
63
|
+
return dot / (math.sqrt(na) * math.sqrt(nb))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _parse_embedding(raw: Any) -> Optional[list[float]]:
|
|
67
|
+
"""Parse the embedding_json column value into a Python list of floats.
|
|
68
|
+
|
|
69
|
+
Returns None when the column is NULL, empty, or unparseable. Defensive
|
|
70
|
+
by design: a corrupt embedding row must not crash recall.
|
|
71
|
+
"""
|
|
72
|
+
if raw is None:
|
|
73
|
+
return None
|
|
74
|
+
if isinstance(raw, list):
|
|
75
|
+
try:
|
|
76
|
+
return [float(x) for x in raw]
|
|
77
|
+
except (TypeError, ValueError):
|
|
78
|
+
return None
|
|
79
|
+
if isinstance(raw, (bytes, bytearray)):
|
|
80
|
+
try:
|
|
81
|
+
raw = raw.decode("utf-8")
|
|
82
|
+
except UnicodeDecodeError:
|
|
83
|
+
return None
|
|
84
|
+
if not isinstance(raw, str) or not raw.strip():
|
|
85
|
+
return None
|
|
86
|
+
try:
|
|
87
|
+
parsed = json.loads(raw)
|
|
88
|
+
except (json.JSONDecodeError, ValueError):
|
|
89
|
+
return None
|
|
90
|
+
if not isinstance(parsed, list):
|
|
91
|
+
return None
|
|
92
|
+
try:
|
|
93
|
+
return [float(x) for x in parsed]
|
|
94
|
+
except (TypeError, ValueError):
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _safe_embed_query(
|
|
99
|
+
query: str,
|
|
100
|
+
embed_fn: Optional[Callable[[str], list[float]]],
|
|
101
|
+
) -> Optional[list[float]]:
|
|
102
|
+
"""Embed the query with the supplied backend. Returns None on failure.
|
|
103
|
+
|
|
104
|
+
``embed_fn`` is injectable so tests can supply a deterministic backend
|
|
105
|
+
without monkey-patching the module-level ``embed_backend.embed``. When
|
|
106
|
+
None, falls through to ``embed_backend.embed`` lazily — the import is
|
|
107
|
+
deferred so missing optional deps don't fail at module import time.
|
|
108
|
+
"""
|
|
109
|
+
if not query or not query.strip():
|
|
110
|
+
return None
|
|
111
|
+
if embed_fn is None:
|
|
112
|
+
try:
|
|
113
|
+
from embed_backend import embed as _embed # type: ignore # noqa: PLC0415
|
|
114
|
+
except Exception: # noqa: BLE001 graceful: backend not importable.
|
|
115
|
+
return None
|
|
116
|
+
embed_fn = _embed
|
|
117
|
+
try:
|
|
118
|
+
vec = embed_fn(query)
|
|
119
|
+
except Exception: # noqa: BLE001 graceful: backend raised at call time.
|
|
120
|
+
return None
|
|
121
|
+
if not isinstance(vec, list) or not vec:
|
|
122
|
+
return None
|
|
123
|
+
return vec
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def rerank_candidates(
|
|
127
|
+
candidates: list[tuple[float, sqlite3.Row]],
|
|
128
|
+
query_embedding: list[float],
|
|
129
|
+
) -> list[tuple[float, sqlite3.Row]]:
|
|
130
|
+
"""Add cosine boost to candidate scores.
|
|
131
|
+
|
|
132
|
+
Returns a new list sorted by (boosted_score DESC). Candidates without a
|
|
133
|
+
parseable embedding keep their original keyword score (no cosine
|
|
134
|
+
boost), so they don't lose ground to no-embedding rows in the pure
|
|
135
|
+
keyword case.
|
|
136
|
+
|
|
137
|
+
Score formula: ``final = keyword_score + cosine(query, candidate)``.
|
|
138
|
+
Additive rather than weighted-mix because (a) the keyword score is a
|
|
139
|
+
small integer (token match count) and the cosine is in [-1,1] — the
|
|
140
|
+
addition naturally lets a strong cosine match (≈0.7+) rescue a
|
|
141
|
+
candidate that only matched on 1 keyword token; (b) anything with the
|
|
142
|
+
same keyword score now ranks by semantic similarity. No weighted
|
|
143
|
+
tuning needed; we never trade keyword precision away.
|
|
144
|
+
"""
|
|
145
|
+
boosted: list[tuple[float, sqlite3.Row]] = []
|
|
146
|
+
for keyword_score, row in candidates:
|
|
147
|
+
emb = _parse_embedding(row["embedding_json"] if "embedding_json" in row.keys() else None)
|
|
148
|
+
if emb is None:
|
|
149
|
+
boosted.append((float(keyword_score), row))
|
|
150
|
+
continue
|
|
151
|
+
sim = _cosine(query_embedding, emb)
|
|
152
|
+
boosted.append((float(keyword_score) + sim, row))
|
|
153
|
+
boosted.sort(key=lambda item: item[0], reverse=True)
|
|
154
|
+
return boosted
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def has_any_embedding(candidates: list[tuple[float, sqlite3.Row]]) -> bool:
|
|
158
|
+
"""Cheap probe: does any candidate row carry a parseable embedding?
|
|
159
|
+
|
|
160
|
+
Per-row guard: a candidate without the column is skipped via ``continue``,
|
|
161
|
+
not short-circuited with ``return False``. Heterogeneous candidate sets
|
|
162
|
+
(e.g. a mid-migration mix of with/without-embedding rows, or a unioned
|
|
163
|
+
cursor from a future multi-source path) must still surface "yes" when
|
|
164
|
+
ANY row carries a vector. The earlier ``return False`` on the first
|
|
165
|
+
column-less row hid valid embedding rows downstream and turned hybrid
|
|
166
|
+
silently into keyword for the whole query — the exact dormancy this
|
|
167
|
+
rerank was meant to lift.
|
|
168
|
+
"""
|
|
169
|
+
for _, row in candidates:
|
|
170
|
+
if "embedding_json" not in row.keys():
|
|
171
|
+
continue
|
|
172
|
+
if _parse_embedding(row["embedding_json"]) is not None:
|
|
173
|
+
return True
|
|
174
|
+
return False
|