@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.
Files changed (389) hide show
  1. package/.agents/plugins/marketplace.json +10 -0
  2. package/.claude-plugin/marketplace.json +38 -0
  3. package/.claude-plugin/plugin.json +30 -0
  4. package/.codex-plugin/plugin.json +41 -0
  5. package/.cursor/rules/build-loop-surface.mdc +22 -0
  6. package/AGENTS.md +498 -0
  7. package/LICENSE +202 -0
  8. package/README.md +403 -0
  9. package/agents/alignment-checker.md +124 -0
  10. package/agents/api-assessor.md +136 -0
  11. package/agents/architecture-scout.md +185 -0
  12. package/agents/assessment-orchestrator.md +163 -0
  13. package/agents/build-orchestrator.md +198 -0
  14. package/agents/database-assessor.md +133 -0
  15. package/agents/design-contract-specialist.md +239 -0
  16. package/agents/fact-checker.md +69 -0
  17. package/agents/fix-critique.md +161 -0
  18. package/agents/frontend-assessor.md +136 -0
  19. package/agents/implementer.md +185 -0
  20. package/agents/independent-auditor.md +143 -0
  21. package/agents/mock-scanner.md +78 -0
  22. package/agents/optimize-runner.md +112 -0
  23. package/agents/overfitting-reviewer.md +96 -0
  24. package/agents/performance-assessor.md +159 -0
  25. package/agents/plan-critic.md +85 -0
  26. package/agents/promotion-reviewer.md +121 -0
  27. package/agents/recurring-pattern-detector.md +248 -0
  28. package/agents/retrospective-synthesizer.md +109 -0
  29. package/agents/root-cause-investigator.md +285 -0
  30. package/agents/scope-auditor.md +121 -0
  31. package/agents/security-reviewer.md +151 -0
  32. package/agents/self-improvement-architect.md +125 -0
  33. package/agents/synthesis-critic.md +99 -0
  34. package/agents/transcript-pattern-miner.md +165 -0
  35. package/agents/ui-validator.md +193 -0
  36. package/codex-skills/build-loop/SKILL.md +20 -0
  37. package/codex-skills/debug-loop/SKILL.md +18 -0
  38. package/codex-skills/handoff/SKILL.md +22 -0
  39. package/codex-skills/knowledge/SKILL.md +21 -0
  40. package/codex-skills/optimize/SKILL.md +18 -0
  41. package/codex-skills/research/SKILL.md +18 -0
  42. package/commands/agent-rally-point.md +301 -0
  43. package/commands/assess.md +41 -0
  44. package/commands/debug.md +20 -0
  45. package/commands/debugger-detail.md +34 -0
  46. package/commands/debugger-scan.md +15 -0
  47. package/commands/debugger-status.md +20 -0
  48. package/commands/debugger.md +32 -0
  49. package/commands/handoff.md +113 -0
  50. package/commands/knowledge-review.md +16 -0
  51. package/commands/optimize.md +31 -0
  52. package/commands/plan-verify.md +39 -0
  53. package/commands/promote-experiment.md +129 -0
  54. package/commands/research.md +22 -0
  55. package/commands/run.md +12 -0
  56. package/commands/self-review.md +51 -0
  57. package/commands/start-prd.md +57 -0
  58. package/commands/test.md +34 -0
  59. package/dist/src/assessment-orchestrator.d.ts +78 -0
  60. package/dist/src/assessment-orchestrator.d.ts.map +1 -0
  61. package/dist/src/assessment-orchestrator.js +304 -0
  62. package/dist/src/assessment-orchestrator.js.map +1 -0
  63. package/dist/src/audit-miner.d.ts +21 -0
  64. package/dist/src/audit-miner.d.ts.map +1 -0
  65. package/dist/src/audit-miner.js +421 -0
  66. package/dist/src/audit-miner.js.map +1 -0
  67. package/dist/src/batch-operations.d.ts +30 -0
  68. package/dist/src/batch-operations.d.ts.map +1 -0
  69. package/dist/src/batch-operations.js +339 -0
  70. package/dist/src/batch-operations.js.map +1 -0
  71. package/dist/src/config.d.ts +83 -0
  72. package/dist/src/config.d.ts.map +1 -0
  73. package/dist/src/config.js +176 -0
  74. package/dist/src/config.js.map +1 -0
  75. package/dist/src/context-engine.d.ts +45 -0
  76. package/dist/src/context-engine.d.ts.map +1 -0
  77. package/dist/src/context-engine.js +135 -0
  78. package/dist/src/context-engine.js.map +1 -0
  79. package/dist/src/debug-wrapper.d.ts +58 -0
  80. package/dist/src/debug-wrapper.d.ts.map +1 -0
  81. package/dist/src/debug-wrapper.js +285 -0
  82. package/dist/src/debug-wrapper.js.map +1 -0
  83. package/dist/src/index.d.ts +31 -0
  84. package/dist/src/index.d.ts.map +1 -0
  85. package/dist/src/index.js +149 -0
  86. package/dist/src/index.js.map +1 -0
  87. package/dist/src/interactive-verifier.d.ts +26 -0
  88. package/dist/src/interactive-verifier.d.ts.map +1 -0
  89. package/dist/src/interactive-verifier.js +435 -0
  90. package/dist/src/interactive-verifier.js.map +1 -0
  91. package/dist/src/lessons.d.ts +59 -0
  92. package/dist/src/lessons.d.ts.map +1 -0
  93. package/dist/src/lessons.js +172 -0
  94. package/dist/src/lessons.js.map +1 -0
  95. package/dist/src/log-reader.d.ts +39 -0
  96. package/dist/src/log-reader.d.ts.map +1 -0
  97. package/dist/src/log-reader.js +291 -0
  98. package/dist/src/log-reader.js.map +1 -0
  99. package/dist/src/logger.d.ts +59 -0
  100. package/dist/src/logger.d.ts.map +1 -0
  101. package/dist/src/logger.js +328 -0
  102. package/dist/src/logger.js.map +1 -0
  103. package/dist/src/parallel-retrieval.d.ts +52 -0
  104. package/dist/src/parallel-retrieval.d.ts.map +1 -0
  105. package/dist/src/parallel-retrieval.js +309 -0
  106. package/dist/src/parallel-retrieval.js.map +1 -0
  107. package/dist/src/pattern-extractor.d.ts +30 -0
  108. package/dist/src/pattern-extractor.d.ts.map +1 -0
  109. package/dist/src/pattern-extractor.js +386 -0
  110. package/dist/src/pattern-extractor.js.map +1 -0
  111. package/dist/src/result-aggregator.d.ts +44 -0
  112. package/dist/src/result-aggregator.d.ts.map +1 -0
  113. package/dist/src/result-aggregator.js +315 -0
  114. package/dist/src/result-aggregator.js.map +1 -0
  115. package/dist/src/retrieval.d.ts +139 -0
  116. package/dist/src/retrieval.d.ts.map +1 -0
  117. package/dist/src/retrieval.js +724 -0
  118. package/dist/src/retrieval.js.map +1 -0
  119. package/dist/src/setup/auto-setup.d.ts +15 -0
  120. package/dist/src/setup/auto-setup.d.ts.map +1 -0
  121. package/dist/src/setup/auto-setup.js +157 -0
  122. package/dist/src/setup/auto-setup.js.map +1 -0
  123. package/dist/src/setup/configure-hooks.d.ts +8 -0
  124. package/dist/src/setup/configure-hooks.d.ts.map +1 -0
  125. package/dist/src/setup/configure-hooks.js +16 -0
  126. package/dist/src/setup/configure-hooks.js.map +1 -0
  127. package/dist/src/setup/create-slash-commands.d.ts +8 -0
  128. package/dist/src/setup/create-slash-commands.d.ts.map +1 -0
  129. package/dist/src/setup/create-slash-commands.js +101 -0
  130. package/dist/src/setup/create-slash-commands.js.map +1 -0
  131. package/dist/src/setup/index.d.ts +8 -0
  132. package/dist/src/setup/index.d.ts.map +1 -0
  133. package/dist/src/setup/index.js +17 -0
  134. package/dist/src/setup/index.js.map +1 -0
  135. package/dist/src/setup/inject-claude-md.d.ts +10 -0
  136. package/dist/src/setup/inject-claude-md.d.ts.map +1 -0
  137. package/dist/src/setup/inject-claude-md.js +150 -0
  138. package/dist/src/setup/inject-claude-md.js.map +1 -0
  139. package/dist/src/setup/uninstall.d.ts +22 -0
  140. package/dist/src/setup/uninstall.d.ts.map +1 -0
  141. package/dist/src/setup/uninstall.js +235 -0
  142. package/dist/src/setup/uninstall.js.map +1 -0
  143. package/dist/src/storage.d.ts +192 -0
  144. package/dist/src/storage.d.ts.map +1 -0
  145. package/dist/src/storage.js +1037 -0
  146. package/dist/src/storage.js.map +1 -0
  147. package/dist/src/string-similarity.d.ts +8 -0
  148. package/dist/src/string-similarity.d.ts.map +1 -0
  149. package/dist/src/string-similarity.js +57 -0
  150. package/dist/src/string-similarity.js.map +1 -0
  151. package/dist/src/traces/adapters/browser.d.ts +69 -0
  152. package/dist/src/traces/adapters/browser.d.ts.map +1 -0
  153. package/dist/src/traces/adapters/browser.js +280 -0
  154. package/dist/src/traces/adapters/browser.js.map +1 -0
  155. package/dist/src/traces/adapters/index.d.ts +23 -0
  156. package/dist/src/traces/adapters/index.d.ts.map +1 -0
  157. package/dist/src/traces/adapters/index.js +67 -0
  158. package/dist/src/traces/adapters/index.js.map +1 -0
  159. package/dist/src/traces/adapters/langchain.d.ts +42 -0
  160. package/dist/src/traces/adapters/langchain.d.ts.map +1 -0
  161. package/dist/src/traces/adapters/langchain.js +151 -0
  162. package/dist/src/traces/adapters/langchain.js.map +1 -0
  163. package/dist/src/traces/adapters/opentelemetry.d.ts +46 -0
  164. package/dist/src/traces/adapters/opentelemetry.d.ts.map +1 -0
  165. package/dist/src/traces/adapters/opentelemetry.js +164 -0
  166. package/dist/src/traces/adapters/opentelemetry.js.map +1 -0
  167. package/dist/src/traces/adapters/sentry.d.ts +58 -0
  168. package/dist/src/traces/adapters/sentry.d.ts.map +1 -0
  169. package/dist/src/traces/adapters/sentry.js +212 -0
  170. package/dist/src/traces/adapters/sentry.js.map +1 -0
  171. package/dist/src/traces/index.d.ts +10 -0
  172. package/dist/src/traces/index.d.ts.map +1 -0
  173. package/dist/src/traces/index.js +45 -0
  174. package/dist/src/traces/index.js.map +1 -0
  175. package/dist/src/traces/storage.d.ts +78 -0
  176. package/dist/src/traces/storage.d.ts.map +1 -0
  177. package/dist/src/traces/storage.js +399 -0
  178. package/dist/src/traces/storage.js.map +1 -0
  179. package/dist/src/traces/summarizer.d.ts +83 -0
  180. package/dist/src/traces/summarizer.d.ts.map +1 -0
  181. package/dist/src/traces/summarizer.js +247 -0
  182. package/dist/src/traces/summarizer.js.map +1 -0
  183. package/dist/src/traces/types.d.ts +365 -0
  184. package/dist/src/traces/types.d.ts.map +1 -0
  185. package/dist/src/traces/types.js +14 -0
  186. package/dist/src/traces/types.js.map +1 -0
  187. package/dist/src/types.d.ts +495 -0
  188. package/dist/src/types.d.ts.map +1 -0
  189. package/dist/src/types.js +27 -0
  190. package/dist/src/types.js.map +1 -0
  191. package/docs/agent-surface-policy.md +45 -0
  192. package/docs/memory-setup.md +226 -0
  193. package/hooks/_arch_scan_bg.py +88 -0
  194. package/hooks/_plugin_drift_check_bg.py +85 -0
  195. package/hooks/_session_start_lib.sh +42 -0
  196. package/hooks/git/pre-push +141 -0
  197. package/hooks/hooks.json +126 -0
  198. package/hooks/post-commit +30 -0
  199. package/hooks/pre-commit +29 -0
  200. package/hooks/pre-edit-apppulse.sh +6 -0
  201. package/hooks/pre-edit-architecture.sh +106 -0
  202. package/hooks/pre-edit-rally-point.sh +92 -0
  203. package/hooks/session-start-apppulse.sh +6 -0
  204. package/hooks/session-start-architecture.sh +10 -0
  205. package/hooks/session-start-git-hooks.sh +12 -0
  206. package/hooks/session-start-plugin-drift.sh +27 -0
  207. package/hooks/session-start-rally-point.sh +44 -0
  208. package/hooks/session-start-retrieval.sh +24 -0
  209. package/hooks/session-start-worktree-gc.sh +105 -0
  210. package/hooks/test_apppulse_hooks.sh +6 -0
  211. package/hooks/test_rally_point_hooks.sh +388 -0
  212. package/hooks/test_sec008_hooks.sh +66 -0
  213. package/package.json +111 -0
  214. package/scripts/README.md +301 -0
  215. package/scripts/_db_url.py +61 -0
  216. package/scripts/_paths.py +445 -0
  217. package/scripts/blm.py +162 -0
  218. package/scripts/blm_api.py +263 -0
  219. package/scripts/check_cache_sync.py +490 -0
  220. package/scripts/embed_backend.py +475 -0
  221. package/scripts/install_memory.py +556 -0
  222. package/scripts/lessons_index/__init__.py +77 -0
  223. package/scripts/lessons_index/__main__.py +135 -0
  224. package/scripts/lessons_index/ingest.py +288 -0
  225. package/scripts/lessons_index/query.py +285 -0
  226. package/scripts/lessons_index/schema.py +114 -0
  227. package/scripts/memory_context/__init__.py +610 -0
  228. package/scripts/memory_graph/__init__.py +393 -0
  229. package/scripts/project_resolver.py +172 -0
  230. package/scripts/prune_codex_plugin_cache.py +23 -0
  231. package/scripts/prune_plugin_cache.py +359 -0
  232. package/scripts/semantic_index/__init__.py +482 -0
  233. package/scripts/semantic_index/_bench_hybrid.py +175 -0
  234. package/scripts/semantic_index/backfill.py +263 -0
  235. package/scripts/semantic_index/hybrid.py +174 -0
  236. package/scripts/semantic_index/test_backfill.py +292 -0
  237. package/scripts/semantic_index/test_hybrid.py +541 -0
  238. package/scripts/sync_navgator_lessons.py +666 -0
  239. package/scripts/sync_plugin_cache.py +388 -0
  240. package/skills/agent-rally-point/SKILL.md +165 -0
  241. package/skills/agent-rally-watcher/SKILL.md +83 -0
  242. package/skills/api-registry-bridge/SKILL.md +59 -0
  243. package/skills/architecture/dead/SKILL.md +56 -0
  244. package/skills/architecture/impact/SKILL.md +88 -0
  245. package/skills/architecture/review/SKILL.md +227 -0
  246. package/skills/architecture/rules/SKILL.md +72 -0
  247. package/skills/architecture/scan/SKILL.md +79 -0
  248. package/skills/architecture/trace/SKILL.md +65 -0
  249. package/skills/attribution-standard/SKILL.md +122 -0
  250. package/skills/authentication/SKILL.md +81 -0
  251. package/skills/authentication/references/better-auth-magic-link.md +135 -0
  252. package/skills/authentication/references/better-auth-setup.md +224 -0
  253. package/skills/authentication/references/google-calendar-sync.md +278 -0
  254. package/skills/authentication/references/google-cloud-console.md +159 -0
  255. package/skills/authentication/references/google-geocoding-directions.md +126 -0
  256. package/skills/authentication/references/google-lessons-example-web-app.md +177 -0
  257. package/skills/authentication/references/google-maps.md +162 -0
  258. package/skills/authentication/references/google-oauth-setup.md +343 -0
  259. package/skills/authentication/references/google-places.md +173 -0
  260. package/skills/authentication/references/lessons-example-web-app-better-auth.md +126 -0
  261. package/skills/authentication/references/resend-email.md +227 -0
  262. package/skills/authentication/references/resend-otp-magic-link.md +153 -0
  263. package/skills/authentication/references/supabase-auth.md +172 -0
  264. package/skills/auto-decision-capture/SKILL.md +419 -0
  265. package/skills/build-loop/SKILL.md +516 -0
  266. package/skills/build-loop/detect-plugins.mjs +82 -0
  267. package/skills/build-loop/eval-guide.md +65 -0
  268. package/skills/build-loop/fallbacks.md +549 -0
  269. package/skills/build-loop/phases/fact-check.md +42 -0
  270. package/skills/build-loop/phases/ui-validation.md +267 -0
  271. package/skills/build-loop/references/apple-native-planning.md +439 -0
  272. package/skills/build-loop/references/capability-routing.md +181 -0
  273. package/skills/build-loop/references/codex-subagents.md +98 -0
  274. package/skills/build-loop/references/coordination.md +161 -0
  275. package/skills/build-loop/references/correction-aware-capture.md +177 -0
  276. package/skills/build-loop/references/independent-auditor.md +72 -0
  277. package/skills/build-loop/references/intent-capability-pack.md +170 -0
  278. package/skills/build-loop/references/intent-exploration-prompts.md +96 -0
  279. package/skills/build-loop/references/leadership.md +72 -0
  280. package/skills/build-loop/references/memory.md +284 -0
  281. package/skills/build-loop/references/modular-systems-pack.md +96 -0
  282. package/skills/build-loop/references/output-style.md +129 -0
  283. package/skills/build-loop/references/pay-it-forward-arch.md +98 -0
  284. package/skills/build-loop/references/phase-1-assess.md +218 -0
  285. package/skills/build-loop/references/phase-2-plan.md +77 -0
  286. package/skills/build-loop/references/phase-3-execute.md +47 -0
  287. package/skills/build-loop/references/phase-4-review.md +310 -0
  288. package/skills/build-loop/references/phase-5-iterate.md +71 -0
  289. package/skills/build-loop/references/phase-6-learn.md +56 -0
  290. package/skills/build-loop/references/recent-design-structures.md +274 -0
  291. package/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
  292. package/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
  293. package/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
  294. package/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
  295. package/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
  296. package/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
  297. package/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
  298. package/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
  299. package/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
  300. package/skills/build-loop/references/self-recursive-dev.md +77 -0
  301. package/skills/build-loop/references/self-review.md +232 -0
  302. package/skills/build-loop/references/ui-io-contract.md +116 -0
  303. package/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
  304. package/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
  305. package/skills/build-loop/templates/backlog-item.md +35 -0
  306. package/skills/build-loop/templates/codex-worker-prompt.md +89 -0
  307. package/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
  308. package/skills/build-loop/templates/ux-fix-plan.md +40 -0
  309. package/skills/building-with-deepagents/SKILL.md +195 -0
  310. package/skills/building-with-deepagents/references/anti-patterns.md +141 -0
  311. package/skills/building-with-deepagents/references/api-quick-reference.md +184 -0
  312. package/skills/capabilities/SKILL.md +91 -0
  313. package/skills/debug-loop/SKILL.md +278 -0
  314. package/skills/debug-loop/references/convergence-rules.md +145 -0
  315. package/skills/debug-loop/references/swiftui-macos-clunky-ui-debugger.html +823 -0
  316. package/skills/debugging/assess/SKILL.md +111 -0
  317. package/skills/debugging/debug-loop/SKILL.md +231 -0
  318. package/skills/debugging/memory/SKILL.md +147 -0
  319. package/skills/debugging/store/SKILL.md +153 -0
  320. package/skills/debugging-memory/SKILL.md +440 -0
  321. package/skills/debugging-memory/examples/incident-example.json +67 -0
  322. package/skills/debugging-memory/references/incident-documentation.md +280 -0
  323. package/skills/debugging-memory/references/ios-notification-alarm-playbook.md +300 -0
  324. package/skills/debugging-memory/references/pattern-extraction.md +163 -0
  325. package/skills/debugging-memory/references/subagent-integration.md +312 -0
  326. package/skills/defenseclaw-bridge/SKILL.md +156 -0
  327. package/skills/defenseclaw-bridge/references/dc-config-mapping.md +162 -0
  328. package/skills/defenseclaw-bridge/references/output-format.md +108 -0
  329. package/skills/handoff/SKILL.md +94 -0
  330. package/skills/ibr-bridge/SKILL.md +68 -0
  331. package/skills/knowledge/SKILL.md +127 -0
  332. package/skills/knowledge/references/recall-integration.md +83 -0
  333. package/skills/knowledge/templates/madr-minimal.md +56 -0
  334. package/skills/knowledge-review/SKILL.md +112 -0
  335. package/skills/logging-tracer/SKILL.md +202 -0
  336. package/skills/logging-tracer/references/log-analysis.md +128 -0
  337. package/skills/logging-tracer/references/stack-templates.md +274 -0
  338. package/skills/logging-tracer-bridge/SKILL.md +70 -0
  339. package/skills/mcp-builder/SKILL.md +278 -0
  340. package/skills/mcp-builder/references/mcp-security.md +106 -0
  341. package/skills/model-tiering/SKILL.md +216 -0
  342. package/skills/native-ax-driver/SKILL.md +220 -0
  343. package/skills/native-ax-driver/scripts/layout_fill.py +222 -0
  344. package/skills/native-ax-driver/scripts/native_driver.py +495 -0
  345. package/skills/native-ax-driver/swift/bl-ax-driver/Package.swift +20 -0
  346. package/skills/native-ax-driver/swift/bl-ax-driver/Sources/main.swift +542 -0
  347. package/skills/optimize/SKILL.md +202 -0
  348. package/skills/optimize/profiles.md +61 -0
  349. package/skills/plan-verify/SKILL.md +115 -0
  350. package/skills/plan-verify/test-fixtures/example-app-v20-findings.json +27 -0
  351. package/skills/plan-verify/test-fixtures/example-app-v20.md +42 -0
  352. package/skills/plan-verify/test-fixtures/example-app-v22-findings.json +15 -0
  353. package/skills/plan-verify/test-fixtures/example-app-v22.md +1608 -0
  354. package/skills/plan-verify/test-fixtures/unrelated-good-plan-findings.json +15 -0
  355. package/skills/plan-verify/test-fixtures/unrelated-good-plan.md +55 -0
  356. package/skills/plugin-builder/SKILL.md +329 -0
  357. package/skills/plugin-builder/references/authoritative-sources.md +104 -0
  358. package/skills/plugin-builder/references/build-loop-phase-guidance.md +94 -0
  359. package/skills/plugin-builder/references/components-guide.md +221 -0
  360. package/skills/plugin-builder/references/distribution.md +317 -0
  361. package/skills/plugin-builder/references/dual-host-claude-codex.md +220 -0
  362. package/skills/plugin-builder/references/hooks-reference.md +210 -0
  363. package/skills/plugin-builder/references/manifest-schema.md +111 -0
  364. package/skills/plugin-builder/references/plugin-hygiene-lessons.md +307 -0
  365. package/skills/plugin-tests/SKILL.md +94 -0
  366. package/skills/prd-bridge/SKILL.md +135 -0
  367. package/skills/research/SKILL.md +80 -0
  368. package/skills/security-methodology/SKILL.md +105 -0
  369. package/skills/security-methodology/references/cross-source-matrix.md +77 -0
  370. package/skills/security-methodology/references/mitre-atlas-starter.md +83 -0
  371. package/skills/security-methodology/references/nist-600-1-mapping.md +119 -0
  372. package/skills/security-methodology/references/owasp-agentic-top-10.md +157 -0
  373. package/skills/security-methodology/references/owasp-llm-top-10.md +144 -0
  374. package/skills/security-methodology/references/owasp-web-top-10.md +65 -0
  375. package/skills/self-improve/SKILL.md +298 -0
  376. package/skills/spec-writing/SKILL.md +548 -0
  377. package/skills/spec-writing/scripts/check_checklist.py +478 -0
  378. package/skills/sync-skills/SKILL.md +132 -0
  379. package/skills/telemetry/SKILL.md +66 -0
  380. package/skills/ui-design/SKILL.md +75 -0
  381. package/skills/ui-design/references/design-patterns-multi.md +165 -0
  382. package/skills/ui-design/references/design-preferences-from-owned-apps.md +150 -0
  383. package/skills/ui-design/references/evidence-capture-policy.md +55 -0
  384. package/skills/ui-design/references/ui-guidance-sources.md +330 -0
  385. package/skills/ui-design/references/universal-design-principles.alt.md +182 -0
  386. package/skills/ui-design/references/universal-design-principles.md +419 -0
  387. package/templates/memory/MEMORY.md.template +55 -0
  388. package/templates/memory/constitution.md.template +64 -0
  389. 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())