@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,666 @@
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
+ """One-way NavGator lessons -> local SQLite semantic-facts sync.
5
+
6
+ Reads project-local + global NavGator lessons.json files and upserts each
7
+ real (non-template) lesson into the local build-loop-memory SQLite index
8
+ with ``subject='lesson:nav:<id>'`` and ``domain='architecture'``.
9
+
10
+ Cross-project lesson recall happens through the existing recall pipeline;
11
+ this script only writes.
12
+
13
+ Inputs
14
+ ------
15
+ * Project-local: ``<workdir>/.navgator/lessons/lessons.json``
16
+ * Global: ``~/.navgator/lessons/global-lessons.json``
17
+
18
+ Both files share a common shape (``{"lessons": [...]}``) with an optional
19
+ ``_template`` sibling object that is always skipped. Inside ``lessons``,
20
+ any entry whose ``id`` starts with ``_template`` or is empty is also
21
+ skipped.
22
+
23
+ Project identity
24
+ ----------------
25
+ Project tag is derived once per run from:
26
+ 1. ``git remote get-url origin`` (basename minus ``.git``)
27
+ 2. ``Path.cwd().name`` fallback
28
+
29
+ For project-local lessons the row's ``project`` column holds this tag.
30
+ For global lessons ``project`` is NULL — that is the cross-project
31
+ sentinel ``recall.py`` already understands.
32
+
33
+ SQLite upsert
34
+ -------------
35
+ The local index lives at ``<memory-root>/indexes/semantic_facts.sqlite`` by
36
+ default. It has a real ``PRIMARY KEY(subject, project_key)`` and uses an
37
+ idempotent upsert.
38
+
39
+ Postgres mirror
40
+ ---------------
41
+ Postgres is optional. Pass ``--postgres-mirror`` to also write
42
+ ``agent_memory.<schema>.semantic_facts``. Postgres-side, ``semantic_facts``
43
+ has no UNIQUE constraint on ``(subject, project)``. We follow the
44
+ ``sync_db_from_files.py`` pattern: DELETE-then-INSERT in a single transaction.
45
+
46
+ Embeddings
47
+ ----------
48
+ The SQLite default does not require embeddings. When ``--postgres-mirror`` is
49
+ set, ``scripts/embed_backend.embed`` is attempted for the Postgres vector
50
+ column. On embedding failure the mirror row is still written with
51
+ ``embedding = NULL`` and the JSON output records the per-lesson error.
52
+
53
+ Failure modes
54
+ -------------
55
+ * SQLite write failure -> log to ``.build-loop/sync_errors.log`` and continue
56
+ with ``errors: ["sqlite_upsert_failed:<lesson_id>"]``.
57
+ * Postgres mirror unreachable -> log to ``.build-loop/sync_errors.log`` and
58
+ exit 0 with SQLite counts intact plus ``errors: ["postgres_unavailable"]``.
59
+ * Missing/empty/template-only ``lessons.json`` → ``synced: 0`` cleanly.
60
+ * Embedding subsystem unavailable → row written with NULL embedding;
61
+ ``errors`` lists ``embed_unavailable:<lesson_id>``.
62
+
63
+ Stdout (always JSON):
64
+ {
65
+ "synced": <int>, # project-local SQLite rows written
66
+ "global_synced": <int>, # global SQLite rows written
67
+ "postgres_mirrored": <int>, # project-local Postgres mirror rows
68
+ "global_postgres_mirrored": <int>,
69
+ "skipped_templates": <int>,
70
+ "errors": [str],
71
+ "schema_version": "1.0.0"
72
+ }
73
+
74
+ CLI
75
+ ---
76
+ --project-only only sync project-local lessons
77
+ --global-only only sync ~/.navgator/lessons/global-lessons.json
78
+ --dry-run don't open a DB connection or write anything
79
+ --postgres-mirror also write Postgres semantic_facts
80
+ --sqlite-db PATH override local SQLite index path
81
+ --workdir PATH project root (default: cwd)
82
+ --lessons-file PATH override the lessons source — treat the given file as the
83
+ single project-local lessons.json and skip both the
84
+ NavGator project-local + global discovery. Used by
85
+ Chunk 8's promotion pipeline to feed
86
+ ``.build-loop/architecture/lessons.json`` through the
87
+ same write path with a different subject prefix.
88
+ --source-prefix STR override the ``subject = '<prefix><lesson_id>'`` mapping.
89
+ Default ``lesson:nav:`` (NavGator origin). Chunk 8's
90
+ promotion script passes ``lesson:bl:`` so build-loop-
91
+ native lessons land in distinct semantic_facts rows.
92
+
93
+ Postgres DSN resolution (mirror only; matches build-loop convention):
94
+ 1. $BUILD_LOOP_DATABASE_URL (preferred, plan-doc convention)
95
+ 2. $DATABASE_URL (fallback, ``db.py`` default)
96
+ 3. ~/.config/agent-memory/connection.env
97
+ """
98
+ from __future__ import annotations
99
+
100
+ import argparse
101
+ import json
102
+ import os
103
+ import re
104
+ import subprocess
105
+ import sys
106
+ from datetime import datetime, timezone
107
+ from pathlib import Path
108
+ from typing import Any
109
+
110
+ SCHEMA_VERSION = "1.0.0"
111
+ DOMAIN = "architecture"
112
+ TOOL = "navgator"
113
+ SOURCE = "migration" # one-way external import per write_decision taxonomy
114
+ SYNC_ERRORS_LOG = ".build-loop/sync_errors.log"
115
+
116
+ # Make scripts/ importable as a sibling module (mirrors capture_arch_violation.py).
117
+ HERE = Path(__file__).resolve().parent
118
+ if str(HERE) not in sys.path:
119
+ sys.path.insert(0, str(HERE))
120
+
121
+ from semantic_index import upsert_lesson as _upsert_sqlite_lesson # type: ignore # noqa: E402
122
+
123
+
124
+ # ---------- helpers ----------
125
+
126
+
127
+ def _now_iso() -> str:
128
+ return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
129
+
130
+
131
+ def _log(msg: str) -> None:
132
+ print(f"[sync_navgator_lessons] {msg}", file=sys.stderr, flush=True)
133
+
134
+
135
+ def _append_sync_error(workdir: Path, message: str) -> None:
136
+ """Best-effort append to .build-loop/sync_errors.log; never raises."""
137
+ try:
138
+ log_path = workdir / SYNC_ERRORS_LOG
139
+ log_path.parent.mkdir(parents=True, exist_ok=True)
140
+ with log_path.open("a", encoding="utf-8") as fh:
141
+ fh.write(f"{_now_iso()}: {message}\n")
142
+ except OSError as exc:
143
+ _log(f"WARN: could not write sync_errors.log ({exc})")
144
+
145
+
146
+ def _detect_project_tag(workdir: Path) -> str:
147
+ """Best-effort project identifier: git remote basename, then cwd name."""
148
+ try:
149
+ cp = subprocess.run(
150
+ ["git", "remote", "get-url", "origin"],
151
+ cwd=str(workdir),
152
+ capture_output=True,
153
+ text=True,
154
+ timeout=5,
155
+ )
156
+ if cp.returncode == 0:
157
+ url = cp.stdout.strip()
158
+ if url:
159
+ # Strip trailing .git, take final path segment.
160
+ stem = url.rstrip("/")
161
+ if stem.endswith(".git"):
162
+ stem = stem[: -len(".git")]
163
+ # Handle both git@host:owner/repo and https://host/owner/repo
164
+ for sep in (":", "/"):
165
+ if sep in stem:
166
+ stem = stem.rsplit(sep, 1)[-1]
167
+ if stem:
168
+ return stem
169
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
170
+ pass
171
+ name = workdir.name
172
+ return name or "unknown"
173
+
174
+
175
+ def _read_lessons_file(path: Path) -> tuple[list[dict], int]:
176
+ """Return (real_lessons, skipped_template_count). Missing/invalid → ([], 0)."""
177
+ if not path.exists():
178
+ return [], 0
179
+ try:
180
+ with path.open("r", encoding="utf-8") as fh:
181
+ data = json.load(fh)
182
+ except (OSError, json.JSONDecodeError) as exc:
183
+ _log(f"WARN: lessons file unreadable ({path}): {exc}")
184
+ return [], 0
185
+ if not isinstance(data, dict):
186
+ return [], 0
187
+ raw_lessons = data.get("lessons", []) or []
188
+ if not isinstance(raw_lessons, list):
189
+ return [], 0
190
+ real: list[dict] = []
191
+ skipped = 0
192
+ for entry in raw_lessons:
193
+ if not isinstance(entry, dict):
194
+ continue
195
+ lid = str(entry.get("id", "") or "").strip()
196
+ if not lid or lid.startswith("_template"):
197
+ skipped += 1
198
+ continue
199
+ real.append(entry)
200
+ return real, skipped
201
+
202
+
203
+ def _confidence_source_for(promoted: bool) -> str:
204
+ """Map NavGator's ``promoted`` flag to write_decision's closed taxonomy.
205
+
206
+ Aligns with Chunk 6 helper inside ``capture_arch_violation.py``: every
207
+ automated capture uses the ``auto-*`` prefix. Promoted lessons have
208
+ been hand-validated → ``auto-confirmed``; un-promoted are heuristic →
209
+ ``auto-inferred``.
210
+ """
211
+ return "auto-confirmed" if promoted else "auto-inferred"
212
+
213
+
214
+ def _confidence_float_for(promoted: bool) -> float:
215
+ """Mirror ``write_decision._confidence_to_float`` so DB readers see the
216
+ same numeric scale they get for decisions."""
217
+ return 0.75 if promoted else 0.5
218
+
219
+
220
+ def _is_safe_schema(schema: str) -> bool:
221
+ return bool(re.match(r"^[a-z][a-z0-9_]*$", schema))
222
+
223
+
224
+ # ---------- DB layer ----------
225
+
226
+
227
+ def _resolve_dsn() -> str | None:
228
+ """Plan-doc DSN order: BUILD_LOOP_DATABASE_URL → DATABASE_URL → connection.env.
229
+
230
+ Delegates to the shared resolver (`scripts/_db_url.py`). Returns None
231
+ when nothing is configured (the shared resolver returns ""); caller
232
+ treats None as 'postgres unavailable' only when --postgres-mirror is set.
233
+ """
234
+ from _db_url import resolve_db_url # noqa: PLC0415
235
+
236
+ return resolve_db_url() or None
237
+
238
+
239
+ def _open_connection():
240
+ """Open a psycopg connection. Raises on any failure.
241
+
242
+ DSN comes from `_resolve_dsn`, which delegates to the shared
243
+ `scripts/_db_url.py` resolver (BUILD_LOOP_DATABASE_URL → DATABASE_URL →
244
+ connection.env). We open psycopg directly here rather than via
245
+ ``scripts.db`` to keep this script's soft-failure (return-None →
246
+ exit-0) envelope instead of db.py's raise-on-missing contract.
247
+ """
248
+ import psycopg # type: ignore # noqa: PLC0415
249
+
250
+ dsn = _resolve_dsn()
251
+ if not dsn:
252
+ raise RuntimeError("DATABASE_URL/BUILD_LOOP_DATABASE_URL not configured")
253
+ return psycopg.connect(dsn, autocommit=False)
254
+
255
+
256
+ def _vector_literal(embedding: list[float] | None) -> str | None:
257
+ if not embedding:
258
+ return None
259
+ return "[" + ",".join(f"{x:.6f}" for x in embedding) + "]"
260
+
261
+
262
+ def _upsert_lesson(
263
+ conn,
264
+ *,
265
+ schema: str,
266
+ lesson: dict,
267
+ project: str | None,
268
+ embedding: list[float] | None,
269
+ subject_prefix: str = "lesson:nav:",
270
+ ) -> None:
271
+ """DELETE-then-INSERT a single lesson row in one transaction.
272
+
273
+ Mirrors ``sync_db_from_files.upsert_decision`` — the schema has no
274
+ UNIQUE(subject, project) so we cannot use a real ON CONFLICT. The
275
+ DELETE+INSERT pair runs inside the caller's transaction; commit is
276
+ issued by the caller after all lessons are processed (or on error).
277
+ """
278
+ if not _is_safe_schema(schema):
279
+ raise ValueError(f"unsafe schema name: {schema!r}")
280
+
281
+ lesson_id = str(lesson.get("id", ""))
282
+ subject = f"{subject_prefix}{lesson_id}"
283
+ predicate = str(lesson.get("category", "") or "uncategorized")
284
+ obj_summary = str(lesson.get("pattern", "") or "")
285
+ promoted = bool(lesson.get("promoted", False))
286
+ confidence_source = _confidence_source_for(promoted)
287
+ confidence = _confidence_float_for(promoted)
288
+
289
+ metadata = {
290
+ "lesson_id": lesson_id,
291
+ "promoted": promoted,
292
+ "navgator_lesson": lesson, # full original payload
293
+ "synced_at": _now_iso(),
294
+ "schema_version": SCHEMA_VERSION,
295
+ }
296
+
297
+ # Files affected → array column when present, else empty list.
298
+ ctx = lesson.get("context") or {}
299
+ files_affected = []
300
+ if isinstance(ctx, dict):
301
+ fa = ctx.get("files_affected") or []
302
+ if isinstance(fa, list):
303
+ files_affected = [str(x) for x in fa]
304
+
305
+ # last_synced lives in metadata, not as its own column (semantic_facts
306
+ # already has last_validated/last_accessed; we don't want to clobber
307
+ # those for facts that pre-existed in the table).
308
+ metadata["last_synced"] = _now_iso()
309
+
310
+ vec_lit = _vector_literal(embedding)
311
+
312
+ # DELETE + INSERT inside one cursor for transactional symmetry.
313
+ with conn.cursor() as cur:
314
+ if project is None:
315
+ cur.execute(
316
+ f"DELETE FROM {schema}.semantic_facts "
317
+ "WHERE subject = %s AND project IS NULL;",
318
+ (subject,),
319
+ )
320
+ else:
321
+ cur.execute(
322
+ f"DELETE FROM {schema}.semantic_facts "
323
+ "WHERE subject = %s AND project = %s;",
324
+ (subject, project),
325
+ )
326
+ if vec_lit is None:
327
+ sql = (
328
+ f"INSERT INTO {schema}.semantic_facts "
329
+ "(subject, predicate, object, confidence, status, embedding, metadata, "
330
+ " project, tool, task_category, files_touched, "
331
+ " confidence_source, domain) "
332
+ "VALUES (%s, %s, %s, %s, 'active', NULL, %s::jsonb, "
333
+ " %s, %s, %s, %s, "
334
+ " %s, %s);"
335
+ )
336
+ params = (
337
+ subject,
338
+ predicate,
339
+ obj_summary,
340
+ confidence,
341
+ json.dumps(metadata, ensure_ascii=False),
342
+ project,
343
+ TOOL,
344
+ "research",
345
+ files_affected,
346
+ confidence_source,
347
+ DOMAIN,
348
+ )
349
+ else:
350
+ sql = (
351
+ f"INSERT INTO {schema}.semantic_facts "
352
+ "(subject, predicate, object, confidence, status, embedding, metadata, "
353
+ " project, tool, task_category, files_touched, "
354
+ " confidence_source, domain) "
355
+ "VALUES (%s, %s, %s, %s, 'active', %s::vector, %s::jsonb, "
356
+ " %s, %s, %s, %s, "
357
+ " %s, %s);"
358
+ )
359
+ params = (
360
+ subject,
361
+ predicate,
362
+ obj_summary,
363
+ confidence,
364
+ vec_lit,
365
+ json.dumps(metadata, ensure_ascii=False),
366
+ project,
367
+ TOOL,
368
+ "research",
369
+ files_affected,
370
+ confidence_source,
371
+ DOMAIN,
372
+ )
373
+ cur.execute(sql, params)
374
+
375
+
376
+ # ---------- main pipeline ----------
377
+
378
+
379
+ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
380
+ p = argparse.ArgumentParser(
381
+ description="One-way NavGator lessons -> local SQLite semantic-facts sync"
382
+ )
383
+ p.add_argument(
384
+ "--workdir",
385
+ default=".",
386
+ help="Project root (default: cwd). Project-local lessons live under <workdir>/.navgator/lessons/lessons.json.",
387
+ )
388
+ p.add_argument(
389
+ "--project-only",
390
+ action="store_true",
391
+ help="Sync only project-local lessons.json.",
392
+ )
393
+ p.add_argument(
394
+ "--global-only",
395
+ action="store_true",
396
+ help="Sync only ~/.navgator/lessons/global-lessons.json.",
397
+ )
398
+ p.add_argument(
399
+ "--dry-run",
400
+ action="store_true",
401
+ help="Do not open SQLite or Postgres. Reports what would be synced.",
402
+ )
403
+ p.add_argument(
404
+ "--sqlite-db",
405
+ default=None,
406
+ help="Override local SQLite semantic index path. Default: <memory-root>/indexes/semantic_facts.sqlite.",
407
+ )
408
+ p.add_argument(
409
+ "--postgres-mirror",
410
+ action="store_true",
411
+ help="Also mirror rows into Postgres semantic_facts. Default: SQLite only.",
412
+ )
413
+ p.add_argument(
414
+ "--schema",
415
+ default=None,
416
+ help="Postgres schema. Default: $AGENT_MEMORY_SCHEMA or 'personal_memory'.",
417
+ )
418
+ p.add_argument(
419
+ "--lessons-file",
420
+ default=None,
421
+ help=(
422
+ "Override lessons source. When set, the given file is treated as the "
423
+ "single project-local lessons.json and the NavGator project-local + "
424
+ "global discovery is skipped. Used by Chunk 8's build-loop-native "
425
+ "promotion pipeline."
426
+ ),
427
+ )
428
+ p.add_argument(
429
+ "--source-prefix",
430
+ default="lesson:nav:",
431
+ help=(
432
+ "Subject prefix written into semantic_facts.subject "
433
+ "(default 'lesson:nav:'). Chunk 8's promotion script passes "
434
+ "'lesson:bl:' to keep build-loop-native lessons in distinct rows."
435
+ ),
436
+ )
437
+ return p.parse_args(argv)
438
+
439
+
440
+ def _default_schema() -> str:
441
+ return os.environ.get("AGENT_MEMORY_SCHEMA") or "personal_memory"
442
+
443
+
444
+ def _embed_safely(text: str, errors: list[str], lesson_id: str) -> list[float] | None:
445
+ """Call embed_backend; on any failure log to ``errors`` and return None."""
446
+ if not text or not text.strip():
447
+ return None
448
+ try:
449
+ from embed_backend import embed as _embed # type: ignore # noqa: PLC0415
450
+ except Exception as exc: # noqa: BLE001
451
+ errors.append(f"embed_unavailable:{lesson_id}")
452
+ _log(f"WARN: embed_backend import failed for {lesson_id}: {exc}")
453
+ return None
454
+ try:
455
+ return _embed(text)
456
+ except Exception as exc: # noqa: BLE001
457
+ errors.append(f"embed_failed:{lesson_id}")
458
+ _log(f"WARN: embedding {lesson_id} failed: {exc}")
459
+ return None
460
+
461
+
462
+ def main(argv: list[str] | None = None) -> int:
463
+ args = parse_args(argv)
464
+ workdir = Path(args.workdir).resolve()
465
+ schema = args.schema or _default_schema()
466
+
467
+ if args.project_only and args.global_only:
468
+ print(
469
+ json.dumps(
470
+ {
471
+ "synced": 0,
472
+ "global_synced": 0,
473
+ "postgres_mirrored": 0,
474
+ "global_postgres_mirrored": 0,
475
+ "skipped_templates": 0,
476
+ "errors": ["invalid_flags:project_only_and_global_only_mutually_exclusive"],
477
+ "schema_version": SCHEMA_VERSION,
478
+ },
479
+ sort_keys=True,
480
+ )
481
+ )
482
+ return 0
483
+
484
+ project_tag = _detect_project_tag(workdir)
485
+ subject_prefix = args.source_prefix or "lesson:nav:"
486
+
487
+ project_lessons: list[dict] = []
488
+ global_lessons: list[dict] = []
489
+ skipped_templates = 0
490
+
491
+ if args.lessons_file:
492
+ # Override mode: treat --lessons-file as the sole project-local
493
+ # source. Skip NavGator's project-local + global discovery so
494
+ # promotion-fed lessons don't double-sync.
495
+ path = Path(args.lessons_file)
496
+ if not path.is_absolute():
497
+ path = (workdir / path).resolve()
498
+ real, skipped = _read_lessons_file(path)
499
+ project_lessons = real
500
+ skipped_templates += skipped
501
+ else:
502
+ if not args.global_only:
503
+ path = workdir / ".navgator" / "lessons" / "lessons.json"
504
+ real, skipped = _read_lessons_file(path)
505
+ project_lessons = real
506
+ skipped_templates += skipped
507
+
508
+ if not args.project_only:
509
+ path = Path.home() / ".navgator" / "lessons" / "global-lessons.json"
510
+ real, skipped = _read_lessons_file(path)
511
+ global_lessons = real
512
+ skipped_templates += skipped
513
+
514
+ errors: list[str] = []
515
+ synced = 0
516
+ global_synced = 0
517
+ postgres_mirrored = 0
518
+ global_postgres_mirrored = 0
519
+
520
+ if args.dry_run:
521
+ out = {
522
+ "synced": len(project_lessons),
523
+ "global_synced": len(global_lessons),
524
+ "postgres_mirrored": 0,
525
+ "global_postgres_mirrored": 0,
526
+ "skipped_templates": skipped_templates,
527
+ "errors": errors,
528
+ "schema_version": SCHEMA_VERSION,
529
+ "dry_run": True,
530
+ "postgres_mirror": bool(args.postgres_mirror),
531
+ }
532
+ print(json.dumps(out, sort_keys=True))
533
+ return 0
534
+
535
+ if not project_lessons and not global_lessons:
536
+ out = {
537
+ "synced": 0,
538
+ "global_synced": 0,
539
+ "postgres_mirrored": 0,
540
+ "global_postgres_mirrored": 0,
541
+ "skipped_templates": skipped_templates,
542
+ "errors": errors,
543
+ "schema_version": SCHEMA_VERSION,
544
+ "postgres_mirror": bool(args.postgres_mirror),
545
+ }
546
+ print(json.dumps(out, sort_keys=True))
547
+ return 0
548
+
549
+ sqlite_db = Path(args.sqlite_db).expanduser().resolve() if args.sqlite_db else None
550
+
551
+ for lesson in project_lessons:
552
+ lid = str(lesson.get("id", ""))
553
+ promoted = bool(lesson.get("promoted", False))
554
+ try:
555
+ _upsert_sqlite_lesson(
556
+ lesson=lesson,
557
+ project=project_tag,
558
+ subject_prefix=subject_prefix,
559
+ confidence=_confidence_float_for(promoted),
560
+ confidence_source=_confidence_source_for(promoted),
561
+ db_path=sqlite_db,
562
+ tool=TOOL,
563
+ domain=DOMAIN,
564
+ )
565
+ synced += 1
566
+ except Exception as exc: # noqa: BLE001
567
+ msg = f"sqlite_upsert_failed:{lid}: {type(exc).__name__}: {exc}"
568
+ errors.append(f"sqlite_upsert_failed:{lid}")
569
+ _append_sync_error(workdir, msg)
570
+
571
+ for lesson in global_lessons:
572
+ lid = str(lesson.get("id", ""))
573
+ promoted = bool(lesson.get("promoted", False))
574
+ try:
575
+ _upsert_sqlite_lesson(
576
+ lesson=lesson,
577
+ project=None,
578
+ subject_prefix=subject_prefix,
579
+ confidence=_confidence_float_for(promoted),
580
+ confidence_source=_confidence_source_for(promoted),
581
+ db_path=sqlite_db,
582
+ tool=TOOL,
583
+ domain=DOMAIN,
584
+ )
585
+ global_synced += 1
586
+ except Exception as exc: # noqa: BLE001
587
+ msg = f"sqlite_upsert_failed_global:{lid}: {type(exc).__name__}: {exc}"
588
+ errors.append(f"sqlite_upsert_failed:{lid}")
589
+ _append_sync_error(workdir, msg)
590
+
591
+ if args.postgres_mirror:
592
+ # Open Postgres. Soft-fail to graceful exit on any connection error.
593
+ try:
594
+ conn = _open_connection()
595
+ except Exception as exc: # noqa: BLE001 - psycopg.OperationalError, RuntimeError, etc.
596
+ msg = f"postgres_unavailable: {type(exc).__name__}: {exc}"
597
+ _append_sync_error(workdir, msg)
598
+ errors.append("postgres_unavailable")
599
+ conn = None
600
+
601
+ if conn is not None:
602
+ try:
603
+ for lesson in project_lessons:
604
+ lid = str(lesson.get("id", ""))
605
+ embedding = _embed_safely(
606
+ str(lesson.get("pattern", "") or ""), errors, lid
607
+ )
608
+ try:
609
+ _upsert_lesson(
610
+ conn,
611
+ schema=schema,
612
+ lesson=lesson,
613
+ project=project_tag,
614
+ embedding=embedding,
615
+ subject_prefix=subject_prefix,
616
+ )
617
+ postgres_mirrored += 1
618
+ except Exception as exc: # noqa: BLE001 - psycopg + unforeseen
619
+ conn.rollback()
620
+ msg = f"upsert_failed:{lid}: {type(exc).__name__}: {exc}"
621
+ errors.append(f"upsert_failed:{lid}")
622
+ _append_sync_error(workdir, msg)
623
+ for lesson in global_lessons:
624
+ lid = str(lesson.get("id", ""))
625
+ embedding = _embed_safely(
626
+ str(lesson.get("pattern", "") or ""), errors, lid
627
+ )
628
+ try:
629
+ _upsert_lesson(
630
+ conn,
631
+ schema=schema,
632
+ lesson=lesson,
633
+ project=None,
634
+ embedding=embedding,
635
+ subject_prefix=subject_prefix,
636
+ )
637
+ global_postgres_mirrored += 1
638
+ except Exception as exc: # noqa: BLE001
639
+ conn.rollback()
640
+ msg = f"upsert_failed_global:{lid}: {type(exc).__name__}: {exc}"
641
+ errors.append(f"upsert_failed:{lid}")
642
+ _append_sync_error(workdir, msg)
643
+ conn.commit()
644
+ finally:
645
+ try:
646
+ conn.close()
647
+ except Exception: # noqa: BLE001
648
+ pass
649
+
650
+ out = {
651
+ "synced": synced,
652
+ "global_synced": global_synced,
653
+ "postgres_mirrored": postgres_mirrored,
654
+ "global_postgres_mirrored": global_postgres_mirrored,
655
+ "skipped_templates": skipped_templates,
656
+ "errors": errors,
657
+ "schema_version": SCHEMA_VERSION,
658
+ "postgres_mirror": bool(args.postgres_mirror),
659
+ "sqlite_db": str(sqlite_db) if sqlite_db else None,
660
+ }
661
+ print(json.dumps(out, sort_keys=True))
662
+ return 0
663
+
664
+
665
+ if __name__ == "__main__":
666
+ raise SystemExit(main())