@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,556 @@
|
|
|
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
|
+
"""install_memory.py — bootstrap the build-loop memory directory.
|
|
5
|
+
|
|
6
|
+
Creates the canonical build-loop-memory root (default:
|
|
7
|
+
`~/dev/git-folder/build-loop-memory`) and seeds it with template
|
|
8
|
+
`constitution.md` + `MEMORY.md` if either is missing. Idempotent — never
|
|
9
|
+
overwrites existing files.
|
|
10
|
+
|
|
11
|
+
Optional: link the directory to a private git repo so user-specific memory
|
|
12
|
+
content is versioned. The build-loop public repo ships ONLY the templates;
|
|
13
|
+
the user's actual lessons / patterns / decisions belong in a separate private
|
|
14
|
+
repo (see `docs/memory-setup.md`).
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
python3 scripts/install_memory.py # bootstrap + seed
|
|
18
|
+
python3 scripts/install_memory.py --guided # guided terminal install
|
|
19
|
+
python3 scripts/install_memory.py --check # report status, no writes
|
|
20
|
+
python3 scripts/install_memory.py --validate-seed # validate packaged public seed
|
|
21
|
+
python3 scripts/install_memory.py --link-repo <url> # bootstrap + clone private repo
|
|
22
|
+
python3 scripts/install_memory.py --dest <path> # override default location
|
|
23
|
+
|
|
24
|
+
Exit codes:
|
|
25
|
+
0 — success (or check completed)
|
|
26
|
+
1 — write failure
|
|
27
|
+
2 — invalid arguments
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import json
|
|
33
|
+
import re
|
|
34
|
+
import shutil
|
|
35
|
+
import subprocess
|
|
36
|
+
import sys
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
from typing import Optional
|
|
39
|
+
|
|
40
|
+
HERE = Path(__file__).resolve().parent
|
|
41
|
+
if str(HERE) not in sys.path:
|
|
42
|
+
sys.path.insert(0, str(HERE))
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
from _paths import _safe_project_relpath, memory_store_root # type: ignore # noqa: E402
|
|
46
|
+
except Exception: # noqa: BLE001
|
|
47
|
+
memory_store_root = None # type: ignore[assignment]
|
|
48
|
+
_safe_project_relpath = None # type: ignore[assignment]
|
|
49
|
+
|
|
50
|
+
DEFAULT_DEST = memory_store_root() if memory_store_root is not None else Path.home() / "dev" / "git-folder" / "build-loop-memory"
|
|
51
|
+
TEMPLATE_DIR_RELATIVE = "templates/memory"
|
|
52
|
+
SEED_MANIFEST_FILENAME = "manifest.json"
|
|
53
|
+
TEMPLATES = [
|
|
54
|
+
("constitution.md.template", "constitution.md"),
|
|
55
|
+
("MEMORY.md.template", "MEMORY.md"),
|
|
56
|
+
]
|
|
57
|
+
PROJECT_RAW_SUBDIRS = (
|
|
58
|
+
"documents",
|
|
59
|
+
"data",
|
|
60
|
+
"db",
|
|
61
|
+
"runtime",
|
|
62
|
+
"agent-artifacts",
|
|
63
|
+
"artifacts",
|
|
64
|
+
"files",
|
|
65
|
+
)
|
|
66
|
+
PROJECT_TOPIC_DIRS = frozenset({
|
|
67
|
+
"apps",
|
|
68
|
+
"assets",
|
|
69
|
+
"architecture",
|
|
70
|
+
"context",
|
|
71
|
+
"decisions",
|
|
72
|
+
"docs",
|
|
73
|
+
"features",
|
|
74
|
+
"formats",
|
|
75
|
+
"indexes",
|
|
76
|
+
"lessons",
|
|
77
|
+
"plugins",
|
|
78
|
+
"product",
|
|
79
|
+
"prompts",
|
|
80
|
+
"raw",
|
|
81
|
+
"research",
|
|
82
|
+
"semantic",
|
|
83
|
+
"skills",
|
|
84
|
+
"sources",
|
|
85
|
+
"testing",
|
|
86
|
+
"tradeoffs",
|
|
87
|
+
})
|
|
88
|
+
KEEP_FILENAME = ".gitkeep"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def script_dir() -> Path:
|
|
92
|
+
return HERE
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def template_dir() -> Path:
|
|
96
|
+
# scripts/install_memory.py lives next to scripts/ at the repo root
|
|
97
|
+
return script_dir().parent / TEMPLATE_DIR_RELATIVE
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def seed_manifest_path(tpl_dir: Optional[Path] = None) -> Path:
|
|
101
|
+
return (tpl_dir or template_dir()) / SEED_MANIFEST_FILENAME
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _load_seed_manifest(tpl_dir: Optional[Path] = None) -> tuple[Optional[dict], list[str]]:
|
|
105
|
+
path = seed_manifest_path(tpl_dir)
|
|
106
|
+
if not path.exists():
|
|
107
|
+
return None, [f"seed manifest missing: {path}"]
|
|
108
|
+
try:
|
|
109
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
110
|
+
except json.JSONDecodeError as exc:
|
|
111
|
+
return None, [f"seed manifest invalid JSON: {exc}"]
|
|
112
|
+
if not isinstance(data, dict):
|
|
113
|
+
return None, ["seed manifest root must be an object"]
|
|
114
|
+
issues: list[str] = []
|
|
115
|
+
if data.get("kind") != "build-loop-memory-public-seed":
|
|
116
|
+
issues.append("seed manifest kind must be build-loop-memory-public-seed")
|
|
117
|
+
if not data.get("schema_version"):
|
|
118
|
+
issues.append("seed manifest schema_version missing")
|
|
119
|
+
if not isinstance(data.get("sources"), list):
|
|
120
|
+
issues.append("seed manifest sources must be a list")
|
|
121
|
+
privacy = data.get("privacy")
|
|
122
|
+
if not isinstance(privacy, dict):
|
|
123
|
+
issues.append("seed manifest privacy block missing")
|
|
124
|
+
elif not isinstance(privacy.get("deny_patterns"), list):
|
|
125
|
+
issues.append("seed manifest privacy.deny_patterns must be a list")
|
|
126
|
+
return data, issues
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _manifest_source_rels(manifest: dict) -> tuple[set[str], list[str]]:
|
|
130
|
+
rels: set[str] = set()
|
|
131
|
+
issues: list[str] = []
|
|
132
|
+
for entry in manifest.get("sources", []):
|
|
133
|
+
if not isinstance(entry, dict):
|
|
134
|
+
issues.append("seed manifest source entry must be an object")
|
|
135
|
+
continue
|
|
136
|
+
source = entry.get("source")
|
|
137
|
+
if isinstance(source, str):
|
|
138
|
+
rel_path = Path(source)
|
|
139
|
+
if rel_path.is_absolute() or ".." in rel_path.parts:
|
|
140
|
+
issues.append(f"seed manifest source must stay inside template dir: {source}")
|
|
141
|
+
continue
|
|
142
|
+
rels.add(source)
|
|
143
|
+
else:
|
|
144
|
+
issues.append("seed manifest source entry missing source string")
|
|
145
|
+
return rels, issues
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def validate_public_seed(tpl_dir: Optional[Path] = None) -> dict:
|
|
149
|
+
"""Validate that the packaged memory seed is scaffold-only and allowlisted."""
|
|
150
|
+
root = tpl_dir or template_dir()
|
|
151
|
+
manifest, issues = _load_seed_manifest(root)
|
|
152
|
+
files: list[str] = []
|
|
153
|
+
seed_version = None
|
|
154
|
+
privacy_classification = None
|
|
155
|
+
if manifest is not None:
|
|
156
|
+
seed_version = manifest.get("seed_version")
|
|
157
|
+
privacy = manifest.get("privacy") if isinstance(manifest.get("privacy"), dict) else {}
|
|
158
|
+
privacy_classification = privacy.get("classification") if isinstance(privacy, dict) else None
|
|
159
|
+
allowed, source_issues = _manifest_source_rels(manifest)
|
|
160
|
+
issues.extend(source_issues)
|
|
161
|
+
if not allowed:
|
|
162
|
+
issues.append("seed manifest has no source allowlist")
|
|
163
|
+
for rel in sorted(allowed):
|
|
164
|
+
if not (root / rel).is_file():
|
|
165
|
+
issues.append(f"allowlisted seed file missing: {rel}")
|
|
166
|
+
deny_patterns = privacy.get("deny_patterns", []) if isinstance(privacy, dict) else []
|
|
167
|
+
compiled_patterns: list[tuple[str, re.Pattern[str]]] = []
|
|
168
|
+
for pattern in deny_patterns:
|
|
169
|
+
if not isinstance(pattern, str):
|
|
170
|
+
issues.append("seed manifest deny pattern must be a string")
|
|
171
|
+
continue
|
|
172
|
+
try:
|
|
173
|
+
compiled_patterns.append((pattern, re.compile(pattern)))
|
|
174
|
+
except re.error as exc:
|
|
175
|
+
issues.append(f"seed manifest deny pattern invalid: {pattern}: {exc}")
|
|
176
|
+
|
|
177
|
+
for path in sorted(root.rglob("*")):
|
|
178
|
+
if path.is_dir():
|
|
179
|
+
continue
|
|
180
|
+
rel = path.relative_to(root).as_posix()
|
|
181
|
+
if rel == SEED_MANIFEST_FILENAME:
|
|
182
|
+
continue
|
|
183
|
+
files.append(rel)
|
|
184
|
+
if rel not in allowed:
|
|
185
|
+
issues.append(f"seed file not allowlisted in manifest: {rel}")
|
|
186
|
+
continue
|
|
187
|
+
try:
|
|
188
|
+
body = path.read_text(encoding="utf-8")
|
|
189
|
+
except UnicodeDecodeError:
|
|
190
|
+
issues.append(f"seed file is not UTF-8 text: {rel}")
|
|
191
|
+
continue
|
|
192
|
+
for pattern, compiled in compiled_patterns:
|
|
193
|
+
if compiled.search(body):
|
|
194
|
+
issues.append(f"privacy deny pattern matched {rel}: {pattern}")
|
|
195
|
+
return {
|
|
196
|
+
"ok": not issues,
|
|
197
|
+
"kind": "build-loop-memory-public-seed-validation",
|
|
198
|
+
"manifest": str(seed_manifest_path(root)),
|
|
199
|
+
"seed_version": seed_version,
|
|
200
|
+
"privacy_classification": privacy_classification,
|
|
201
|
+
"files": files,
|
|
202
|
+
"issues": issues,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def print_seed_validation(validation: dict) -> None:
|
|
207
|
+
status = "ok" if validation.get("ok") else "failed"
|
|
208
|
+
print(f"public seed: {status}")
|
|
209
|
+
print(f" manifest: {validation.get('manifest')}")
|
|
210
|
+
print(f" seed_version: {validation.get('seed_version') or 'unknown'}")
|
|
211
|
+
print(f" privacy: {validation.get('privacy_classification') or 'unknown'}")
|
|
212
|
+
for rel in validation.get("files") or []:
|
|
213
|
+
print(f" - {rel}")
|
|
214
|
+
for issue in validation.get("issues") or []:
|
|
215
|
+
print(f" ! {issue}")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def report(dest: Path) -> dict:
|
|
219
|
+
"""Inspect the destination and report what's present / missing."""
|
|
220
|
+
status = {
|
|
221
|
+
"dest": str(dest),
|
|
222
|
+
"exists": dest.exists(),
|
|
223
|
+
"is_git_repo": (dest / ".git").exists() if dest.exists() else False,
|
|
224
|
+
"files": {},
|
|
225
|
+
}
|
|
226
|
+
for _, target_name in TEMPLATES:
|
|
227
|
+
target = dest / target_name
|
|
228
|
+
status["files"][target_name] = {
|
|
229
|
+
"exists": target.exists(),
|
|
230
|
+
"size_bytes": target.stat().st_size if target.exists() else 0,
|
|
231
|
+
}
|
|
232
|
+
# Count user content (root level, excluding templates and the projects/ tree)
|
|
233
|
+
if dest.exists():
|
|
234
|
+
template_names = {t[1] for t in TEMPLATES}
|
|
235
|
+
user_files = [
|
|
236
|
+
p for p in dest.glob("*.md")
|
|
237
|
+
if p.name not in template_names and p.name != "README.md"
|
|
238
|
+
]
|
|
239
|
+
status["user_md_files"] = len(user_files)
|
|
240
|
+
# Project segmentation (NEW PR 1)
|
|
241
|
+
indexes_dir = dest / "indexes"
|
|
242
|
+
status["indexes_dir_exists"] = indexes_dir.is_dir()
|
|
243
|
+
projects_dir = dest / "projects"
|
|
244
|
+
status["projects_dir_exists"] = projects_dir.is_dir()
|
|
245
|
+
if projects_dir.is_dir():
|
|
246
|
+
per_project: list[dict] = []
|
|
247
|
+
for sub in sorted(projects_dir.iterdir()):
|
|
248
|
+
if not sub.is_dir():
|
|
249
|
+
continue
|
|
250
|
+
# Count .md files at top level of the project subdir (sub-component
|
|
251
|
+
# dirs like workers/ contribute their own row).
|
|
252
|
+
md_count = sum(1 for _ in sub.glob("*.md"))
|
|
253
|
+
raw_status = _raw_status(sub)
|
|
254
|
+
per_project.append({
|
|
255
|
+
"slug": sub.name,
|
|
256
|
+
"md_files": md_count,
|
|
257
|
+
"raw_exists": raw_status["exists"],
|
|
258
|
+
"raw_missing": raw_status["missing"],
|
|
259
|
+
})
|
|
260
|
+
# Sub-components (e.g. workers/) — surface as <slug>/<sub>
|
|
261
|
+
for nested in sorted(sub.iterdir()):
|
|
262
|
+
if nested.is_dir() and not nested.name.startswith("_") and nested.name not in PROJECT_TOPIC_DIRS:
|
|
263
|
+
nested_count = sum(1 for _ in nested.glob("*.md"))
|
|
264
|
+
if nested_count:
|
|
265
|
+
per_project.append({
|
|
266
|
+
"slug": f"{sub.name}/{nested.name}",
|
|
267
|
+
"md_files": nested_count,
|
|
268
|
+
})
|
|
269
|
+
status["projects"] = per_project
|
|
270
|
+
status["public_seed"] = validate_public_seed()
|
|
271
|
+
return status
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _raw_status(project_dir: Path) -> dict:
|
|
275
|
+
"""Return raw-lane presence for a project directory."""
|
|
276
|
+
raw = project_dir / "raw"
|
|
277
|
+
missing = [
|
|
278
|
+
name for name in PROJECT_RAW_SUBDIRS
|
|
279
|
+
if not (raw / name).is_dir()
|
|
280
|
+
]
|
|
281
|
+
return {"exists": raw.is_dir() and not missing, "missing": missing}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def seed_template(src: Path, dest: Path, force: bool = False) -> tuple[bool, str]:
|
|
285
|
+
"""Copy a template if the destination doesn't exist (or force=True). Returns (wrote, reason)."""
|
|
286
|
+
if dest.exists() and not force:
|
|
287
|
+
return False, "already exists"
|
|
288
|
+
if not src.exists():
|
|
289
|
+
return False, f"template missing: {src}"
|
|
290
|
+
shutil.copy2(src, dest)
|
|
291
|
+
return True, "seeded"
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _project_relpath(project: str) -> Path:
|
|
295
|
+
if _safe_project_relpath is None:
|
|
296
|
+
raise ValueError("_paths._safe_project_relpath unavailable")
|
|
297
|
+
return _safe_project_relpath(project)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def ensure_project_scaffold(dest: Path, project: str) -> Path:
|
|
301
|
+
"""Create the per-project raw-source scaffold and return the project dir."""
|
|
302
|
+
project_dir = dest / "projects" / _project_relpath(project)
|
|
303
|
+
raw_dir = project_dir / "raw"
|
|
304
|
+
for subdir in PROJECT_RAW_SUBDIRS:
|
|
305
|
+
leaf = raw_dir / subdir
|
|
306
|
+
leaf.mkdir(parents=True, exist_ok=True)
|
|
307
|
+
keep = leaf / KEEP_FILENAME
|
|
308
|
+
if not keep.exists():
|
|
309
|
+
keep.write_text("", encoding="utf-8")
|
|
310
|
+
return project_dir
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def link_private_repo(dest: Path, repo_url: str) -> tuple[bool, str]:
|
|
314
|
+
"""Clone a private repo INTO the dest path. Requires dest to be empty or non-existent.
|
|
315
|
+
|
|
316
|
+
The repo is expected to contain user-specific memory content (feedback_*.md, pattern_*.md, etc.).
|
|
317
|
+
Templates are NOT seeded when linking — the repo provides the actual content.
|
|
318
|
+
"""
|
|
319
|
+
if dest.exists() and any(dest.iterdir()):
|
|
320
|
+
return False, (
|
|
321
|
+
f"{dest} exists and is non-empty. Move existing content out, then re-run with --link-repo."
|
|
322
|
+
)
|
|
323
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
324
|
+
try:
|
|
325
|
+
subprocess.run(
|
|
326
|
+
["git", "clone", repo_url, str(dest)],
|
|
327
|
+
check=True,
|
|
328
|
+
capture_output=True,
|
|
329
|
+
)
|
|
330
|
+
except subprocess.CalledProcessError as e:
|
|
331
|
+
return False, f"git clone failed: {e.stderr.decode().strip() or e}"
|
|
332
|
+
return True, f"cloned {repo_url} → {dest}"
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
336
|
+
p = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
|
337
|
+
p.add_argument("--dest", default=str(DEFAULT_DEST),
|
|
338
|
+
help=f"Memory directory location (default: {DEFAULT_DEST})")
|
|
339
|
+
p.add_argument("--check", action="store_true",
|
|
340
|
+
help="Report status without writing")
|
|
341
|
+
p.add_argument("--guided", action="store_true",
|
|
342
|
+
help="Terminal-first guided install: validate packaged seed, bootstrap, and print next steps.")
|
|
343
|
+
p.add_argument("--validate-seed", action="store_true",
|
|
344
|
+
help="Validate the packaged public memory seed without writing")
|
|
345
|
+
p.add_argument("--json", action="store_true",
|
|
346
|
+
help="Emit machine-readable JSON for --check or --validate-seed")
|
|
347
|
+
p.add_argument("--link-repo", default=None,
|
|
348
|
+
help="Clone a private git repo into the dest (for versioned user content). "
|
|
349
|
+
"Skips template seeding — the repo provides content.")
|
|
350
|
+
p.add_argument("--force", action="store_true",
|
|
351
|
+
help="Overwrite existing template files (dangerous; use only on fresh install)")
|
|
352
|
+
p.add_argument("--ensure-project", action="append", default=[],
|
|
353
|
+
help="Create projects/<slug>/raw/ lane folders with .gitkeep files. "
|
|
354
|
+
"May be passed more than once.")
|
|
355
|
+
args = p.parse_args(argv)
|
|
356
|
+
|
|
357
|
+
dest = Path(args.dest).expanduser().resolve()
|
|
358
|
+
|
|
359
|
+
if args.json and not (args.check or args.validate_seed):
|
|
360
|
+
print("install_memory: --json is only supported with --check or --validate-seed", file=sys.stderr)
|
|
361
|
+
return 2
|
|
362
|
+
|
|
363
|
+
if args.guided and args.link_repo:
|
|
364
|
+
print("install_memory: --guided and --link-repo are separate paths; run one at a time", file=sys.stderr)
|
|
365
|
+
return 2
|
|
366
|
+
|
|
367
|
+
if args.validate_seed:
|
|
368
|
+
validation = validate_public_seed()
|
|
369
|
+
if args.json:
|
|
370
|
+
print(json.dumps(validation, indent=2, sort_keys=True))
|
|
371
|
+
else:
|
|
372
|
+
print_seed_validation(validation)
|
|
373
|
+
return 0 if validation.get("ok") else 1
|
|
374
|
+
|
|
375
|
+
if args.check:
|
|
376
|
+
status = report(dest)
|
|
377
|
+
if args.json:
|
|
378
|
+
print(json.dumps(status, indent=2, sort_keys=True))
|
|
379
|
+
return 0
|
|
380
|
+
print(f"dest: {status['dest']}")
|
|
381
|
+
print(f" exists: {status['exists']}")
|
|
382
|
+
print(f" is_git_repo: {status['is_git_repo']}")
|
|
383
|
+
for name, info in status["files"].items():
|
|
384
|
+
present = "✓" if info["exists"] else "✗"
|
|
385
|
+
print(f" {present} {name} ({info['size_bytes']}B)")
|
|
386
|
+
if "user_md_files" in status:
|
|
387
|
+
print(f" global content: {status['user_md_files']} .md files beyond templates")
|
|
388
|
+
projects_present = "✓" if status.get("projects_dir_exists") else "✗"
|
|
389
|
+
indexes_present = "✓" if status.get("indexes_dir_exists") else "✗"
|
|
390
|
+
print(f" {indexes_present} indexes/ subtree")
|
|
391
|
+
print(f" {projects_present} projects/ subtree")
|
|
392
|
+
if status.get("projects"):
|
|
393
|
+
for entry in status["projects"]:
|
|
394
|
+
if "raw_exists" not in entry:
|
|
395
|
+
raw = "raw n/a"
|
|
396
|
+
else:
|
|
397
|
+
raw = "raw ok" if entry.get("raw_exists") else (
|
|
398
|
+
"raw missing: " + ",".join(entry.get("raw_missing") or [])
|
|
399
|
+
)
|
|
400
|
+
print(f" - {entry['slug']}: {entry['md_files']} file(s), {raw}")
|
|
401
|
+
elif status.get("projects_dir_exists"):
|
|
402
|
+
print(" (no project subdirs yet — populated by migration script in PR 1.5)")
|
|
403
|
+
print_seed_validation(status["public_seed"])
|
|
404
|
+
return 0
|
|
405
|
+
|
|
406
|
+
# Link-repo path: clone instead of seeding
|
|
407
|
+
if args.link_repo:
|
|
408
|
+
ok, reason = link_private_repo(dest, args.link_repo)
|
|
409
|
+
if not ok:
|
|
410
|
+
print(f"install_memory: {reason}", file=sys.stderr)
|
|
411
|
+
return 1
|
|
412
|
+
print(f"install_memory: {reason}")
|
|
413
|
+
# Verify templates landed via the repo (warn if not)
|
|
414
|
+
for _, target_name in TEMPLATES:
|
|
415
|
+
if not (dest / target_name).exists():
|
|
416
|
+
print(f" ⚠️ expected file not in cloned repo: {target_name}", file=sys.stderr)
|
|
417
|
+
return 0
|
|
418
|
+
|
|
419
|
+
seed_validation = validate_public_seed()
|
|
420
|
+
if not seed_validation.get("ok"):
|
|
421
|
+
print_seed_validation(seed_validation)
|
|
422
|
+
print("install_memory: packaged public seed failed validation; refusing to install templates", file=sys.stderr)
|
|
423
|
+
return 1
|
|
424
|
+
|
|
425
|
+
if args.guided and not args.json:
|
|
426
|
+
print("Build-loop memory guided install")
|
|
427
|
+
print(f" dest: {dest}")
|
|
428
|
+
print(" source: packaged public seed only; no personal memory content is copied")
|
|
429
|
+
print()
|
|
430
|
+
print_seed_validation(seed_validation)
|
|
431
|
+
print()
|
|
432
|
+
|
|
433
|
+
# Standard bootstrap: mkdir + seed templates
|
|
434
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
435
|
+
print(f"install_memory: dest = {dest}")
|
|
436
|
+
tpl_dir = template_dir()
|
|
437
|
+
if not tpl_dir.exists():
|
|
438
|
+
print(f"install_memory: template directory missing: {tpl_dir}", file=sys.stderr)
|
|
439
|
+
return 1
|
|
440
|
+
|
|
441
|
+
for template_name, target_name in TEMPLATES:
|
|
442
|
+
src = tpl_dir / template_name
|
|
443
|
+
target = dest / target_name
|
|
444
|
+
wrote, reason = seed_template(src, target, force=args.force)
|
|
445
|
+
status = "✓ seeded" if wrote else f"– skipped ({reason})"
|
|
446
|
+
print(f" {status}: {target_name}")
|
|
447
|
+
|
|
448
|
+
indexes_dir = dest / "indexes"
|
|
449
|
+
if not indexes_dir.exists():
|
|
450
|
+
indexes_dir.mkdir(parents=True, exist_ok=True)
|
|
451
|
+
print(" ✓ created: indexes/")
|
|
452
|
+
else:
|
|
453
|
+
print(" – skipped: indexes/ (already exists)")
|
|
454
|
+
|
|
455
|
+
# Bootstrap the project-segmentation subtree (PR 1).
|
|
456
|
+
projects_dir = dest / "projects"
|
|
457
|
+
projects_readme = projects_dir / "README.md"
|
|
458
|
+
if not projects_dir.exists():
|
|
459
|
+
projects_dir.mkdir(parents=True, exist_ok=True)
|
|
460
|
+
print(" ✓ created: projects/")
|
|
461
|
+
else:
|
|
462
|
+
print(" – skipped: projects/ (already exists)")
|
|
463
|
+
if not projects_readme.exists():
|
|
464
|
+
projects_readme.write_text(_PROJECTS_README_BODY, encoding="utf-8")
|
|
465
|
+
print(" ✓ seeded: projects/README.md")
|
|
466
|
+
else:
|
|
467
|
+
print(" – skipped: projects/README.md (already exists)")
|
|
468
|
+
|
|
469
|
+
for project in args.ensure_project:
|
|
470
|
+
try:
|
|
471
|
+
project_dir = ensure_project_scaffold(dest, project)
|
|
472
|
+
except ValueError as exc:
|
|
473
|
+
print(f"install_memory: invalid project tag {project!r}: {exc}", file=sys.stderr)
|
|
474
|
+
return 2
|
|
475
|
+
print(f" ✓ ensured: {project_dir.relative_to(dest)}/raw/")
|
|
476
|
+
|
|
477
|
+
print()
|
|
478
|
+
print("Done. Next steps:")
|
|
479
|
+
print(f" - Edit {dest / 'constitution.md'} with your invariants")
|
|
480
|
+
print(f" - Add lessons over time as feedback_*.md / pattern_*.md / reference_*.md")
|
|
481
|
+
print(" - Optionally version this directory in a private git repo:")
|
|
482
|
+
print(f" cd {dest} && git init && git remote add origin <your-private-repo-url>")
|
|
483
|
+
print(f" - Project-scoped lessons: {dest / 'projects' / '<slug>'} (filled by migration when needed)")
|
|
484
|
+
print(f" - Raw source files: {dest / 'projects' / '<slug>' / 'raw'}")
|
|
485
|
+
print(" - See docs/memory-setup.md for full setup guide")
|
|
486
|
+
return 0
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
_PROJECTS_README_BODY = """# projects/ — project-scoped lessons
|
|
490
|
+
|
|
491
|
+
Per-project memory entries live in subdirectories of this folder:
|
|
492
|
+
|
|
493
|
+
```
|
|
494
|
+
projects/
|
|
495
|
+
├── build-loop/
|
|
496
|
+
│ ├── MEMORY.md # project-specific index (optional)
|
|
497
|
+
│ ├── constitution.md # project-specific overrides (optional)
|
|
498
|
+
│ ├── raw/ # verbatim source material
|
|
499
|
+
│ │ ├── documents/ # PDFs, docs, notes, exported pages
|
|
500
|
+
│ │ ├── data/ # JSON, YAML, TOML, CSV, XML
|
|
501
|
+
│ │ ├── db/ # schemas, migrations, SQL
|
|
502
|
+
│ │ ├── agent-artifacts/ # .build-loop/.claude/.codex/.navgator context
|
|
503
|
+
│ │ ├── files/ # reusable source files copied for reference
|
|
504
|
+
│ │ └── artifacts/ # screenshots, traces, exports
|
|
505
|
+
│ └── feedback_*.md / pattern_*.md / reference_*.md
|
|
506
|
+
├── example-project/
|
|
507
|
+
│ └── workers/ # sub-component memory (deliberate hierarchy)
|
|
508
|
+
└── _archive/
|
|
509
|
+
└── <retired-project>/ # historical memory, still queryable
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
## Slug derivation
|
|
513
|
+
|
|
514
|
+
The slug for the current working directory is derived by
|
|
515
|
+
`scripts/derive_slug_from_cwd` in build-loop:
|
|
516
|
+
|
|
517
|
+
1. Resolve symlinks (`Path.resolve()`)
|
|
518
|
+
2. Walk up looking for a `.git` directory
|
|
519
|
+
3. Slug = `basename(repo_root)` normalized (lowercase, non-safe chars → `-`)
|
|
520
|
+
4. If `cwd` is under `<repo_root>/workers/...`, append `/workers` to the slug
|
|
521
|
+
5. No `.git` ancestor → `_unscoped`
|
|
522
|
+
|
|
523
|
+
This is filesystem-driven; no hand-maintained registry. The legacy
|
|
524
|
+
`projects.yaml` (in build-loop-memory repo) remains as a fallback for
|
|
525
|
+
explicit aliases only.
|
|
526
|
+
|
|
527
|
+
## Precedence
|
|
528
|
+
|
|
529
|
+
When the orchestrator loads memory at Phase 1 Assess:
|
|
530
|
+
|
|
531
|
+
1. Global tier — files at the root of the build-loop-memory store
|
|
532
|
+
2. Project tier — files at `projects/<slug>/` under that store
|
|
533
|
+
|
|
534
|
+
Later tiers OVERRIDE earlier ones on filename collision (project wins
|
|
535
|
+
over global).
|
|
536
|
+
|
|
537
|
+
Raw source material belongs under `projects/<slug>/raw/` and stays verbatim.
|
|
538
|
+
Use `documents/` for human-readable source documents, `data/` for structured
|
|
539
|
+
data/config, `db/` for schemas and migrations, `agent-artifacts/` for local
|
|
540
|
+
agent context, `files/` for reusable source files, and `artifacts/` for
|
|
541
|
+
screenshots, traces, and exports.
|
|
542
|
+
|
|
543
|
+
Historical: a legacy per-repo tier (`<repo>/.build-loop/memory/`) was
|
|
544
|
+
read during the PR 1/2 transition. PR 3 (2026-05-13) removed that
|
|
545
|
+
read-shim; the consolidated tree is now the only readable location.
|
|
546
|
+
|
|
547
|
+
## Privacy
|
|
548
|
+
|
|
549
|
+
Same as the root: this directory should be in a private git repo. The
|
|
550
|
+
build-loop public repo ships templates + the projects/ scaffolding only,
|
|
551
|
+
not the user-specific content.
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
if __name__ == "__main__":
|
|
556
|
+
sys.exit(main())
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
"""lessons_index — portable SQLite/FTS5 lessons index for build-loop.
|
|
5
|
+
|
|
6
|
+
Zero external dependencies. Works out-of-box on a fresh install.
|
|
7
|
+
Postgres recall.py remains the optional power-tier; this is the PRIMARY.
|
|
8
|
+
|
|
9
|
+
Public API:
|
|
10
|
+
ingest(project, db_path) — walk memory lanes, upsert markdown → SQLite
|
|
11
|
+
query(goal_text, project, limit, db_path) — BM25 + optional cosine rerank
|
|
12
|
+
stats(db_path) — counts + schema_version + last_ingest_ts
|
|
13
|
+
open_db(db_path) — connection helper (creates schema if needed)
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
_PKG_DIR = Path(__file__).resolve().parent
|
|
21
|
+
for _p in (str(_PKG_DIR), str(_PKG_DIR.parent)):
|
|
22
|
+
if _p not in sys.path:
|
|
23
|
+
sys.path.insert(0, _p)
|
|
24
|
+
|
|
25
|
+
import importlib.util as _iutil # noqa: E402
|
|
26
|
+
|
|
27
|
+
def _load_pkg_module(name: str):
|
|
28
|
+
spec = _iutil.spec_from_file_location(
|
|
29
|
+
f"lessons_index._{name}", _PKG_DIR / f"{name}.py"
|
|
30
|
+
)
|
|
31
|
+
mod = _iutil.module_from_spec(spec)
|
|
32
|
+
spec.loader.exec_module(mod)
|
|
33
|
+
return mod
|
|
34
|
+
|
|
35
|
+
_schema = _load_pkg_module("schema")
|
|
36
|
+
_ingest = _load_pkg_module("ingest")
|
|
37
|
+
_query = _load_pkg_module("query")
|
|
38
|
+
|
|
39
|
+
open_db = _schema.open_db
|
|
40
|
+
ingest = _ingest.ingest
|
|
41
|
+
query = _query.query
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def stats(db_path=None) -> dict:
|
|
45
|
+
"""Return a stats dict: {total_facts, schema_version, last_ingest_ts}.
|
|
46
|
+
|
|
47
|
+
Safe to call on a non-existent DB (returns zeros).
|
|
48
|
+
"""
|
|
49
|
+
import sqlite3 # noqa: PLC0415
|
|
50
|
+
from pathlib import Path as _Path # noqa: PLC0415
|
|
51
|
+
|
|
52
|
+
if db_path is None:
|
|
53
|
+
try:
|
|
54
|
+
from _paths import memory_store_root # type: ignore # noqa: PLC0415
|
|
55
|
+
db_path = memory_store_root() / "indexes" / "lessons_index.db"
|
|
56
|
+
except ImportError:
|
|
57
|
+
return {"total_facts": 0, "schema_version": None, "last_ingest_ts": None}
|
|
58
|
+
|
|
59
|
+
db_path = _Path(db_path)
|
|
60
|
+
if not db_path.exists():
|
|
61
|
+
return {"total_facts": 0, "schema_version": None, "last_ingest_ts": None}
|
|
62
|
+
|
|
63
|
+
conn = open_db(db_path)
|
|
64
|
+
try:
|
|
65
|
+
total = conn.execute("SELECT COUNT(*) FROM facts").fetchone()[0]
|
|
66
|
+
meta_rows = conn.execute("SELECT key, value FROM meta").fetchall()
|
|
67
|
+
meta = {r["key"]: r["value"] for r in meta_rows}
|
|
68
|
+
return {
|
|
69
|
+
"total_facts": total,
|
|
70
|
+
"schema_version": meta.get("schema_version"),
|
|
71
|
+
"last_ingest_ts": meta.get("last_ingest_ts"),
|
|
72
|
+
}
|
|
73
|
+
finally:
|
|
74
|
+
conn.close()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
__all__ = ["ingest", "query", "stats", "open_db"]
|