@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,226 @@
|
|
|
1
|
+
# Memory Setup
|
|
2
|
+
|
|
3
|
+
Build-loop's advisory judges and the Phase 1 Assess memory-load step read from
|
|
4
|
+
one consolidated tree under `~/dev/git-folder/build-loop-memory/` by default:
|
|
5
|
+
|
|
6
|
+
| Tier | Location | Owner | Versioning |
|
|
7
|
+
|---|---|---|---|
|
|
8
|
+
| Global | `~/dev/git-folder/build-loop-memory/` plus top-level lanes such as `lessons/` | This user | **Should be in a private git repo** (your lessons live here) |
|
|
9
|
+
| Project | `~/dev/git-folder/build-loop-memory/projects/<slug>/` (slug derived via `scripts/_paths.derive_slug_from_cwd` — basename of the git repo root, lowercased + normalized; `workers/` sub-component becomes `<slug>/workers`) | This user | Same private repo as global |
|
|
10
|
+
|
|
11
|
+
> **History** — until PR 3 of the memory-consolidation series (merged 2026-05-13), the legacy per-repo location was also read by `memory_facade._resolve_memory_dirs` as a transitional shim. As of PR 3, only the consolidated tree is read; any pre-migration content still at the legacy path is invisible. Operators with such content should run `scripts/migrate_project_memory.py --apply` (idempotent), then `scripts/cleanup_legacy_memory_stubs.py --apply` to remove the now-inert `.MOVED.md` stubs.
|
|
12
|
+
|
|
13
|
+
The build-loop public repo ships only the **scaffolding** — templates, a public
|
|
14
|
+
seed manifest, and the setup script. Your actual lessons, constitution rules,
|
|
15
|
+
patterns, project plans, raw artifacts, generated indexes, and decisions belong
|
|
16
|
+
in a private repo because they contain references to specific projects and
|
|
17
|
+
operator preferences that are not appropriate for public distribution.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Guided terminal install. This validates the packaged public seed first, then
|
|
23
|
+
# creates ~/dev/git-folder/build-loop-memory/ with scaffold-only files.
|
|
24
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/install_memory.py --guided
|
|
25
|
+
|
|
26
|
+
# Bootstrap with templates (creates ~/dev/git-folder/build-loop-memory/ if missing,
|
|
27
|
+
# seeds constitution.md + MEMORY.md from templates/memory/, plus indexes/ and projects/)
|
|
28
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/install_memory.py
|
|
29
|
+
|
|
30
|
+
# Check status anytime
|
|
31
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/install_memory.py --check
|
|
32
|
+
|
|
33
|
+
# Validate the shipped public seed without writing
|
|
34
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/install_memory.py --validate-seed
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This creates:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
~/dev/git-folder/build-loop-memory/
|
|
41
|
+
├── constitution.md # template — replace with your invariants
|
|
42
|
+
├── MEMORY.md # template — index for entries you add
|
|
43
|
+
├── indexes/ # rebuildable local indexes, including semantic_facts.sqlite
|
|
44
|
+
│ └── updates.jsonl # created on first memory mutation; global update ledger
|
|
45
|
+
└── projects/ # project-scoped memory trees
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Packaged public seed
|
|
49
|
+
|
|
50
|
+
The install package includes `templates/memory/manifest.json`, which is the
|
|
51
|
+
contract for the public `build-loop-memory` seed. It allowlists only generic
|
|
52
|
+
template files and generated empty directories:
|
|
53
|
+
|
|
54
|
+
- `constitution.md.template` -> `constitution.md`
|
|
55
|
+
- `MEMORY.md.template` -> `MEMORY.md`
|
|
56
|
+
- `indexes/`
|
|
57
|
+
- `projects/README.md`
|
|
58
|
+
- empty `projects/<slug>/raw/.../.gitkeep` lanes when `--ensure-project` is used
|
|
59
|
+
|
|
60
|
+
The seed validator refuses unlisted template files and scans the shipped
|
|
61
|
+
templates for common secret, personal-path, private-repo, and operator-identity
|
|
62
|
+
patterns. This protects the install package from accidentally shipping a
|
|
63
|
+
developer's private `build-loop-memory` contents.
|
|
64
|
+
|
|
65
|
+
## Linking to a private repo
|
|
66
|
+
|
|
67
|
+
You have a few options for versioning the global memory. The recommended one is **clone a private repo into the memory dir**:
|
|
68
|
+
|
|
69
|
+
### Option A — Clone a private repo at install time
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Empty / non-existent build-loop-memory directory required
|
|
73
|
+
python3 scripts/install_memory.py --link-repo git@github.com:<you>/<your-memory-repo>.git
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The repo should contain `constitution.md` and `MEMORY.md` plus your existing lessons. The install script will clone it directly; no templates are seeded (the repo provides content).
|
|
77
|
+
|
|
78
|
+
### Option B — Bootstrap with templates, then init a repo
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
python3 scripts/install_memory.py
|
|
82
|
+
cd ~/dev/git-folder/build-loop-memory
|
|
83
|
+
git init
|
|
84
|
+
git add constitution.md MEMORY.md
|
|
85
|
+
git commit -m "init: build-loop memory scaffolding"
|
|
86
|
+
git remote add origin git@github.com:<you>/<your-memory-repo>.git
|
|
87
|
+
git push -u origin main
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Option C — Override the canonical root
|
|
91
|
+
|
|
92
|
+
If your private memory repo lives somewhere else, set
|
|
93
|
+
`BUILD_LOOP_MEMORY_STORE_ROOT` or pass `--dest <path>` to the setup script. The
|
|
94
|
+
scripts resolve the root through `scripts/_paths.py`.
|
|
95
|
+
|
|
96
|
+
### Option D — Use a repo-local ignored store
|
|
97
|
+
|
|
98
|
+
For experiments or repos that should carry their own local memory without a
|
|
99
|
+
separate private repo, set the root to an ignored path such as:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
export BUILD_LOOP_MEMORY_STORE_ROOT="$PWD/.build-loop/memory"
|
|
103
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/install_memory.py --dest "$BUILD_LOOP_MEMORY_STORE_ROOT"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This is supported because build-loop owns the writer scripts and schema, while
|
|
107
|
+
the memory root is just data. Keep `.build-loop/` ignored if the contents should
|
|
108
|
+
not be committed.
|
|
109
|
+
|
|
110
|
+
## What goes in the global store
|
|
111
|
+
|
|
112
|
+
Add entries over time as you accumulate lessons:
|
|
113
|
+
|
|
114
|
+
| Type | Filename pattern | When to add |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `feedback` | `feedback_<slug>.md` | After a user correction OR a validated success worth remembering |
|
|
117
|
+
| `pattern` | `pattern_<slug>.md` | Recurring solution worth naming |
|
|
118
|
+
| `reference` | `reference_<slug>.md` | Durable facts about external systems / topologies |
|
|
119
|
+
| `decision` | `decision_<slug>.md` | One-shot architectural decisions |
|
|
120
|
+
| `user` | `user_<slug>.md` | User-profile facts that shape collaboration style |
|
|
121
|
+
|
|
122
|
+
Each file uses YAML frontmatter:
|
|
123
|
+
|
|
124
|
+
```markdown
|
|
125
|
+
---
|
|
126
|
+
name: <kebab-case-slug>
|
|
127
|
+
description: <one-sentence summary for relevance scoring>
|
|
128
|
+
metadata:
|
|
129
|
+
type: feedback | pattern | reference | decision | user
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
# Title
|
|
133
|
+
|
|
134
|
+
Body content. Link related entries with [[other-slug]].
|
|
135
|
+
|
|
136
|
+
For feedback / pattern entries, structure as:
|
|
137
|
+
- The rule or fact
|
|
138
|
+
- **Why:** the rationale
|
|
139
|
+
- **How to apply:** when/where the rule fires
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`MEMORY.md` is the index. Each entry there is one line:
|
|
143
|
+
|
|
144
|
+
```markdown
|
|
145
|
+
- [Title](filename.md) — one-line hook for relevance matching
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Keep `MEMORY.md` under ~200 lines; entries past that get truncated when loaded into orchestrator context.
|
|
149
|
+
|
|
150
|
+
## What about decisions?
|
|
151
|
+
|
|
152
|
+
The **canonical decision store** is
|
|
153
|
+
`~/dev/git-folder/build-loop-memory/projects/<project>/decisions/`. It is
|
|
154
|
+
project-tagged and written by `scripts/write_decision.py` — it captures the
|
|
155
|
+
discrete "we decided X" events of any project.
|
|
156
|
+
|
|
157
|
+
The top-level `lessons/` lane is for **cross-project lessons** — patterns and
|
|
158
|
+
feedback that apply broadly, not project-specific decisions.
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
~/dev/git-folder/build-loop-memory/ # canonical root
|
|
162
|
+
~/dev/git-folder/build-loop-memory/lessons/ # cross-project lessons
|
|
163
|
+
~/dev/git-folder/build-loop-memory/indexes/updates.jsonl # global update ledger
|
|
164
|
+
~/dev/git-folder/build-loop-memory/projects/<slug>/ # project-local memory
|
|
165
|
+
~/dev/git-folder/build-loop-memory/projects/_archive/<slug>/ # retired projects, still queryable
|
|
166
|
+
<repo>/.build-loop/memory/ # legacy project location — no longer read (PR 3 removed the shim); migrate via scripts/migrate_project_memory.py
|
|
167
|
+
<repo>/.episodic/decisions/ # legacy local decision store (migration/archive)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Global update ledger
|
|
171
|
+
|
|
172
|
+
Build-loop records every canonical memory mutation in
|
|
173
|
+
`<memory-root>/indexes/updates.jsonl`. The ledger is append-only JSONL and is
|
|
174
|
+
written by the plugin scripts:
|
|
175
|
+
|
|
176
|
+
- `scripts/memory_writer.py` for lessons and provenance updates
|
|
177
|
+
- `scripts/write_decision.py` for project decisions and supersession
|
|
178
|
+
- `scripts/append_milestone.py` for run-level milestones
|
|
179
|
+
|
|
180
|
+
Minimum useful fields are `ts`, `project`, `lane`, `action`, `path`, `writer`,
|
|
181
|
+
and `source_commit` when the update is tied to a repo HEAD. Freshness checks use
|
|
182
|
+
the latest project ledger row with `source_commit`; if no row exists, they fall
|
|
183
|
+
back to `projects/<slug>/milestones.jsonl`.
|
|
184
|
+
|
|
185
|
+
Read recent updates:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
python3 scripts/memory_update_ledger.py tail --project build-loop --limit 20
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Constitution rules
|
|
192
|
+
|
|
193
|
+
`constitution.md` is the highest-priority store — loaded at every Phase 1 Assess and cited by advisory judges. Keep it concise:
|
|
194
|
+
|
|
195
|
+
- **Stable rule IDs** (`C-SEC/no_secret_in_repo`, `C-AUTH/owner_only_writes`) — never renumber
|
|
196
|
+
- **One sentence per rule** — detail goes in linked memory entries
|
|
197
|
+
- **Under 200 lines total** — judges re-read this on every Phase 3 checkpoint
|
|
198
|
+
|
|
199
|
+
Edit the template that the install script seeded — the prefilled rules are reasonable defaults but your invariants may differ.
|
|
200
|
+
|
|
201
|
+
## Verifying setup
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Check status
|
|
205
|
+
python3 scripts/install_memory.py --check
|
|
206
|
+
|
|
207
|
+
# Test memory recall (will exit 0 even if backends are down)
|
|
208
|
+
python3 scripts/memory_facade.py recall --query "test" --limit 3
|
|
209
|
+
|
|
210
|
+
# Backend health probe
|
|
211
|
+
python3 scripts/backend_health.py
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
If any of these fail, see `references/memory-systems.md` for the full backend topology and graceful-degradation contract.
|
|
215
|
+
|
|
216
|
+
## Privacy
|
|
217
|
+
|
|
218
|
+
Treat `~/dev/git-folder/build-loop-memory/` as containing potentially-sensitive context:
|
|
219
|
+
|
|
220
|
+
- Operator preferences, project-specific decisions, client names if you write them in
|
|
221
|
+
- Constitution rules may reveal architectural patterns from past projects
|
|
222
|
+
- Feedback entries often quote real exchanges
|
|
223
|
+
|
|
224
|
+
The recommendation is a **private git repo** (`Option A` or `B` above), not the
|
|
225
|
+
public build-loop repo. Build-loop ships only the manifest-backed public seed
|
|
226
|
+
and empty scaffolding.
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
"""Background scan worker — invoked by both hook scripts.
|
|
5
|
+
|
|
6
|
+
Acquires a non-blocking ``fcntl.flock`` on
|
|
7
|
+
``.build-loop/architecture/.scan.lock`` and runs scan + acp + mark-fresh.
|
|
8
|
+
|
|
9
|
+
Exits silently if the lock is held; never raises.
|
|
10
|
+
|
|
11
|
+
Intended to be called as::
|
|
12
|
+
|
|
13
|
+
nohup python3 hooks/_arch_scan_bg.py --workdir <path> </dev/null \\
|
|
14
|
+
>/dev/null 2>&1 &
|
|
15
|
+
|
|
16
|
+
Why a separate file: putting the Python payload in a heredoc inside a shell
|
|
17
|
+
hook conflicts with the `</dev/null` redirection used to fully detach the
|
|
18
|
+
backgrounded process — the second stdin redirection wins, and the heredoc is
|
|
19
|
+
never delivered to the interpreter.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import fcntl
|
|
26
|
+
import os
|
|
27
|
+
import subprocess
|
|
28
|
+
import sys
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main(argv: list | None = None) -> int:
|
|
33
|
+
p = argparse.ArgumentParser()
|
|
34
|
+
p.add_argument("--workdir", required=True)
|
|
35
|
+
args = p.parse_args(argv)
|
|
36
|
+
|
|
37
|
+
workdir = Path(args.workdir).resolve()
|
|
38
|
+
lockpath = workdir / ".build-loop" / "architecture" / ".scan.lock"
|
|
39
|
+
script = workdir / "scripts" / "architecture_freshness.py"
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
lockpath.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
lf = open(lockpath, "a+")
|
|
44
|
+
fcntl.flock(lf.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
45
|
+
except (BlockingIOError, OSError):
|
|
46
|
+
return 0 # another scan is in flight; bail silently
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
env = os.environ.copy()
|
|
50
|
+
for cmd in (
|
|
51
|
+
["uv", "run", "python", "-m", "build_loop.architecture", "scan", "--incremental"],
|
|
52
|
+
["uv", "run", "python", "-m", "build_loop.architecture", "acp"],
|
|
53
|
+
):
|
|
54
|
+
try:
|
|
55
|
+
subprocess.run(
|
|
56
|
+
cmd,
|
|
57
|
+
cwd=str(workdir),
|
|
58
|
+
stdout=subprocess.DEVNULL,
|
|
59
|
+
stderr=subprocess.DEVNULL,
|
|
60
|
+
env=env,
|
|
61
|
+
check=False,
|
|
62
|
+
timeout=120,
|
|
63
|
+
)
|
|
64
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
if script.exists():
|
|
68
|
+
try:
|
|
69
|
+
subprocess.run(
|
|
70
|
+
[sys.executable, str(script), "--mark-fresh", "--workdir", str(workdir)],
|
|
71
|
+
stdout=subprocess.DEVNULL,
|
|
72
|
+
stderr=subprocess.DEVNULL,
|
|
73
|
+
check=False,
|
|
74
|
+
timeout=10,
|
|
75
|
+
)
|
|
76
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
77
|
+
pass
|
|
78
|
+
finally:
|
|
79
|
+
try:
|
|
80
|
+
fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
|
|
81
|
+
lf.close()
|
|
82
|
+
except Exception:
|
|
83
|
+
pass
|
|
84
|
+
return 0
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
"""Background plugin-cache drift detector. Compares cached plugin install
|
|
5
|
+
SHA against the source repo's main HEAD; writes markers under
|
|
6
|
+
~/.claude/plugins/.drift-markers/ for Phase 1 Assess to surface.
|
|
7
|
+
Fire-and-forget; fcntl single-flight; per-plugin 24h freshness gate."""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
import argparse, fcntl, json, subprocess, time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _j(p):
|
|
14
|
+
try: return json.loads(Path(p).read_text())
|
|
15
|
+
except Exception: return None
|
|
16
|
+
|
|
17
|
+
def _url(s):
|
|
18
|
+
if not isinstance(s, dict): return None
|
|
19
|
+
if s.get("source") == "github" and s.get("repo"): return f"https://github.com/{s['repo']}"
|
|
20
|
+
if s.get("source") == "git" and s.get("url"): return s["url"]
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
def _sha(url, t=8):
|
|
24
|
+
try:
|
|
25
|
+
r = subprocess.run(["git","ls-remote",url,"refs/heads/main"],
|
|
26
|
+
capture_output=True, text=True, timeout=t, check=False)
|
|
27
|
+
if r.returncode == 0 and r.stdout.strip(): return r.stdout.split()[0]
|
|
28
|
+
except (FileNotFoundError, subprocess.TimeoutExpired): pass
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main(argv=None):
|
|
33
|
+
ap = argparse.ArgumentParser()
|
|
34
|
+
ap.add_argument("--workdir", default=str(Path.home() / ".claude" / "plugins"))
|
|
35
|
+
ap.add_argument("--force", action="store_true")
|
|
36
|
+
a = ap.parse_args(argv)
|
|
37
|
+
root = Path(a.workdir).resolve()
|
|
38
|
+
plugins = (_j(root/"installed_plugins.json") or {}).get("plugins", {}) or {}
|
|
39
|
+
markets = _j(root/"known_marketplaces.json") or {}
|
|
40
|
+
if not plugins: return 0
|
|
41
|
+
|
|
42
|
+
md = root/".drift-markers"; md.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
try:
|
|
44
|
+
lf = open(md/".check.lock", "a+"); fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
|
|
45
|
+
except (BlockingIOError, OSError): return 0
|
|
46
|
+
try:
|
|
47
|
+
now = time.time()
|
|
48
|
+
for key, entries in plugins.items():
|
|
49
|
+
if not entries or "@" not in key: continue
|
|
50
|
+
name, mkt = key.split("@", 1)
|
|
51
|
+
loc = (markets.get(mkt) or {}).get("installLocation")
|
|
52
|
+
src = None
|
|
53
|
+
if loc:
|
|
54
|
+
for pl in (_j(Path(loc)/".claude-plugin"/"marketplace.json") or {}).get("plugins", []) or []:
|
|
55
|
+
if pl.get("name") == name: src = _url(pl.get("source") or {}); break
|
|
56
|
+
if not src: continue
|
|
57
|
+
ip = Path(entries[0].get("installPath") or "")
|
|
58
|
+
if not ip.exists(): continue
|
|
59
|
+
pj = ip/".claude-plugin"/"plugin.json"
|
|
60
|
+
if not pj.exists(): pj = ip/"plugin.json"
|
|
61
|
+
cv = (_j(pj) or {}).get("version")
|
|
62
|
+
csha = entries[0].get("version")
|
|
63
|
+
mp = md/f"{name}__{mkt}.json"
|
|
64
|
+
if not a.force and mp.exists():
|
|
65
|
+
try:
|
|
66
|
+
if now - mp.stat().st_mtime < 86400: continue
|
|
67
|
+
except OSError: pass
|
|
68
|
+
msha = _sha(src)
|
|
69
|
+
if not msha: continue
|
|
70
|
+
drifted = bool(csha) and not msha.startswith(str(csha))
|
|
71
|
+
if drifted:
|
|
72
|
+
mp.write_text(json.dumps({"plugin": name, "marketplace": mkt, "source": src,
|
|
73
|
+
"cached_version": cv, "cached_sha": csha, "main_sha": msha,
|
|
74
|
+
"commits_behind": None, "drifted": True, "last_checked": int(now)}, indent=2))
|
|
75
|
+
elif mp.exists():
|
|
76
|
+
try: mp.unlink()
|
|
77
|
+
except OSError: pass
|
|
78
|
+
finally:
|
|
79
|
+
try: fcntl.flock(lf.fileno(), fcntl.LOCK_UN); lf.close()
|
|
80
|
+
except Exception: pass
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Shared SessionStart helpers. Source, do not exec.
|
|
3
|
+
# Contract for all SessionStart hooks (see feedback_hook_design.md):
|
|
4
|
+
# command-type only, exit 0 always, silent on success, fire-and-forget,
|
|
5
|
+
# <100ms return via nohup + disown.
|
|
6
|
+
|
|
7
|
+
# Background-fire a python worker with stdin/stdout/stderr fully detached.
|
|
8
|
+
bl_fire_bg() {
|
|
9
|
+
nohup python3 "$@" </dev/null >/dev/null 2>&1 &
|
|
10
|
+
disown 2>/dev/null || true
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# Echo "yes" if $1 is missing or older than $2 seconds; "no" otherwise.
|
|
14
|
+
bl_stale() {
|
|
15
|
+
F="$1" MAX="$2" python3 - <<'PY' 2>/dev/null
|
|
16
|
+
import os, time
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
f = Path(os.environ["F"])
|
|
19
|
+
m = float(os.environ["MAX"])
|
|
20
|
+
if not f.exists():
|
|
21
|
+
print("yes")
|
|
22
|
+
else:
|
|
23
|
+
try:
|
|
24
|
+
print("yes" if time.time() - f.stat().st_mtime > m else "no")
|
|
25
|
+
except OSError:
|
|
26
|
+
print("yes")
|
|
27
|
+
PY
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Spawn a long-running daemon once; PID-file + /health probe single-flight.
|
|
31
|
+
# Args: script_path port pidfile_basename logfile_basename log_dir
|
|
32
|
+
bl_spawn_daemon() {
|
|
33
|
+
local script="$1" port="$2" pidfile="$5/$3.pid" log="$5/$4.log"
|
|
34
|
+
[ -f "$script" ] || return 0
|
|
35
|
+
if [ -f "$pidfile" ]; then
|
|
36
|
+
local pid; pid=$(cat "$pidfile" 2>/dev/null)
|
|
37
|
+
[ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && return 0
|
|
38
|
+
fi
|
|
39
|
+
curl -fsS --max-time 0.2 "http://127.0.0.1:${port}/health" >/dev/null 2>&1 && return 0
|
|
40
|
+
nohup python3 "$script" </dev/null >>"$log" 2>&1 &
|
|
41
|
+
disown 2>/dev/null || true
|
|
42
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
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
|
+
"""build-loop pre-push hook — path-agnostic push-HOLD gate.
|
|
5
|
+
|
|
6
|
+
INSTALLED VIA: ``scripts/install_git_hooks.py --install`` writes this file
|
|
7
|
+
verbatim to ``.git/hooks/pre-push`` (chmod +x). The committed source lives at
|
|
8
|
+
``hooks/git/pre-push`` so it's distributable / reviewable.
|
|
9
|
+
|
|
10
|
+
WHY THIS LIVES AT THE GIT LAYER
|
|
11
|
+
-------------------------------
|
|
12
|
+
``scripts/deployment_policy.py`` is an *app-layer* policy — anything that
|
|
13
|
+
doesn't consult it (a launchd job, a Codex shell, a stray ``git push`` from a
|
|
14
|
+
crashed automation) bypasses it. The git pre-push hook is the only place that
|
|
15
|
+
fires for EVERY push regardless of who initiated it.
|
|
16
|
+
|
|
17
|
+
CONTRACT
|
|
18
|
+
--------
|
|
19
|
+
Per ``man githooks``: pre-push receives ``<remote> <url>`` as argv, and four
|
|
20
|
+
fields per ref on stdin: ``<local_ref> <local_sha> <remote_ref> <remote_sha>``.
|
|
21
|
+
Exit non-zero → push is blocked.
|
|
22
|
+
|
|
23
|
+
This hook:
|
|
24
|
+
|
|
25
|
+
1. Locates the repo (git rev-parse) and adds ``<repo>/scripts`` to sys.path.
|
|
26
|
+
2. Imports ``push_hold.evaluate_push``.
|
|
27
|
+
3. Calls it with the stdin lines.
|
|
28
|
+
4. On ``allow``/``bypass``: exit 0 (push proceeds).
|
|
29
|
+
5. On ``block``: print the reason + release instructions to stderr, exit 1.
|
|
30
|
+
6. On ANY internal exception (broken import, syntax error, OSError): log to
|
|
31
|
+
stderr and exit 0. A broken hook MUST NOT permanently wedge the user's
|
|
32
|
+
ability to push — fail OPEN on internal errors, fail CLOSED on holds.
|
|
33
|
+
|
|
34
|
+
The block-vs-fail-open distinction is the whole reason for the try/except
|
|
35
|
+
wrapper below. Do NOT collapse it into a bare ``raise``.
|
|
36
|
+
"""
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import os
|
|
40
|
+
import subprocess
|
|
41
|
+
import sys
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _repo_root() -> Path:
|
|
46
|
+
"""Resolve the repo root via ``git rev-parse --show-toplevel``."""
|
|
47
|
+
try:
|
|
48
|
+
out = subprocess.check_output(
|
|
49
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
50
|
+
stderr=subprocess.DEVNULL,
|
|
51
|
+
text=True,
|
|
52
|
+
).strip()
|
|
53
|
+
if out:
|
|
54
|
+
return Path(out)
|
|
55
|
+
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
|
|
56
|
+
pass
|
|
57
|
+
# Fallback: walk up from the hook's location. In a normal install the
|
|
58
|
+
# hook lives at ``<repo>/.git/hooks/pre-push``; the hook's __file__ is
|
|
59
|
+
# often a real path but may be a symlink target. Walk up looking for
|
|
60
|
+
# ``.git``.
|
|
61
|
+
here = Path(__file__).resolve()
|
|
62
|
+
for parent in [here] + list(here.parents):
|
|
63
|
+
if (parent / ".git").exists():
|
|
64
|
+
return parent
|
|
65
|
+
return Path.cwd()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _fail_open(reason: str) -> int:
|
|
69
|
+
"""Internal-error path: print to stderr, return 0 (allow push)."""
|
|
70
|
+
sys.stderr.write(
|
|
71
|
+
f"[build-loop pre-push] internal error — allowing push: {reason}\n"
|
|
72
|
+
)
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _format_block_message(verdict: dict) -> str:
|
|
77
|
+
targets = ",".join(verdict.get("protected_targets") or []) or "(unknown)"
|
|
78
|
+
reason = verdict.get("reason") or "hold active"
|
|
79
|
+
source = verdict.get("source") or "unknown"
|
|
80
|
+
|
|
81
|
+
# Corrupt-marker note: help the operator self-rescue without digging into docs.
|
|
82
|
+
corrupt_note = ""
|
|
83
|
+
if source == "marker" and ("unparseable" in reason or "malformed" in reason or "not a JSON" in reason):
|
|
84
|
+
corrupt_note = (
|
|
85
|
+
" NOTE: the hold marker is corrupt (not valid JSON).\n"
|
|
86
|
+
" Run `push_hold.py --release` to clear it (works even\n"
|
|
87
|
+
" on a corrupt marker, no prior --set needed).\n"
|
|
88
|
+
" ---------------------------------------------------------------\n"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
"\n"
|
|
93
|
+
"===============================================================\n"
|
|
94
|
+
" BUILD-LOOP PUSH HOLD — push BLOCKED\n"
|
|
95
|
+
"===============================================================\n"
|
|
96
|
+
f" protected target(s): {targets}\n"
|
|
97
|
+
f" hold source : {source}\n"
|
|
98
|
+
f" reason : {reason}\n"
|
|
99
|
+
" ---------------------------------------------------------------\n"
|
|
100
|
+
+ corrupt_note
|
|
101
|
+
+ " To release this hold:\n"
|
|
102
|
+
" 1. Resolve the underlying issue (auditor findings / brief).\n"
|
|
103
|
+
" 2. Run: python3 scripts/push_hold.py --release \\\n"
|
|
104
|
+
' --reason "<one-line why>"\n'
|
|
105
|
+
" 3. Re-push.\n"
|
|
106
|
+
"\n"
|
|
107
|
+
" EMERGENCY override (logged):\n"
|
|
108
|
+
" BUILDLOOP_PUSH_HOLD_BYPASS=1 git push <remote> <branch>\n"
|
|
109
|
+
"===============================================================\n"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def main() -> int:
|
|
114
|
+
try:
|
|
115
|
+
repo = _repo_root()
|
|
116
|
+
scripts_dir = repo / "scripts"
|
|
117
|
+
if str(scripts_dir) not in sys.path:
|
|
118
|
+
sys.path.insert(0, str(scripts_dir))
|
|
119
|
+
try:
|
|
120
|
+
import push_hold # type: ignore
|
|
121
|
+
except Exception as exc: # pragma: no cover — exercised by installer test
|
|
122
|
+
return _fail_open(f"could not import push_hold: {exc!r}")
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
stdin_lines = sys.stdin.readlines()
|
|
126
|
+
except OSError as exc:
|
|
127
|
+
return _fail_open(f"could not read stdin: {exc!r}")
|
|
128
|
+
|
|
129
|
+
verdict = push_hold.evaluate_push(repo, stdin_lines, env=os.environ)
|
|
130
|
+
action = verdict.get("action", "allow")
|
|
131
|
+
if action == "block":
|
|
132
|
+
sys.stderr.write(_format_block_message(verdict))
|
|
133
|
+
return int(verdict.get("exit_code") or 1)
|
|
134
|
+
# allow / bypass / anything else → exit 0
|
|
135
|
+
return 0
|
|
136
|
+
except Exception as exc: # noqa: BLE001 — broad on purpose; we MUST fail-open
|
|
137
|
+
return _fail_open(f"unhandled exception: {exc!r}")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
raise SystemExit(main())
|