@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,445 @@
|
|
|
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
|
+
"""Global path/schema resolver for the build-loop memory framework.
|
|
5
|
+
|
|
6
|
+
This module is the active path contract for durable build-loop memory.
|
|
7
|
+
Every writer/reader script should import helpers from here instead of
|
|
8
|
+
embedding store paths directly.
|
|
9
|
+
|
|
10
|
+
Environment variable contract:
|
|
11
|
+
- ``$BUILD_LOOP_MEMORY_STORE_ROOT``: override the build-loop-memory root.
|
|
12
|
+
- ``$BUILD_LOOP_MEMORY_ROOT``: compatibility override for the same root.
|
|
13
|
+
- ``$AGENT_MEMORY_ROOT`` : compatibility override for the same root.
|
|
14
|
+
Defaults to ``~/dev/git-folder/build-loop-memory``.
|
|
15
|
+
- ``$AGENT_MEMORY_SCHEMA`` : override the default Postgres schema.
|
|
16
|
+
Defaults to ``personal_memory``.
|
|
17
|
+
- ``$AGENT_MEMORY_DUAL_WRITE``: when set to ``"1"``, writers must produce
|
|
18
|
+
BOTH the legacy artifact (``<repo>/.episodic/decisions/``,
|
|
19
|
+
``build_loop_memory.semantic_facts``) AND the
|
|
20
|
+
new artifact (``<root>/projects/<project>/decisions/``,
|
|
21
|
+
``personal_memory.semantic_facts``).
|
|
22
|
+
|
|
23
|
+
Cutover lock:
|
|
24
|
+
- ``/tmp/agent-memory-cutover.lock`` (exists) → ``write_decision.py``
|
|
25
|
+
prints ``cutover in progress, skipping`` and exits 0 with no writes.
|
|
26
|
+
|
|
27
|
+
These functions are pure and side-effect-free except for environment
|
|
28
|
+
inspection. They never mkdir or touch files; callers handle creation.
|
|
29
|
+
"""
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import os
|
|
33
|
+
import re
|
|
34
|
+
import subprocess
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# Legacy fallback constants. These are for migration/archive tooling only.
|
|
39
|
+
# Active build-loop readers and writers should use the canonical helpers
|
|
40
|
+
# below (`project_decisions_dir`, `project_lessons_dir`, top-level lanes).
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
LEGACY_SCHEMA = "build_loop_memory"
|
|
43
|
+
LEGACY_DECISIONS_REL = ".episodic/decisions"
|
|
44
|
+
|
|
45
|
+
DEFAULT_MEMORY_STORE_ROOT = "~/dev/git-folder/build-loop-memory"
|
|
46
|
+
DEFAULT_AGENT_MEMORY_ROOT = DEFAULT_MEMORY_STORE_ROOT
|
|
47
|
+
DEFAULT_SCHEMA = "personal_memory"
|
|
48
|
+
|
|
49
|
+
CUTOVER_LOCK_PATH = "/tmp/agent-memory-cutover.lock"
|
|
50
|
+
|
|
51
|
+
# Historical name retained as a compatibility alias. The active root is now
|
|
52
|
+
# the sibling `build-loop-memory` repository, not `~/.build-loop/memory`.
|
|
53
|
+
DEFAULT_BUILD_LOOP_MEMORY_ROOT = DEFAULT_MEMORY_STORE_ROOT
|
|
54
|
+
|
|
55
|
+
# Sub-component patterns: paths under a project that count as a distinct
|
|
56
|
+
# slug. Today: just `workers/`. Extend by adding entries; order matters
|
|
57
|
+
# (longest match wins).
|
|
58
|
+
SUBCOMPONENT_PATTERNS: tuple[str, ...] = ("workers",)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def memory_store_root() -> Path:
|
|
62
|
+
"""Return the canonical build-loop-memory root.
|
|
63
|
+
|
|
64
|
+
Reads, in order, ``$BUILD_LOOP_MEMORY_STORE_ROOT``,
|
|
65
|
+
``$BUILD_LOOP_MEMORY_ROOT``, and ``$AGENT_MEMORY_ROOT`` (expanded for
|
|
66
|
+
``~``). Falls back to ``~/dev/git-folder/build-loop-memory``. Path is
|
|
67
|
+
not required to exist; callers that need the directory should create it.
|
|
68
|
+
"""
|
|
69
|
+
raw = (
|
|
70
|
+
os.environ.get("BUILD_LOOP_MEMORY_STORE_ROOT")
|
|
71
|
+
or os.environ.get("BUILD_LOOP_MEMORY_ROOT")
|
|
72
|
+
or os.environ.get("AGENT_MEMORY_ROOT")
|
|
73
|
+
or DEFAULT_MEMORY_STORE_ROOT
|
|
74
|
+
)
|
|
75
|
+
return Path(os.path.expanduser(raw))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def agent_memory_root() -> Path:
|
|
79
|
+
"""Compatibility alias for ``memory_store_root()``."""
|
|
80
|
+
return memory_store_root()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def decisions_root() -> Path:
|
|
84
|
+
"""Return the legacy pre-cutover decisions root.
|
|
85
|
+
|
|
86
|
+
This helper exists for migration/archive tooling that still needs to
|
|
87
|
+
inventory ``<memory_store_root()>/decisions``. Active writers should use
|
|
88
|
+
``project_decisions_dir(project)``.
|
|
89
|
+
"""
|
|
90
|
+
return memory_store_root() / "decisions"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Project tag whitelist: alphanumerics, underscore, dash, dot. No path
|
|
94
|
+
# separators, no leading dot+dot, no leading slash. Length 1..127. The
|
|
95
|
+
# leading underscore allowance covers the canonical ``_unscoped`` tag.
|
|
96
|
+
_SAFE_PROJECT_TAG_RE = re.compile(r"^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _safe_project_tag(tag: str) -> str:
|
|
100
|
+
"""Return ``tag`` if it is safe to use as a directory name.
|
|
101
|
+
|
|
102
|
+
Rejects path-traversal sequences (``..``, ``/``, ``\\``) and
|
|
103
|
+
suspicious characters that could escape the decisions tree on
|
|
104
|
+
case-insensitive or symlink-following filesystems.
|
|
105
|
+
"""
|
|
106
|
+
if not tag or not _SAFE_PROJECT_TAG_RE.match(tag) or tag in {".", ".."}:
|
|
107
|
+
raise ValueError(f"unsafe project tag: {tag!r}")
|
|
108
|
+
return tag
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _safe_project_relpath(project: str) -> Path:
|
|
112
|
+
"""Return a validated relative path for a project tag.
|
|
113
|
+
|
|
114
|
+
Empty strings collapse to ``_unscoped``. Slash-separated subcomponent
|
|
115
|
+
slugs are supported, with every segment validated independently.
|
|
116
|
+
"""
|
|
117
|
+
if not project:
|
|
118
|
+
project = "_unscoped"
|
|
119
|
+
parts = project.split("/")
|
|
120
|
+
for part in parts:
|
|
121
|
+
_safe_project_tag(part)
|
|
122
|
+
return Path(*parts)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _safe_child(root: Path, relpath: Path, label: str) -> Path:
|
|
126
|
+
"""Return ``root / relpath`` after checking it resolves below root."""
|
|
127
|
+
candidate = (root / relpath).resolve()
|
|
128
|
+
root_resolved = root.resolve()
|
|
129
|
+
# Path.is_relative_to was added in 3.9; fall back to startswith on str.
|
|
130
|
+
rel = str(candidate)
|
|
131
|
+
root_str = str(root_resolved)
|
|
132
|
+
if not (rel == root_str or rel.startswith(root_str + os.sep)):
|
|
133
|
+
raise ValueError(f"{label} resolves outside {root}")
|
|
134
|
+
return root / relpath
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def project_root(project: str) -> Path:
|
|
138
|
+
"""Return ``<memory_store_root()>/projects/<project>`` (validated)."""
|
|
139
|
+
return _safe_child(memory_store_root() / "projects", _safe_project_relpath(project), "project")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def project_decisions_dir(project: str) -> Path:
|
|
143
|
+
"""Return ``<project_root(project)>/decisions``."""
|
|
144
|
+
return project_root(project) / "decisions"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def project_lessons_dir(project: str) -> Path:
|
|
148
|
+
"""Return ``<project_root(project)>/lessons``."""
|
|
149
|
+
return project_root(project) / "lessons"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def project_raw_dir(project: str) -> Path:
|
|
153
|
+
"""Return ``<project_root(project)>/raw`` for verbatim source material."""
|
|
154
|
+
return project_root(project) / "raw"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def project_raw_documents_dir(project: str) -> Path:
|
|
158
|
+
"""Return ``<project_raw_dir(project)>/documents``."""
|
|
159
|
+
return project_raw_dir(project) / "documents"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def project_raw_files_dir(project: str) -> Path:
|
|
163
|
+
"""Return ``<project_raw_dir(project)>/files``."""
|
|
164
|
+
return project_raw_dir(project) / "files"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def project_raw_artifacts_dir(project: str) -> Path:
|
|
168
|
+
"""Return ``<project_raw_dir(project)>/artifacts``."""
|
|
169
|
+
return project_raw_dir(project) / "artifacts"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def project_debugging_dir(project: str) -> Path:
|
|
173
|
+
"""Return ``<project_root(project)>/debugging``."""
|
|
174
|
+
return project_root(project) / "debugging"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def project_design_dir(project: str) -> Path:
|
|
178
|
+
"""Return ``<project_root(project)>/design``."""
|
|
179
|
+
return project_root(project) / "design"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def project_product_dir(project: str) -> Path:
|
|
183
|
+
"""Return ``<project_root(project)>/product``."""
|
|
184
|
+
return project_root(project) / "product"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def project_architecture_dir(project: str) -> Path:
|
|
188
|
+
"""Return ``<project_root(project)>/architecture``."""
|
|
189
|
+
return project_root(project) / "architecture"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def top_level_lessons_dir() -> Path:
|
|
193
|
+
"""Return ``<memory_store_root()>/lessons``."""
|
|
194
|
+
return memory_store_root() / "lessons"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def top_level_debugging_dir() -> Path:
|
|
198
|
+
"""Return ``<memory_store_root()>/debugging``."""
|
|
199
|
+
return memory_store_root() / "debugging"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def top_level_design_dir() -> Path:
|
|
203
|
+
"""Return ``<memory_store_root()>/design``."""
|
|
204
|
+
return memory_store_root() / "design"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def top_level_product_dir() -> Path:
|
|
208
|
+
"""Return ``<memory_store_root()>/product``."""
|
|
209
|
+
return memory_store_root() / "product"
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def top_level_architecture_dir() -> Path:
|
|
213
|
+
"""Return ``<memory_store_root()>/architecture``."""
|
|
214
|
+
return memory_store_root() / "architecture"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def memory_indexes_dir() -> Path:
|
|
218
|
+
"""Return ``<memory_store_root()>/indexes``."""
|
|
219
|
+
return memory_store_root() / "indexes"
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def decisions_dir_for_project(project: str) -> Path:
|
|
223
|
+
"""Compatibility alias for ``project_decisions_dir(project)``."""
|
|
224
|
+
return project_decisions_dir(project)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def legacy_decisions_dir(workdir: Path) -> Path:
|
|
228
|
+
"""Return ``<workdir>/.episodic/decisions`` (the per-repo legacy path)."""
|
|
229
|
+
return Path(workdir) / ".episodic" / "decisions"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def default_schema() -> str:
|
|
233
|
+
"""Return the default Postgres schema for the new system.
|
|
234
|
+
|
|
235
|
+
Reads ``$AGENT_MEMORY_SCHEMA``, falls back to ``personal_memory``.
|
|
236
|
+
"""
|
|
237
|
+
return os.environ.get("AGENT_MEMORY_SCHEMA") or DEFAULT_SCHEMA
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def legacy_schema() -> str:
|
|
241
|
+
"""Return the legacy Postgres schema name (``build_loop_memory``).
|
|
242
|
+
|
|
243
|
+
Used during the dual-write transitional window. There is no env-var
|
|
244
|
+
override for the legacy schema — Phase D removes it entirely.
|
|
245
|
+
"""
|
|
246
|
+
return LEGACY_SCHEMA
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def dual_write_enabled() -> bool:
|
|
250
|
+
"""Return True iff ``$AGENT_MEMORY_DUAL_WRITE`` is set to ``"1"``."""
|
|
251
|
+
return os.environ.get("AGENT_MEMORY_DUAL_WRITE") == "1"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def cutover_lock_active() -> bool:
|
|
255
|
+
"""Return True iff the cutover lock file exists.
|
|
256
|
+
|
|
257
|
+
Writers must check this at the *very top* of their entry point and
|
|
258
|
+
exit cleanly when active.
|
|
259
|
+
"""
|
|
260
|
+
return Path(CUTOVER_LOCK_PATH).exists()
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
# Build-loop memory paths (global + per-project segmentation).
|
|
265
|
+
#
|
|
266
|
+
# Per the memory-consolidation PR series (2026-05-13):
|
|
267
|
+
# - ~/.build-loop/memory/ global root (existing)
|
|
268
|
+
# - ~/.build-loop/memory/projects/<slug>/ project-scoped lessons (NEW)
|
|
269
|
+
# - ~/.build-loop/memory/projects/_archive/<slug>/ retired projects
|
|
270
|
+
#
|
|
271
|
+
# PR 1 (read-path tolerance, 2026-05-13) added the projects/<slug>/ tier
|
|
272
|
+
# while still reading the legacy per-repo location. PR 2 cut writes over.
|
|
273
|
+
# PR 3 (2026-05-13) removed the legacy read shim — the consolidated tree
|
|
274
|
+
# is now the only path read. Slug derivation reuses _safe_project_tag so
|
|
275
|
+
# the same normalization applies to filesystem dirs AND the Postgres
|
|
276
|
+
# semantic_facts.project column (no split-brain by construction).
|
|
277
|
+
# ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def build_loop_memory_root() -> Path:
|
|
281
|
+
"""Compatibility alias for ``memory_store_root()``."""
|
|
282
|
+
return memory_store_root()
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def project_memory_root() -> Path:
|
|
286
|
+
"""Return ``<memory_store_root()>/projects``."""
|
|
287
|
+
return memory_store_root() / "projects"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def project_memory_dir_for_project(project: str) -> Path:
|
|
291
|
+
"""Compatibility alias for ``project_root(project)``."""
|
|
292
|
+
return project_root(project)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def archive_memory_dir(project: str) -> Path:
|
|
296
|
+
"""Return ``project_memory_root() / _archive / <project>`` (validated).
|
|
297
|
+
|
|
298
|
+
Same safety contract as ``project_memory_dir_for_project`` — used for
|
|
299
|
+
retired projects whose memory should remain queryable.
|
|
300
|
+
"""
|
|
301
|
+
if not project:
|
|
302
|
+
raise ValueError("archive_memory_dir requires a non-empty project tag")
|
|
303
|
+
parts = project.split("/")
|
|
304
|
+
for p in parts:
|
|
305
|
+
_safe_project_tag(p)
|
|
306
|
+
candidate_rel = Path("_archive", *parts)
|
|
307
|
+
candidate = (project_memory_root() / candidate_rel).resolve()
|
|
308
|
+
root_resolved = project_memory_root().resolve()
|
|
309
|
+
rel = str(candidate)
|
|
310
|
+
root_str = str(root_resolved)
|
|
311
|
+
if not (rel == root_str or rel.startswith(root_str + os.sep)):
|
|
312
|
+
raise ValueError(
|
|
313
|
+
f"archive project tag {project!r} resolves outside project_memory_root()"
|
|
314
|
+
)
|
|
315
|
+
return project_memory_root() / candidate_rel
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _canonical_repo_root(cwd_path: Path) -> Path | None:
|
|
319
|
+
"""Return the canonical (worktree-independent) repo root for ``cwd_path``.
|
|
320
|
+
|
|
321
|
+
Runs ``git rev-parse --git-common-dir`` scoped to ``cwd_path``. A worktree's
|
|
322
|
+
git-common-dir points at the *canonical* repo's ``.git`` (shared by the main
|
|
323
|
+
checkout + every ``git worktree``), so the parent of the resolved common-dir
|
|
324
|
+
is identical from anywhere in the repo family. Returns ``None`` when the
|
|
325
|
+
invocation fails, output is empty, or resolution raises. Never raises.
|
|
326
|
+
|
|
327
|
+
DRY note: ``rally_point/channel_paths.py`` carries a sibling copy used for the
|
|
328
|
+
channel slug. ``_paths`` is the lower-level module (``channel_paths`` imports
|
|
329
|
+
FROM it), so the two cannot share without a circular import; consolidating is
|
|
330
|
+
tracked as a follow-up. Behaviour is identical.
|
|
331
|
+
"""
|
|
332
|
+
try:
|
|
333
|
+
out = subprocess.run(
|
|
334
|
+
["git", "rev-parse", "--git-common-dir"],
|
|
335
|
+
cwd=str(cwd_path),
|
|
336
|
+
check=True,
|
|
337
|
+
capture_output=True,
|
|
338
|
+
text=True,
|
|
339
|
+
).stdout.strip()
|
|
340
|
+
except (subprocess.CalledProcessError, OSError):
|
|
341
|
+
return None
|
|
342
|
+
if not out:
|
|
343
|
+
return None
|
|
344
|
+
common = Path(out)
|
|
345
|
+
if not common.is_absolute():
|
|
346
|
+
common = cwd_path / common
|
|
347
|
+
try:
|
|
348
|
+
return common.resolve().parent
|
|
349
|
+
except (OSError, RuntimeError):
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def derive_slug_from_cwd(cwd: Path | str | None = None) -> str:
|
|
354
|
+
"""Derive the project slug from a working directory.
|
|
355
|
+
|
|
356
|
+
Algorithm (deterministic; alignment with Postgres semantic_facts.project
|
|
357
|
+
is guaranteed by routing every slug through ``_safe_project_tag``):
|
|
358
|
+
|
|
359
|
+
1. Resolve symlinks via ``Path.resolve()`` so a session running in
|
|
360
|
+
``~/.claude/plugins/build-loop`` resolves to ``~/dev/git-folder/build-loop``.
|
|
361
|
+
2. Walk up looking for a ``.git`` entry (file OR dir — worktrees use
|
|
362
|
+
a file). The deepest enclosing ``.git`` defines the repo root.
|
|
363
|
+
3. Slug base = ``basename(repo_root)`` lowercased; non-safe chars
|
|
364
|
+
collapsed to ``-``; runs of ``-`` collapsed; leading/trailing
|
|
365
|
+
``-`` stripped; capped at 64 chars.
|
|
366
|
+
4. If ``cwd`` is N levels below repo_root AND the first level matches
|
|
367
|
+
a ``SUBCOMPONENT_PATTERNS`` entry, append ``/<subcomponent>`` to
|
|
368
|
+
the slug. Today only ``workers`` is recognized.
|
|
369
|
+
5. If no ``.git`` is found in the ancestry, return ``_unscoped``.
|
|
370
|
+
6. Final slug is run through ``_safe_project_tag`` (each segment
|
|
371
|
+
independently for sub-component slugs).
|
|
372
|
+
|
|
373
|
+
Returns the slug string. Never raises (returns ``_unscoped`` on
|
|
374
|
+
ambiguity); callers that need to know whether resolution succeeded
|
|
375
|
+
can check for the ``_unscoped`` sentinel.
|
|
376
|
+
"""
|
|
377
|
+
if cwd is None:
|
|
378
|
+
cwd = Path.cwd()
|
|
379
|
+
try:
|
|
380
|
+
cwd_resolved = Path(os.path.expanduser(str(cwd))).resolve()
|
|
381
|
+
except (OSError, RuntimeError):
|
|
382
|
+
return "_unscoped"
|
|
383
|
+
|
|
384
|
+
# Walk up looking for .git
|
|
385
|
+
repo_root: Path | None = None
|
|
386
|
+
cursor = cwd_resolved
|
|
387
|
+
seen: set[str] = set()
|
|
388
|
+
while True:
|
|
389
|
+
key = str(cursor)
|
|
390
|
+
if key in seen:
|
|
391
|
+
break # symlink cycle guard
|
|
392
|
+
seen.add(key)
|
|
393
|
+
if (cursor / ".git").exists():
|
|
394
|
+
repo_root = cursor
|
|
395
|
+
break
|
|
396
|
+
parent = cursor.parent
|
|
397
|
+
if parent == cursor:
|
|
398
|
+
break
|
|
399
|
+
cursor = parent
|
|
400
|
+
|
|
401
|
+
if repo_root is None:
|
|
402
|
+
return "_unscoped"
|
|
403
|
+
|
|
404
|
+
# Slug base from the CANONICAL repo root so every `git worktree` of one repo
|
|
405
|
+
# shares a single memory project (else each worktree — e.g. an
|
|
406
|
+
# `isolation: "worktree"` build-orchestrator dispatch — gets its own split
|
|
407
|
+
# project). A worktree's `.git` is a FILE pointing at the main repo's gitdir;
|
|
408
|
+
# the main checkout's `.git` is a DIR. Only shell out to git for the worktree
|
|
409
|
+
# case so the common main-checkout path stays subprocess-free. Mirrors the
|
|
410
|
+
# channel slug's D1 fix in `rally_point/channel_paths.py`.
|
|
411
|
+
base_root = repo_root
|
|
412
|
+
if (repo_root / ".git").is_file():
|
|
413
|
+
canonical = _canonical_repo_root(cwd_resolved)
|
|
414
|
+
if canonical is not None:
|
|
415
|
+
base_root = canonical
|
|
416
|
+
|
|
417
|
+
base = base_root.name.lower()
|
|
418
|
+
base = re.sub(r"[^a-z0-9._-]", "-", base)
|
|
419
|
+
base = re.sub(r"-{2,}", "-", base).strip("-")
|
|
420
|
+
base = base[:64] or "_unscoped"
|
|
421
|
+
|
|
422
|
+
# Sub-component detection: is cwd under <repo_root>/<sub>/...?
|
|
423
|
+
try:
|
|
424
|
+
rel = cwd_resolved.relative_to(repo_root)
|
|
425
|
+
except ValueError:
|
|
426
|
+
rel = Path()
|
|
427
|
+
if rel.parts:
|
|
428
|
+
first = rel.parts[0].lower()
|
|
429
|
+
if first in SUBCOMPONENT_PATTERNS:
|
|
430
|
+
sub_safe = re.sub(r"[^a-z0-9._-]", "-", first).strip("-")[:64]
|
|
431
|
+
if sub_safe:
|
|
432
|
+
slug = f"{base}/{sub_safe}"
|
|
433
|
+
# Validate each segment
|
|
434
|
+
for seg in slug.split("/"):
|
|
435
|
+
try:
|
|
436
|
+
_safe_project_tag(seg)
|
|
437
|
+
except ValueError:
|
|
438
|
+
return "_unscoped"
|
|
439
|
+
return slug
|
|
440
|
+
|
|
441
|
+
try:
|
|
442
|
+
_safe_project_tag(base)
|
|
443
|
+
except ValueError:
|
|
444
|
+
return "_unscoped"
|
|
445
|
+
return base
|
package/scripts/blm.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# capability:
|
|
3
|
+
# purpose: Host-neutral CLI for build-loop-memory hot context and evidence reads.
|
|
4
|
+
# application: memory
|
|
5
|
+
# status: experimental
|
|
6
|
+
# SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
|
|
7
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
8
|
+
"""Host-neutral build-loop-memory CLI.
|
|
9
|
+
|
|
10
|
+
Primary surfaces:
|
|
11
|
+
blm context --workdir "$PWD" --query "<goal>" --mode fast --json
|
|
12
|
+
blm context --workdir "$PWD" --query "<goal>" --mode expand --json
|
|
13
|
+
blm open --id <memory-id-or-artifact-id>
|
|
14
|
+
blm status --workdir "$PWD" --json
|
|
15
|
+
blm serve --host 127.0.0.1 --port 8777
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
HERE = Path(__file__).resolve().parent
|
|
27
|
+
if str(HERE) not in sys.path:
|
|
28
|
+
sys.path.insert(0, str(HERE))
|
|
29
|
+
|
|
30
|
+
from memory_context import ( # type: ignore # noqa: E402
|
|
31
|
+
build_context,
|
|
32
|
+
describe_access,
|
|
33
|
+
open_artifact,
|
|
34
|
+
render_current_markdown,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
DEFAULT_API_HOST = "127.0.0.1"
|
|
38
|
+
DEFAULT_API_PORT = 8777
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _emit_json(data: dict[str, Any]) -> None:
|
|
42
|
+
print(json.dumps(data, indent=2, sort_keys=True, default=str))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cmd_context(args: argparse.Namespace) -> int:
|
|
46
|
+
envelope = build_context(
|
|
47
|
+
workdir=Path(args.workdir).resolve(),
|
|
48
|
+
query=args.query,
|
|
49
|
+
mode=args.mode,
|
|
50
|
+
project=args.project,
|
|
51
|
+
write=not args.no_write,
|
|
52
|
+
limit=args.limit,
|
|
53
|
+
)
|
|
54
|
+
if args.json:
|
|
55
|
+
_emit_json(envelope)
|
|
56
|
+
return 0
|
|
57
|
+
current = envelope["current"]
|
|
58
|
+
print(render_current_markdown(current), end="")
|
|
59
|
+
if args.mode == "expand":
|
|
60
|
+
lessons = (envelope.get("expansion") or {}).get("lessons") or []
|
|
61
|
+
if lessons:
|
|
62
|
+
print("\n## Expanded Lessons\n")
|
|
63
|
+
for item in lessons:
|
|
64
|
+
print(f"- {item.get('name')} - {item.get('description')}")
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _cmd_open(args: argparse.Namespace) -> int:
|
|
69
|
+
result = open_artifact(
|
|
70
|
+
args.id,
|
|
71
|
+
workdir=Path(args.workdir).resolve(),
|
|
72
|
+
project=args.project,
|
|
73
|
+
max_chars=args.max_chars,
|
|
74
|
+
)
|
|
75
|
+
if args.json:
|
|
76
|
+
_emit_json(result)
|
|
77
|
+
return 0
|
|
78
|
+
if not result.get("exists"):
|
|
79
|
+
print(result.get("reason") or "not found", file=sys.stderr)
|
|
80
|
+
return 1
|
|
81
|
+
print(result.get("text", ""), end="")
|
|
82
|
+
return 0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _cmd_status(args: argparse.Namespace) -> int:
|
|
86
|
+
result = describe_access(
|
|
87
|
+
workdir=Path(args.workdir).resolve(),
|
|
88
|
+
project=args.project,
|
|
89
|
+
host=args.host,
|
|
90
|
+
port=args.port,
|
|
91
|
+
)
|
|
92
|
+
if args.json:
|
|
93
|
+
_emit_json(result)
|
|
94
|
+
return 0
|
|
95
|
+
print(f"Project: {result['project']}")
|
|
96
|
+
print(f"Memory root: {result['memory_root']}")
|
|
97
|
+
print(f"Current JSON: {result['current_paths']['json']}")
|
|
98
|
+
print(f"Current exists: {result['current_exists']['json']}")
|
|
99
|
+
print(f"Fast CLI: {result['cli']['fast']}")
|
|
100
|
+
print(f"Expand CLI: {result['cli']['expand']}")
|
|
101
|
+
print(f"API: {result['api']['base_url']}")
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _cmd_serve(args: argparse.Namespace) -> int:
|
|
106
|
+
from blm_api import serve # type: ignore # noqa: PLC0415
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
serve(host=args.host, port=args.port, allow_nonlocal=args.allow_nonlocal)
|
|
110
|
+
except ValueError as exc:
|
|
111
|
+
print(str(exc), file=sys.stderr)
|
|
112
|
+
return 1
|
|
113
|
+
return 0
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
117
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
118
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
119
|
+
|
|
120
|
+
context = sub.add_parser("context", help="Return fast or expanded project memory context.")
|
|
121
|
+
context.add_argument("--workdir", default=os.getcwd())
|
|
122
|
+
context.add_argument("--query", default="")
|
|
123
|
+
context.add_argument("--project", default=None)
|
|
124
|
+
context.add_argument("--mode", choices=("fast", "expand"), default="fast")
|
|
125
|
+
context.add_argument("--limit", type=int, default=5)
|
|
126
|
+
context.add_argument("--json", action="store_true", help="Print JSON envelope.")
|
|
127
|
+
context.add_argument("--no-write", action="store_true", help="Do not persist CURRENT files.")
|
|
128
|
+
context.set_defaults(func=_cmd_context)
|
|
129
|
+
|
|
130
|
+
open_cmd = sub.add_parser("open", help="Read a memory evidence item by id or safe memory-store path.")
|
|
131
|
+
open_cmd.add_argument("--id", required=True)
|
|
132
|
+
open_cmd.add_argument("--workdir", default=os.getcwd())
|
|
133
|
+
open_cmd.add_argument("--project", default=None)
|
|
134
|
+
open_cmd.add_argument("--max-chars", type=int, default=8000)
|
|
135
|
+
open_cmd.add_argument("--json", action="store_true", help="Print JSON envelope.")
|
|
136
|
+
open_cmd.set_defaults(func=_cmd_open)
|
|
137
|
+
|
|
138
|
+
status = sub.add_parser("status", help="Show fast-access paths, commands, and API endpoints.")
|
|
139
|
+
status.add_argument("--workdir", default=os.getcwd())
|
|
140
|
+
status.add_argument("--project", default=None)
|
|
141
|
+
status.add_argument("--host", default=DEFAULT_API_HOST)
|
|
142
|
+
status.add_argument("--port", type=int, default=DEFAULT_API_PORT)
|
|
143
|
+
status.add_argument("--json", action="store_true", help="Print JSON envelope.")
|
|
144
|
+
status.set_defaults(func=_cmd_status)
|
|
145
|
+
|
|
146
|
+
serve_cmd = sub.add_parser("serve", help="Serve the optional local build-loop-memory HTTP API.")
|
|
147
|
+
serve_cmd.add_argument("--host", default=os.environ.get("BLM_API_HOST", DEFAULT_API_HOST))
|
|
148
|
+
serve_cmd.add_argument("--port", type=int, default=int(os.environ.get("BLM_API_PORT", str(DEFAULT_API_PORT))))
|
|
149
|
+
serve_cmd.add_argument("--allow-nonlocal", action="store_true")
|
|
150
|
+
serve_cmd.set_defaults(func=_cmd_serve)
|
|
151
|
+
|
|
152
|
+
return parser
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def main(argv: list[str] | None = None) -> int:
|
|
156
|
+
parser = build_parser()
|
|
157
|
+
args = parser.parse_args(argv)
|
|
158
|
+
return int(args.func(args))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
raise SystemExit(main())
|