nlm-memory 0.19.0 → 0.20.1

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 (398) hide show
  1. package/README.md +17 -10
  2. package/dist/cli/digest.js +21 -0
  3. package/dist/cli/digest.js.map +1 -1
  4. package/dist/cli/init.d.ts +12 -0
  5. package/dist/cli/init.js +49 -0
  6. package/dist/cli/init.js.map +1 -0
  7. package/dist/cli/nlm.d.ts +13 -1
  8. package/dist/cli/nlm.js +766 -55
  9. package/dist/cli/nlm.js.map +1 -1
  10. package/dist/core/actions/overlay.d.ts +20 -6
  11. package/dist/core/actions/overlay.js +32 -18
  12. package/dist/core/actions/overlay.js.map +1 -1
  13. package/dist/core/adapters/claude-code.js +6 -0
  14. package/dist/core/adapters/claude-code.js.map +1 -1
  15. package/dist/core/classifier/hierarchical-classify.d.ts +6 -2
  16. package/dist/core/classifier/hierarchical-classify.js +10 -6
  17. package/dist/core/classifier/hierarchical-classify.js.map +1 -1
  18. package/dist/core/classifier/prompt.js +3 -3
  19. package/dist/core/digest/compose.d.ts +10 -0
  20. package/dist/core/digest/compose.js +7 -1
  21. package/dist/core/digest/compose.js.map +1 -1
  22. package/dist/core/embedding/embed-backfill.d.ts +13 -2
  23. package/dist/core/embedding/embed-backfill.js +181 -43
  24. package/dist/core/embedding/embed-backfill.js.map +1 -1
  25. package/dist/core/embedding/embedding-config.d.ts +17 -0
  26. package/dist/core/embedding/embedding-config.js +12 -0
  27. package/dist/core/embedding/embedding-config.js.map +1 -0
  28. package/dist/core/embedding/pg-embed-backfill.d.ts +31 -0
  29. package/dist/core/embedding/pg-embed-backfill.js +264 -0
  30. package/dist/core/embedding/pg-embed-backfill.js.map +1 -0
  31. package/dist/core/entities/dedup-suggest.d.ts +25 -0
  32. package/dist/core/entities/dedup-suggest.js +107 -0
  33. package/dist/core/entities/dedup-suggest.js.map +1 -0
  34. package/dist/core/eval/classifier-fixture-eval.d.ts +29 -0
  35. package/dist/core/eval/classifier-fixture-eval.js +217 -0
  36. package/dist/core/eval/classifier-fixture-eval.js.map +1 -0
  37. package/dist/core/eval/extraction-scoring.d.ts +63 -0
  38. package/dist/core/eval/extraction-scoring.js +79 -0
  39. package/dist/core/eval/extraction-scoring.js.map +1 -0
  40. package/dist/core/eval/run-eval.d.ts +40 -0
  41. package/dist/core/eval/run-eval.js +34 -0
  42. package/dist/core/eval/run-eval.js.map +1 -0
  43. package/dist/core/exemplars/capture-from-session.d.ts +1 -0
  44. package/dist/core/exemplars/capture-from-session.js +6 -2
  45. package/dist/core/exemplars/capture-from-session.js.map +1 -1
  46. package/dist/core/exemplars/diff-parse.d.ts +23 -0
  47. package/dist/core/exemplars/diff-parse.js +73 -0
  48. package/dist/core/exemplars/diff-parse.js.map +1 -0
  49. package/dist/core/exemplars/embed-backfill.d.ts +36 -0
  50. package/dist/core/exemplars/embed-backfill.js +85 -0
  51. package/dist/core/exemplars/embed-backfill.js.map +1 -0
  52. package/dist/core/exemplars/embed-text.d.ts +15 -0
  53. package/dist/core/exemplars/embed-text.js +18 -0
  54. package/dist/core/exemplars/embed-text.js.map +1 -0
  55. package/dist/core/exemplars/extract-exemplar.js +2 -65
  56. package/dist/core/exemplars/extract-exemplar.js.map +1 -1
  57. package/dist/core/exemplars/recall-code.js +4 -0
  58. package/dist/core/exemplars/recall-code.js.map +1 -1
  59. package/dist/core/exemplars/warm-embedder.d.ts +14 -0
  60. package/dist/core/exemplars/warm-embedder.js +18 -0
  61. package/dist/core/exemplars/warm-embedder.js.map +1 -0
  62. package/dist/core/health/corpus-state.d.ts +8 -0
  63. package/dist/core/health/corpus-state.js +11 -0
  64. package/dist/core/health/corpus-state.js.map +1 -0
  65. package/dist/core/health/embed-inflight.d.ts +9 -0
  66. package/dist/core/health/embed-inflight.js +30 -0
  67. package/dist/core/health/embed-inflight.js.map +1 -0
  68. package/dist/core/health/embedding-lane-state.d.ts +6 -0
  69. package/dist/core/health/embedding-lane-state.js +18 -0
  70. package/dist/core/health/embedding-lane-state.js.map +1 -0
  71. package/dist/core/health/warmup-state.d.ts +12 -0
  72. package/dist/core/health/warmup-state.js +12 -0
  73. package/dist/core/health/warmup-state.js.map +1 -0
  74. package/dist/core/hook/gate.d.ts +13 -6
  75. package/dist/core/hook/gate.js +47 -6
  76. package/dist/core/hook/gate.js.map +1 -1
  77. package/dist/core/hook/hook-log.d.ts +5 -0
  78. package/dist/core/hook/hook-log.js.map +1 -1
  79. package/dist/core/hook/memo.d.ts +12 -0
  80. package/dist/core/hook/memo.js +41 -1
  81. package/dist/core/hook/memo.js.map +1 -1
  82. package/dist/core/hook/pointer-block.d.ts +1 -0
  83. package/dist/core/hook/pointer-block.js +23 -2
  84. package/dist/core/hook/pointer-block.js.map +1 -1
  85. package/dist/core/hook/query-extract.d.ts +2 -1
  86. package/dist/core/hook/query-extract.js +10 -1
  87. package/dist/core/hook/query-extract.js.map +1 -1
  88. package/dist/core/hook/resolve-conversation-by-query.d.ts +28 -0
  89. package/dist/core/hook/resolve-conversation-by-query.js +98 -0
  90. package/dist/core/hook/resolve-conversation-by-query.js.map +1 -0
  91. package/dist/core/hook/select.d.ts +8 -0
  92. package/dist/core/hook/select.js +9 -2
  93. package/dist/core/hook/select.js.map +1 -1
  94. package/dist/core/hook/transcript.js +16 -2
  95. package/dist/core/hook/transcript.js.map +1 -1
  96. package/dist/core/ingest/ingest-session.d.ts +5 -0
  97. package/dist/core/ingest/ingest-session.js +3 -0
  98. package/dist/core/ingest/ingest-session.js.map +1 -1
  99. package/dist/core/ingest/reclassify-oversized.d.ts +5 -0
  100. package/dist/core/ingest/reclassify-oversized.js +3 -0
  101. package/dist/core/ingest/reclassify-oversized.js.map +1 -1
  102. package/dist/core/ingest/reprocess.d.ts +96 -0
  103. package/dist/core/ingest/reprocess.js +275 -0
  104. package/dist/core/ingest/reprocess.js.map +1 -0
  105. package/dist/core/integrity/check-invariants.d.ts +6 -1
  106. package/dist/core/integrity/check-invariants.js +209 -3
  107. package/dist/core/integrity/check-invariants.js.map +1 -1
  108. package/dist/core/metrics/corpus-stats.d.ts +49 -0
  109. package/dist/core/metrics/corpus-stats.js +111 -0
  110. package/dist/core/metrics/corpus-stats.js.map +1 -0
  111. package/dist/core/metrics/re-derivation.d.ts +48 -0
  112. package/dist/core/metrics/re-derivation.js +87 -0
  113. package/dist/core/metrics/re-derivation.js.map +1 -0
  114. package/dist/core/recall/citation-log.d.ts +8 -0
  115. package/dist/core/recall/citation-log.js +17 -0
  116. package/dist/core/recall/citation-log.js.map +1 -1
  117. package/dist/core/recall/query-intent.d.ts +7 -0
  118. package/dist/core/recall/query-intent.js +25 -0
  119. package/dist/core/recall/query-intent.js.map +1 -0
  120. package/dist/core/recall/query-log.d.ts +4 -0
  121. package/dist/core/recall/query-log.js +4 -0
  122. package/dist/core/recall/query-log.js.map +1 -1
  123. package/dist/core/recall/recall-service.d.ts +37 -1
  124. package/dist/core/recall/recall-service.js +106 -63
  125. package/dist/core/recall/recall-service.js.map +1 -1
  126. package/dist/core/recall/related-exemplars.d.ts +1 -0
  127. package/dist/core/recall/related-exemplars.js +4 -1
  128. package/dist/core/recall/related-exemplars.js.map +1 -1
  129. package/dist/core/recall/related-facts.js +2 -2
  130. package/dist/core/recall/related-facts.js.map +1 -1
  131. package/dist/core/recall/reranker.d.ts +2 -1
  132. package/dist/core/recall/reranker.js +3 -3
  133. package/dist/core/recall/reranker.js.map +1 -1
  134. package/dist/core/recall-facts/fact-query-log.d.ts +7 -0
  135. package/dist/core/recall-facts/fact-query-log.js +2 -0
  136. package/dist/core/recall-facts/fact-query-log.js.map +1 -1
  137. package/dist/core/recall-facts/fact-recall-service.d.ts +9 -1
  138. package/dist/core/recall-facts/fact-recall-service.js +21 -8
  139. package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
  140. package/dist/core/scheduler/scan-once.d.ts +6 -6
  141. package/dist/core/scheduler/scan-once.js +13 -49
  142. package/dist/core/scheduler/scan-once.js.map +1 -1
  143. package/dist/core/scheduler/scheduler.d.ts +10 -0
  144. package/dist/core/scheduler/scheduler.js +80 -28
  145. package/dist/core/scheduler/scheduler.js.map +1 -1
  146. package/dist/core/signals/code-signal.d.ts +49 -0
  147. package/dist/core/signals/code-signal.js +53 -0
  148. package/dist/core/signals/code-signal.js.map +1 -0
  149. package/dist/core/storage/db-restore.js +3 -1
  150. package/dist/core/storage/db-restore.js.map +1 -1
  151. package/dist/core/storage/fact-batch.d.ts +9 -0
  152. package/dist/core/storage/fact-batch.js +14 -0
  153. package/dist/core/storage/fact-batch.js.map +1 -0
  154. package/dist/core/storage/pg-code-exemplar-store.d.ts +1 -0
  155. package/dist/core/storage/pg-code-exemplar-store.js +7 -0
  156. package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
  157. package/dist/core/storage/pg-embedding-config.d.ts +29 -0
  158. package/dist/core/storage/pg-embedding-config.js +53 -0
  159. package/dist/core/storage/pg-embedding-config.js.map +1 -0
  160. package/dist/core/storage/pg-entity-store.d.ts +12 -0
  161. package/dist/core/storage/pg-entity-store.js +79 -0
  162. package/dist/core/storage/pg-entity-store.js.map +1 -0
  163. package/dist/core/storage/pg-fact-ingest.d.ts +15 -0
  164. package/dist/core/storage/pg-fact-ingest.js +34 -0
  165. package/dist/core/storage/pg-fact-ingest.js.map +1 -0
  166. package/dist/core/storage/pg-fact-store.d.ts +3 -0
  167. package/dist/core/storage/pg-fact-store.js +26 -30
  168. package/dist/core/storage/pg-fact-store.js.map +1 -1
  169. package/dist/core/storage/pg-migrate.d.ts +18 -0
  170. package/dist/core/storage/pg-migrate.js +73 -0
  171. package/dist/core/storage/pg-migrate.js.map +1 -0
  172. package/dist/core/storage/pg-session-store.d.ts +9 -0
  173. package/dist/core/storage/pg-session-store.js +204 -76
  174. package/dist/core/storage/pg-session-store.js.map +1 -1
  175. package/dist/core/storage/pg-storage.d.ts +9 -7
  176. package/dist/core/storage/pg-storage.js +20 -50
  177. package/dist/core/storage/pg-storage.js.map +1 -1
  178. package/dist/core/storage/pg-workstream-store.d.ts +24 -0
  179. package/dist/core/storage/pg-workstream-store.js +96 -0
  180. package/dist/core/storage/pg-workstream-store.js.map +1 -0
  181. package/dist/core/storage/sqlite-code-exemplar-store.d.ts +1 -0
  182. package/dist/core/storage/sqlite-code-exemplar-store.js +11 -0
  183. package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
  184. package/dist/core/storage/sqlite-embedding-config.d.ts +10 -0
  185. package/dist/core/storage/sqlite-embedding-config.js +27 -0
  186. package/dist/core/storage/sqlite-embedding-config.js.map +1 -0
  187. package/dist/core/storage/sqlite-entity-store.d.ts +13 -0
  188. package/dist/core/storage/sqlite-entity-store.js +96 -0
  189. package/dist/core/storage/sqlite-entity-store.js.map +1 -0
  190. package/dist/core/storage/sqlite-fact-store.d.ts +15 -7
  191. package/dist/core/storage/sqlite-fact-store.js +76 -25
  192. package/dist/core/storage/sqlite-fact-store.js.map +1 -1
  193. package/dist/core/storage/sqlite-session-store.d.ts +20 -5
  194. package/dist/core/storage/sqlite-session-store.js +183 -68
  195. package/dist/core/storage/sqlite-session-store.js.map +1 -1
  196. package/dist/core/storage/sqlite-storage.d.ts +8 -8
  197. package/dist/core/storage/sqlite-storage.js +21 -32
  198. package/dist/core/storage/sqlite-storage.js.map +1 -1
  199. package/dist/core/storage/sqlite-workstream-store.d.ts +24 -0
  200. package/dist/core/storage/sqlite-workstream-store.js +98 -0
  201. package/dist/core/storage/sqlite-workstream-store.js.map +1 -0
  202. package/dist/core/util/with-timeout.d.ts +5 -0
  203. package/dist/core/util/with-timeout.js +20 -0
  204. package/dist/core/util/with-timeout.js.map +1 -0
  205. package/dist/core/work-digest/active-spans.d.ts +8 -0
  206. package/dist/core/work-digest/active-spans.js +29 -0
  207. package/dist/core/work-digest/active-spans.js.map +1 -0
  208. package/dist/core/work-digest/attribute.d.ts +15 -0
  209. package/dist/core/work-digest/attribute.js +76 -0
  210. package/dist/core/work-digest/attribute.js.map +1 -0
  211. package/dist/core/work-digest/build-work-digest.d.ts +12 -0
  212. package/dist/core/work-digest/build-work-digest.js +69 -0
  213. package/dist/core/work-digest/build-work-digest.js.map +1 -0
  214. package/dist/core/work-digest/compose-work-digest.d.ts +3 -0
  215. package/dist/core/work-digest/compose-work-digest.js +52 -0
  216. package/dist/core/work-digest/compose-work-digest.js.map +1 -0
  217. package/dist/core/work-digest/merge-active.d.ts +10 -0
  218. package/dist/core/work-digest/merge-active.js +22 -0
  219. package/dist/core/work-digest/merge-active.js.map +1 -0
  220. package/dist/core/work-digest/read-transcript-timestamps.d.ts +7 -0
  221. package/dist/core/work-digest/read-transcript-timestamps.js +40 -0
  222. package/dist/core/work-digest/read-transcript-timestamps.js.map +1 -0
  223. package/dist/core/work-digest/topics.d.ts +18 -0
  224. package/dist/core/work-digest/topics.js +26 -0
  225. package/dist/core/work-digest/topics.js.map +1 -0
  226. package/dist/core/work-digest/types.d.ts +45 -0
  227. package/dist/core/work-digest/types.js +2 -0
  228. package/dist/core/work-digest/types.js.map +1 -0
  229. package/dist/core/workstream/bind.d.ts +25 -0
  230. package/dist/core/workstream/bind.js +23 -0
  231. package/dist/core/workstream/bind.js.map +1 -0
  232. package/dist/core/workstream/compose-recall.d.ts +2 -0
  233. package/dist/core/workstream/compose-recall.js +39 -0
  234. package/dist/core/workstream/compose-recall.js.map +1 -0
  235. package/dist/core/workstream/merge-suggest.d.ts +19 -0
  236. package/dist/core/workstream/merge-suggest.js +61 -0
  237. package/dist/core/workstream/merge-suggest.js.map +1 -0
  238. package/dist/core/workstream/model.d.ts +20 -0
  239. package/dist/core/workstream/model.js +9 -0
  240. package/dist/core/workstream/model.js.map +1 -0
  241. package/dist/core/workstream/name-match.d.ts +8 -0
  242. package/dist/core/workstream/name-match.js +18 -0
  243. package/dist/core/workstream/name-match.js.map +1 -0
  244. package/dist/core/workstream/resolve.d.ts +10 -0
  245. package/dist/core/workstream/resolve.js +21 -0
  246. package/dist/core/workstream/resolve.js.map +1 -0
  247. package/dist/core/workstream/rollup.d.ts +12 -0
  248. package/dist/core/workstream/rollup.js +17 -0
  249. package/dist/core/workstream/rollup.js.map +1 -0
  250. package/dist/core/workstream/work-topics.d.ts +10 -0
  251. package/dist/core/workstream/work-topics.js +55 -0
  252. package/dist/core/workstream/work-topics.js.map +1 -0
  253. package/dist/hook/hook-helpers.d.ts +4 -0
  254. package/dist/hook/hook-helpers.js +36 -0
  255. package/dist/hook/hook-helpers.js.map +1 -0
  256. package/dist/hook/pre-compact-hook.d.ts +23 -0
  257. package/dist/hook/pre-compact-hook.js +66 -0
  258. package/dist/hook/pre-compact-hook.js.map +1 -0
  259. package/dist/hook/prompt-recall-hook.d.ts +37 -0
  260. package/dist/hook/prompt-recall-hook.js +120 -20
  261. package/dist/hook/prompt-recall-hook.js.map +1 -1
  262. package/dist/hook/recall-gate.d.ts +29 -0
  263. package/dist/hook/recall-gate.js +50 -0
  264. package/dist/hook/recall-gate.js.map +1 -0
  265. package/dist/hook/recall-over-http.d.ts +1 -1
  266. package/dist/hook/recall-over-http.js +11 -11
  267. package/dist/hook/recall-over-http.js.map +1 -1
  268. package/dist/hook/recent-context.d.ts +25 -0
  269. package/dist/hook/recent-context.js +104 -0
  270. package/dist/hook/recent-context.js.map +1 -0
  271. package/dist/hook/score-floor.d.ts +27 -0
  272. package/dist/hook/score-floor.js +42 -0
  273. package/dist/hook/score-floor.js.map +1 -0
  274. package/dist/hook/session-end-hook.js +8 -29
  275. package/dist/hook/session-end-hook.js.map +1 -1
  276. package/dist/hook/session-start-hook.js +24 -59
  277. package/dist/hook/session-start-hook.js.map +1 -1
  278. package/dist/hook/stop-hook.js +22 -59
  279. package/dist/hook/stop-hook.js.map +1 -1
  280. package/dist/hook/subagent-start-hook.d.ts +28 -0
  281. package/dist/hook/subagent-start-hook.js +81 -0
  282. package/dist/hook/subagent-start-hook.js.map +1 -0
  283. package/dist/http/app.js +28 -8
  284. package/dist/http/app.js.map +1 -1
  285. package/dist/install/setup.js +2 -0
  286. package/dist/install/setup.js.map +1 -1
  287. package/dist/llm/build-classifier.d.ts +22 -0
  288. package/dist/llm/build-classifier.js +42 -0
  289. package/dist/llm/build-classifier.js.map +1 -0
  290. package/dist/llm/build-embedder.d.ts +11 -0
  291. package/dist/llm/build-embedder.js +42 -0
  292. package/dist/llm/build-embedder.js.map +1 -0
  293. package/dist/llm/bundled-embedder-client.d.ts +37 -0
  294. package/dist/llm/bundled-embedder-client.js +90 -0
  295. package/dist/llm/bundled-embedder-client.js.map +1 -0
  296. package/dist/llm/classifier-box.d.ts +13 -5
  297. package/dist/llm/classifier-box.js +28 -6
  298. package/dist/llm/classifier-box.js.map +1 -1
  299. package/dist/llm/classifier-egress.d.ts +18 -0
  300. package/dist/llm/classifier-egress.js +59 -0
  301. package/dist/llm/classifier-egress.js.map +1 -0
  302. package/dist/llm/client-shared.d.ts +4 -0
  303. package/dist/llm/client-shared.js +39 -0
  304. package/dist/llm/client-shared.js.map +1 -0
  305. package/dist/llm/deepseek-client.d.ts +20 -0
  306. package/dist/llm/deepseek-client.js +59 -45
  307. package/dist/llm/deepseek-client.js.map +1 -1
  308. package/dist/llm/embedder-info.d.ts +15 -0
  309. package/dist/llm/embedder-info.js +27 -0
  310. package/dist/llm/embedder-info.js.map +1 -0
  311. package/dist/llm/model-quirks.d.ts +4 -0
  312. package/dist/llm/model-quirks.js +7 -0
  313. package/dist/llm/model-quirks.js.map +1 -0
  314. package/dist/llm/naming.d.ts +5 -0
  315. package/dist/llm/naming.js +20 -0
  316. package/dist/llm/naming.js.map +1 -0
  317. package/dist/llm/ollama-client.d.ts +7 -8
  318. package/dist/llm/ollama-client.js +59 -51
  319. package/dist/llm/ollama-client.js.map +1 -1
  320. package/dist/llm/ollama-code-embedder.d.ts +8 -3
  321. package/dist/llm/ollama-code-embedder.js +14 -24
  322. package/dist/llm/ollama-code-embedder.js.map +1 -1
  323. package/dist/llm/openai-code-embedder-client.d.ts +25 -0
  324. package/dist/llm/openai-code-embedder-client.js +52 -0
  325. package/dist/llm/openai-code-embedder-client.js.map +1 -0
  326. package/dist/llm/openai-embedder-client.d.ts +41 -0
  327. package/dist/llm/openai-embedder-client.js +83 -0
  328. package/dist/llm/openai-embedder-client.js.map +1 -0
  329. package/dist/mcp/server.d.ts +47 -2
  330. package/dist/mcp/server.js +303 -18
  331. package/dist/mcp/server.js.map +1 -1
  332. package/dist/ports/code-embedder.d.ts +6 -1
  333. package/dist/ports/code-exemplar-store.d.ts +5 -0
  334. package/dist/ports/entity-store.d.ts +35 -0
  335. package/dist/ports/entity-store.js +7 -0
  336. package/dist/ports/entity-store.js.map +1 -0
  337. package/dist/ports/fact-store.d.ts +0 -0
  338. package/dist/ports/fact-store.js.map +1 -1
  339. package/dist/ports/llm-client.d.ts +9 -1
  340. package/dist/ports/session-store.d.ts +12 -0
  341. package/dist/ports/storage.d.ts +7 -30
  342. package/dist/ports/storage.js +3 -7
  343. package/dist/ports/storage.js.map +1 -1
  344. package/dist/ports/transcript-adapter.d.ts +2 -0
  345. package/dist/ports/workstream-store.d.ts +21 -0
  346. package/dist/ports/workstream-store.js +2 -0
  347. package/dist/ports/workstream-store.js.map +1 -0
  348. package/dist/shared/types.d.ts +23 -2
  349. package/fixtures/classifier-gold/README.md +94 -0
  350. package/fixtures/classifier-gold/reference.json +318 -0
  351. package/fixtures/classifier-gold/transcripts/01-bug-fix-null-pointer-api.txt +75 -0
  352. package/fixtures/classifier-gold/transcripts/02-bug-fix-race-condition-queue.txt +77 -0
  353. package/fixtures/classifier-gold/transcripts/03-bug-fix-jwt-clock-skew.txt +86 -0
  354. package/fixtures/classifier-gold/transcripts/04-feature-webhook-retry.txt +95 -0
  355. package/fixtures/classifier-gold/transcripts/05-feature-csv-export.txt +86 -0
  356. package/fixtures/classifier-gold/transcripts/06-feature-rate-limiting.txt +61 -0
  357. package/fixtures/classifier-gold/transcripts/07-refactor-db-abstraction.txt +72 -0
  358. package/fixtures/classifier-gold/transcripts/08-refactor-config-loading.txt +89 -0
  359. package/fixtures/classifier-gold/transcripts/09-ops-docker-staging.txt +94 -0
  360. package/fixtures/classifier-gold/transcripts/10-ops-nginx-ssl.txt +121 -0
  361. package/fixtures/classifier-gold/transcripts/11-ops-postgres-migration.txt +74 -0
  362. package/fixtures/classifier-gold/transcripts/12-ops-cron-debugging.txt +73 -0
  363. package/fixtures/classifier-gold/transcripts/13-research-vector-db-comparison.txt +59 -0
  364. package/fixtures/classifier-gold/transcripts/14-research-oauth-provider.txt +74 -0
  365. package/fixtures/classifier-gold/transcripts/15-writing-api-docs.txt +154 -0
  366. package/fixtures/classifier-gold/transcripts/16-meeting-sprint-planning.txt +69 -0
  367. package/fixtures/classifier-gold/transcripts/17-trivial-list-files.txt +14 -0
  368. package/fixtures/classifier-gold/transcripts/18-trivial-version-check.txt +21 -0
  369. package/fixtures/classifier-gold/transcripts/19-trivial-syntax-help.txt +29 -0
  370. package/fixtures/classifier-gold/transcripts/20-trivial-read-package-json.txt +41 -0
  371. package/migrations/000_initial_schema.sql +1 -1
  372. package/migrations/024_backfill_exemplar_repo_basename.sql +32 -0
  373. package/migrations/025_workstreams.sql +30 -0
  374. package/migrations/026_embedding_config.sql +12 -0
  375. package/migrations/027_drop_legacy_session_embeddings.sql +7 -0
  376. package/migrations/028_classifier_provenance.sql +5 -0
  377. package/migrations/pg/001_initial.sql +34 -0
  378. package/migrations/pg/019_split_replaces.sql +16 -7
  379. package/migrations/pg/025_workstreams.sql +34 -0
  380. package/migrations/pg/026_embedding_config.sql +12 -0
  381. package/migrations/pg/028_classifier_provenance.sql +5 -0
  382. package/migrations/pg/029_entity_variants.sql +9 -0
  383. package/nlm/index.js +395 -37
  384. package/package.json +12 -2
  385. package/plugin/.codex-plugin/plugin.json +1 -1
  386. package/plugin/scripts/prompt-recall-hook.mjs +402 -41
  387. package/plugin/scripts/session-start-hook.mjs +286 -60
  388. package/plugin/scripts/stop-hook.mjs +85 -65
  389. package/plugin-hermes-agent/__init__.py +2 -1
  390. package/templates/agent-contract/README.md +24 -0
  391. package/templates/agent-contract/claude-code.md +40 -0
  392. package/templates/agent-contract/generic.md +38 -0
  393. package/dist/core/embedding/embed-normalize.d.ts +0 -28
  394. package/dist/core/embedding/embed-normalize.js +0 -98
  395. package/dist/core/embedding/embed-normalize.js.map +0 -1
  396. package/dist/core/storage/pg-tx-context.d.ts +0 -52
  397. package/dist/core/storage/pg-tx-context.js +0 -149
  398. package/dist/core/storage/pg-tx-context.js.map +0 -1
@@ -0,0 +1,86 @@
1
+ User: lumens needs a CSV export endpoint. Users want to download their usage report as a CSV. The report query already exists in src/reports/usage.go and returns a slice of UsageRow structs. I want to add GET /reports/export?format=csv.
2
+
3
+ Agent: [Read: src/reports/usage.go]
4
+
5
+ ```go
6
+ type UsageRow struct {
7
+ Date string
8
+ Metric string
9
+ Value float64
10
+ Unit string
11
+ }
12
+
13
+ func QueryUsage(ctx context.Context, db *sql.DB, accountID string, from, to time.Time) ([]UsageRow, error) {
14
+ // ... query implementation
15
+ }
16
+ ```
17
+
18
+ [Read: src/api/routes.go]
19
+
20
+ I see the existing route structure. I'll add the export handler. The question is whether to buffer the rows in memory and send them all at once, or stream directly. For large reports this matters.
21
+
22
+ How large can these reports get? Hundreds of rows, or could it be tens of thousands?
23
+
24
+ User: Reports can be up to 100k rows for heavy users. Stream directly, don't buffer.
25
+
26
+ Agent: In that case I'll write directly to the http.ResponseWriter using encoding/csv rather than building a string in memory. I'll also set the Content-Disposition header so browsers download the file rather than display it.
27
+
28
+ [Edit: src/api/export_handler.go] (new file)
29
+
30
+ ```go
31
+ package api
32
+
33
+ import (
34
+ "encoding/csv"
35
+ "fmt"
36
+ "net/http"
37
+ "time"
38
+
39
+ "lumens/reports"
40
+ )
41
+
42
+ func (s *Server) handleCSVExport(w http.ResponseWriter, r *http.Request) {
43
+ accountID := r.Context().Value(ctxKeyAccountID).(string)
44
+ from, to, err := parseDateRange(r)
45
+ if err != nil {
46
+ http.Error(w, "invalid date range", http.StatusBadRequest)
47
+ return
48
+ }
49
+ rows, err := reports.QueryUsage(r.Context(), s.db, accountID, from, to)
50
+ if err != nil {
51
+ http.Error(w, "query failed", http.StatusInternalServerError)
52
+ return
53
+ }
54
+ filename := fmt.Sprintf("usage-%s.csv", time.Now().Format("2006-01-02"))
55
+ w.Header().Set("Content-Type", "text/csv")
56
+ w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
57
+ cw := csv.NewWriter(w)
58
+ _ = cw.Write([]string{"Date", "Metric", "Value", "Unit"})
59
+ for _, row := range rows {
60
+ _ = cw.Write([]string{row.Date, row.Metric, fmt.Sprintf("%.4f", row.Value), row.Unit})
61
+ }
62
+ cw.Flush()
63
+ }
64
+ ```
65
+
66
+ [Edit: src/api/routes.go]
67
+
68
+ Added `GET /reports/export` route pointing to handleCSVExport.
69
+
70
+ User: Should I add this to the OpenAPI spec?
71
+
72
+ Agent: Yes. [Read: api/openapi.yaml]
73
+
74
+ [Edit: api/openapi.yaml]
75
+
76
+ Added the /reports/export path with GET, query params `from`, `to`, and the `format` param (currently only `csv` accepted), response schema as text/csv with the Content-Disposition header documented.
77
+
78
+ User: Run the existing API tests to make sure nothing broke.
79
+
80
+ Agent: [Bash: go test ./src/api/...]
81
+
82
+ ```
83
+ ok lumens/src/api 1.243s
84
+ ```
85
+
86
+ All passing. The export handler streams rows directly to the response writer without buffering in memory.
@@ -0,0 +1,61 @@
1
+ User: crestapi needs API rate limiting. We're getting abuse from a handful of API keys hammering the search endpoint. I want per-API-key rate limiting: 100 requests per minute, returning 429 with a Retry-After header when exceeded.
2
+
3
+ Agent: The standard approach for FastAPI is slowapi, which wraps limits.py and integrates as middleware. It supports Redis as the backing store so limits are shared across multiple crestapi workers. Let me check what's already in requirements.txt and whether Redis is already a dependency.
4
+
5
+ [Bash: grep -E "redis|slowapi|limits" requirements.txt]
6
+
7
+ ```
8
+ redis==5.0.4
9
+ ```
10
+
11
+ Redis is there. slowapi is not yet installed. I'll add it.
12
+
13
+ User: Go ahead and add slowapi.
14
+
15
+ Agent: [Edit: requirements.txt]
16
+
17
+ Added `slowapi==0.1.9`.
18
+
19
+ [Read: src/main.py]
20
+
21
+ [Edit: src/main.py]
22
+
23
+ ```python
24
+ from slowapi import Limiter, _rate_limit_exceeded_handler
25
+ from slowapi.util import get_remote_address
26
+ from slowapi.errors import RateLimitExceeded
27
+ from slowapi.middleware import SlowAPIMiddleware
28
+
29
+ def get_api_key(request: Request) -> str:
30
+ return request.headers.get("X-API-Key", get_remote_address(request))
31
+
32
+ limiter = Limiter(key_func=get_api_key, storage_uri=settings.REDIS_URL)
33
+ app.state.limiter = limiter
34
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
35
+ app.add_middleware(SlowAPIMiddleware)
36
+ ```
37
+
38
+ [Read: src/routes/search.py]
39
+
40
+ [Edit: src/routes/search.py]
41
+
42
+ Added the `@limiter.limit("100/minute")` decorator to the search endpoint handler.
43
+
44
+ User: Will the 429 response include a Retry-After header?
45
+
46
+ Agent: slowapi's built-in _rate_limit_exceeded_handler sets the Retry-After header automatically based on the window reset time. You don't need to add it manually.
47
+
48
+ User: Good. What happens to unauthenticated requests with no API key header?
49
+
50
+ Agent: The `get_api_key` function falls back to `get_remote_address`, which returns the client IP. So unauthenticated clients are rate-limited by IP at the same 100 req/min threshold. If you want a stricter limit for unauthenticated access you'd need a separate decorator with a lower limit on those routes.
51
+
52
+ User: Keep it the same limit for now. Run the tests.
53
+
54
+ Agent: [Bash: pip install -r requirements.txt -q && pytest tests/ -q]
55
+
56
+ ```
57
+ ...................................
58
+ 35 passed in 2.14s
59
+ ```
60
+
61
+ All passing. slowapi with Redis backing installed and wired to the search endpoint.
@@ -0,0 +1,72 @@
1
+ User: In vaultdb I want to refactor the database layer so we can swap between Postgres and SQLite for tests without touching core logic. Right now the core modules import from src/adapters/postgres.ts directly.
2
+
3
+ Agent: [Bash: grep -rn "from.*postgres" src/core/]
4
+
5
+ ```
6
+ src/core/session-store.ts:3: import { pgPool } from "../adapters/postgres.js";
7
+ src/core/fact-store.ts:2: import { pgPool } from "../adapters/postgres.js";
8
+ src/core/search.ts:5: import { pgPool } from "../adapters/postgres.js";
9
+ ```
10
+
11
+ Three core modules import Postgres directly. The fix is to define a DbAdapter interface in the ports layer and have the core modules depend on that interface rather than the concrete Postgres adapter. The Postgres and SQLite implementations both implement the interface.
12
+
13
+ User: Yes. Let's extract the interface to src/ports/db-adapter.ts.
14
+
15
+ Agent: I'll first enumerate what operations the core modules actually call on pgPool, then define only those in the interface.
16
+
17
+ [Read: src/core/session-store.ts]
18
+ [Read: src/core/fact-store.ts]
19
+ [Read: src/core/search.ts]
20
+
21
+ Operations used: `query(sql, params)` returning rows, `transaction(fn)` for multi-statement writes. That's it.
22
+
23
+ [Write: src/ports/db-adapter.ts]
24
+
25
+ ```typescript
26
+ export interface QueryResult<T = Record<string, unknown>> {
27
+ readonly rows: ReadonlyArray<T>;
28
+ }
29
+
30
+ export interface DbAdapter {
31
+ query<T = Record<string, unknown>>(
32
+ sql: string,
33
+ params?: ReadonlyArray<unknown>,
34
+ ): Promise<QueryResult<T>>;
35
+ transaction<T>(fn: (db: DbAdapter) => Promise<T>): Promise<T>;
36
+ }
37
+ ```
38
+
39
+ [Edit: src/adapters/postgres.ts]
40
+
41
+ Implemented DbAdapter on the existing PgAdapter class. Added `transaction()` using `BEGIN` / `COMMIT` / `ROLLBACK` calls.
42
+
43
+ [Write: src/adapters/sqlite.ts]
44
+
45
+ New SQLite adapter implementing DbAdapter for use in tests using better-sqlite3.
46
+
47
+ [Edit: src/core/session-store.ts]
48
+ [Edit: src/core/fact-store.ts]
49
+ [Edit: src/core/search.ts]
50
+
51
+ Replaced `import { pgPool }` with `import type { DbAdapter } from "../ports/db-adapter.js"` in each. Each module now receives the adapter through its constructor or init function rather than importing it directly.
52
+
53
+ User: Where do we wire the concrete adapter?
54
+
55
+ Agent: In src/app.ts, which is the composition root. That's the only place that imports PgAdapter. In tests, SqliteAdapter is passed instead.
56
+
57
+ User: Can you verify no core module imports from adapters directly after this refactor?
58
+
59
+ Agent: [Bash: grep -rn "from.*adapters" src/core/]
60
+
61
+ No output. Core is clean.
62
+
63
+ User: Good. Run the full test suite.
64
+
65
+ Agent: [Bash: npm test]
66
+
67
+ ```
68
+ Test Suites: 8 passed, 8 total
69
+ Tests: 61 passed, 61 total
70
+ ```
71
+
72
+ All green. The DbAdapter interface is in the ports layer; SQL stays in adapters, not in core.
@@ -0,0 +1,89 @@
1
+ User: axiom-worker has config scattered across the codebase. I want to centralize it into a single Pydantic Settings class loaded once at startup. Can you audit how config is currently read?
2
+
3
+ Agent: [Bash: grep -rn "os.getenv\|os.environ" src/ --include="*.py"]
4
+
5
+ ```
6
+ src/worker/processor.py:14: DB_URL = os.getenv("DATABASE_URL", "")
7
+ src/worker/processor.py:31: BATCH_SIZE = int(os.getenv("BATCH_SIZE", "50"))
8
+ src/jobs/runner.py:8: QUEUE_URL = os.environ["QUEUE_URL"]
9
+ src/jobs/runner.py:9: RETRY_LIMIT = int(os.environ.get("RETRY_LIMIT", "3"))
10
+ src/notifier/slack.py:3: SLACK_WEBHOOK = os.environ["SLACK_WEBHOOK_URL"]
11
+ src/health/check.py:22: PORT = int(os.getenv("PORT", "8080"))
12
+ ```
13
+
14
+ Six separate call sites across four files. Some use `os.getenv` with defaults, some use `os.environ` which raises on missing keys. Inconsistent.
15
+
16
+ User: Consolidate these into a Pydantic Settings class.
17
+
18
+ Agent: [Write: src/config.py]
19
+
20
+ ```python
21
+ from pydantic_settings import BaseSettings, SettingsConfigDict
22
+
23
+ class Settings(BaseSettings):
24
+ model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
25
+
26
+ database_url: str
27
+ queue_url: str
28
+ slack_webhook_url: str
29
+ batch_size: int = 50
30
+ retry_limit: int = 3
31
+ port: int = 8080
32
+
33
+ settings = Settings()
34
+ ```
35
+
36
+ `database_url`, `queue_url`, and `slack_webhook_url` have no default, so startup fails fast with a clear Pydantic ValidationError if they are missing rather than silently proceeding with an empty string.
37
+
38
+ [Edit: src/worker/processor.py]
39
+ [Edit: src/jobs/runner.py]
40
+ [Edit: src/notifier/slack.py]
41
+ [Edit: src/health/check.py]
42
+
43
+ Replaced all `os.getenv`/`os.environ` calls with imports from `src/config.py`.
44
+
45
+ User: Is pydantic-settings already a dependency?
46
+
47
+ Agent: [Bash: grep "pydantic" requirements.txt]
48
+
49
+ ```
50
+ pydantic==2.7.1
51
+ ```
52
+
53
+ pydantic-settings is a separate package. I'll add it.
54
+
55
+ [Edit: requirements.txt]
56
+
57
+ Added `pydantic-settings==2.3.4`.
58
+
59
+ User: Any tests need updating?
60
+
61
+ Agent: [Bash: grep -rn "os.environ\|os.getenv\|monkeypatch.setenv" tests/ --include="*.py"]
62
+
63
+ ```
64
+ tests/test_processor.py:12: monkeypatch.setenv("DATABASE_URL", "sqlite:///:memory:")
65
+ tests/test_processor.py:13: monkeypatch.setenv("BATCH_SIZE", "5")
66
+ tests/test_runner.py:9: monkeypatch.setenv("QUEUE_URL", "amqp://localhost")
67
+ ```
68
+
69
+ These tests set env vars which Pydantic Settings will pick up automatically since it reads from the environment. However, the Settings object is a module-level singleton in config.py, so it is initialized once at import time before `monkeypatch.setenv` runs.
70
+
71
+ The fix is to move Settings instantiation to a function and call it inside each module that needs it, or to use pydantic-settings' `_env_file` override per test. The simpler fix for tests: patch `src.config.settings` directly.
72
+
73
+ I'll update the tests to patch the singleton.
74
+
75
+ [Edit: tests/test_processor.py]
76
+ [Edit: tests/test_runner.py]
77
+
78
+ User: Run the tests.
79
+
80
+ Agent: [Bash: pip install pydantic-settings==2.3.4 -q && pytest tests/ -v]
81
+
82
+ ```
83
+ tests/test_processor.py::test_batch_size_respected PASSED
84
+ tests/test_runner.py::test_retry_on_failure PASSED
85
+ tests/test_health_check.py::test_port_default PASSED
86
+ 5 passed in 1.87s
87
+ ```
88
+
89
+ All green. Config is now centralized in a single Pydantic Settings class loaded at startup.
@@ -0,0 +1,94 @@
1
+ User: droneflow needs a staging environment that mirrors production. Right now we only have a docker-compose.yml for local dev. I want a separate staging compose file with Traefik as the router and real Postgres instead of the local volume.
2
+
3
+ Agent: [Read: docker-compose.yml]
4
+
5
+ ```yaml
6
+ services:
7
+ api:
8
+ build: .
9
+ ports:
10
+ - "3000:3000"
11
+ environment:
12
+ DATABASE_URL: postgres://dev:dev@db:5432/droneflow
13
+ db:
14
+ image: postgres:16
15
+ volumes:
16
+ - pgdata:/var/lib/postgresql/data
17
+ ```
18
+
19
+ For staging I'll create docker-compose.staging.yml that:
20
+ - Removes the direct port binding (Traefik will handle routing)
21
+ - Adds Traefik as a router service with Let's Encrypt TLS
22
+ - Points DATABASE_URL at an external Postgres instance (staging.db.example.com)
23
+ - Adds explicit labels so Traefik routes api.staging.example.com to the api service
24
+
25
+ User: The staging database is already provisioned at staging.db.example.com. Use that.
26
+
27
+ Agent: [Write: docker-compose.staging.yml]
28
+
29
+ ```yaml
30
+ services:
31
+ traefik:
32
+ image: traefik:v3.1
33
+ command:
34
+ - --providers.docker=true
35
+ - --providers.docker.exposedbydefault=false
36
+ - --entrypoints.web.address=:80
37
+ - --entrypoints.websecure.address=:443
38
+ - --certificatesresolvers.letsencrypt.acme.email=ops@example.com
39
+ - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
40
+ - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
41
+ ports:
42
+ - "80:80"
43
+ - "443:443"
44
+ volumes:
45
+ - /var/run/docker.sock:/var/run/docker.sock:ro
46
+ - letsencrypt:/letsencrypt
47
+
48
+ api:
49
+ build: .
50
+ environment:
51
+ DATABASE_URL: postgres://app:${STAGING_DB_PASSWORD}@staging.db.example.com:5432/droneflow_staging
52
+ NODE_ENV: staging
53
+ labels:
54
+ - traefik.enable=true
55
+ - traefik.http.routers.api.rule=Host(`api.staging.example.com`)
56
+ - traefik.http.routers.api.entrypoints=websecure
57
+ - traefik.http.routers.api.tls.certresolver=letsencrypt
58
+
59
+ volumes:
60
+ letsencrypt:
61
+ ```
62
+
63
+ User: Should the staging environment auto-pull new images on deploy?
64
+
65
+ Agent: Not with compose alone. For that you'd need Watchtower or a deploy hook. The typical pattern is to keep deploy explicit: SSH to the staging host and run `docker compose -f docker-compose.staging.yml pull && docker compose -f docker-compose.staging.yml up -d`. That way staging only updates when you intentionally trigger a deploy, not on every push.
66
+
67
+ User: Let's keep it explicit for now. Add a deploy script.
68
+
69
+ Agent: [Write: scripts/deploy-staging.sh]
70
+
71
+ ```bash
72
+ #!/usr/bin/env bash
73
+ set -euo pipefail
74
+ HOST="${STAGING_HOST:-staging.example.com}"
75
+ ssh "deploy@${HOST}" '
76
+ cd /opt/droneflow
77
+ docker compose -f docker-compose.staging.yml pull
78
+ docker compose -f docker-compose.staging.yml up -d --remove-orphans
79
+ '
80
+ ```
81
+
82
+ User: Good. Any concerns about the current setup?
83
+
84
+ Agent: One: the `STAGING_DB_PASSWORD` must be set in the environment on the staging host before starting the stack. If it's missing, Docker Compose substitutes an empty string and Postgres connection will fail at startup rather than at config validation time. Consider adding a `.env.staging.example` file documenting required variables.
85
+
86
+ User: Good call. Add that.
87
+
88
+ Agent: [Write: .env.staging.example]
89
+
90
+ ```
91
+ STAGING_DB_PASSWORD=changeme
92
+ ```
93
+
94
+ Setup complete. Traefik routes api.staging.example.com with TLS; deploy is explicit via the deploy script.
@@ -0,0 +1,121 @@
1
+ User: I need to set up nginx as a reverse proxy for gridhost with TLS from Let's Encrypt. The app is running on port 8080 on the same machine. Domain is gridhost.example.com.
2
+
3
+ Agent: [Bash: nginx -v]
4
+
5
+ ```
6
+ nginx version: nginx/1.24.0
7
+ ```
8
+
9
+ [Bash: which certbot]
10
+
11
+ ```
12
+ /usr/bin/certbot
13
+ ```
14
+
15
+ Both are present. Let me check what's currently in /etc/nginx/sites-available/.
16
+
17
+ [Bash: ls /etc/nginx/sites-available/]
18
+
19
+ ```
20
+ default
21
+ ```
22
+
23
+ Only the default site. I'll create a new config for gridhost.example.com, get the certificate, then update the config to use it.
24
+
25
+ First, the initial HTTP-only config for the ACME challenge:
26
+
27
+ [Write: /etc/nginx/sites-available/gridhost]
28
+
29
+ ```nginx
30
+ server {
31
+ listen 80;
32
+ server_name gridhost.example.com;
33
+
34
+ location /.well-known/acme-challenge/ {
35
+ root /var/www/certbot;
36
+ }
37
+
38
+ location / {
39
+ return 301 https://$host$request_uri;
40
+ }
41
+ }
42
+ ```
43
+
44
+ [Bash: ln -s /etc/nginx/sites-available/gridhost /etc/nginx/sites-enabled/gridhost]
45
+ [Bash: nginx -t]
46
+
47
+ ```
48
+ nginx: configuration file /etc/nginx/nginx.conf test is successful
49
+ ```
50
+
51
+ [Bash: systemctl reload nginx]
52
+
53
+ [Bash: certbot certonly --webroot -w /var/www/certbot -d gridhost.example.com --non-interactive --agree-tos -m ops@example.com]
54
+
55
+ ```
56
+ Successfully received certificate.
57
+ Certificate is saved at: /etc/letsencrypt/live/gridhost.example.com/fullchain.pem
58
+ Key is saved at: /etc/letsencrypt/live/gridhost.example.com/privkey.pem
59
+ ```
60
+
61
+ Now updating the config to add TLS and the proxy:
62
+
63
+ [Edit: /etc/nginx/sites-available/gridhost]
64
+
65
+ ```nginx
66
+ server {
67
+ listen 80;
68
+ server_name gridhost.example.com;
69
+ return 301 https://$host$request_uri;
70
+ }
71
+
72
+ server {
73
+ listen 443 ssl;
74
+ server_name gridhost.example.com;
75
+
76
+ ssl_certificate /etc/letsencrypt/live/gridhost.example.com/fullchain.pem;
77
+ ssl_certificate_key /etc/letsencrypt/live/gridhost.example.com/privkey.pem;
78
+
79
+ ssl_protocols TLSv1.2 TLSv1.3;
80
+ ssl_prefer_server_ciphers on;
81
+
82
+ location / {
83
+ proxy_pass http://127.0.0.1:8080;
84
+ proxy_set_header Host $host;
85
+ proxy_set_header X-Real-IP $remote_addr;
86
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
87
+ proxy_set_header X-Forwarded-Proto $scheme;
88
+ }
89
+ }
90
+ ```
91
+
92
+ [Bash: nginx -t && systemctl reload nginx]
93
+
94
+ ```
95
+ nginx: configuration file /etc/nginx/nginx.conf test is successful
96
+ ```
97
+
98
+ User: Does Let's Encrypt auto-renew?
99
+
100
+ Agent: certbot installs a systemd timer or cron job during installation that runs `certbot renew` twice daily. Renewal happens automatically when the certificate is within 30 days of expiry. To confirm it's active:
101
+
102
+ [Bash: systemctl status certbot.timer]
103
+
104
+ ```
105
+ Active: active (waiting)
106
+ Trigger: Fri 2026-06-05 12:00:00 UTC
107
+ ```
108
+
109
+ Active. You don't need to do anything manual for renewal.
110
+
111
+ User: Add a cron fallback in case the timer is disabled.
112
+
113
+ Agent: [Bash: crontab -l]
114
+
115
+ No existing crontab. Adding:
116
+
117
+ [Bash: (crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --post-hook 'systemctl reload nginx'") | crontab -]
118
+
119
+ Runs at 3am daily; the post-hook reloads nginx after a successful renewal so the new certificate is served without a restart.
120
+
121
+ User: Perfect. TLS is live.
@@ -0,0 +1,74 @@
1
+ User: I need to add indexes to the sessions table in our Postgres database. The table has 8 million rows and I can't afford a full table lock. The migration is in alembic/versions/0042_session_indexes.py.
2
+
3
+ Agent: [Read: alembic/versions/0042_session_indexes.py]
4
+
5
+ ```python
6
+ def upgrade() -> None:
7
+ op.create_index("idx_sessions_created_at", "sessions", ["created_at"])
8
+ op.create_index("idx_sessions_workspace_id", "sessions", ["workspace_id"])
9
+ op.create_index("idx_sessions_workspace_created", "sessions", ["workspace_id", "created_at"])
10
+ ```
11
+
12
+ These use standard CREATE INDEX, which takes an AccessShareExclusive lock for the full duration on an 8M-row table. That will block reads and writes for minutes.
13
+
14
+ The fix is to use CREATE INDEX CONCURRENTLY, which only takes a brief lock at the start and end. Alembic supports this via `postgresql_concurrently=True`.
15
+
16
+ User: Switch them all to concurrent.
17
+
18
+ Agent: [Edit: alembic/versions/0042_session_indexes.py]
19
+
20
+ ```python
21
+ def upgrade() -> None:
22
+ with op.get_context().autocommit_block():
23
+ op.create_index(
24
+ "idx_sessions_created_at", "sessions", ["created_at"],
25
+ postgresql_concurrently=True,
26
+ )
27
+ op.create_index(
28
+ "idx_sessions_workspace_id", "sessions", ["workspace_id"],
29
+ postgresql_concurrently=True,
30
+ )
31
+ op.create_index(
32
+ "idx_sessions_workspace_created", "sessions",
33
+ ["workspace_id", "created_at"],
34
+ postgresql_concurrently=True,
35
+ )
36
+ ```
37
+
38
+ The `autocommit_block()` is required because CREATE INDEX CONCURRENTLY cannot run inside a transaction.
39
+
40
+ User: What about statement_timeout? Our default is 30 seconds which would kill a concurrent index build.
41
+
42
+ Agent: Good catch. Concurrent index builds on large tables can take several minutes. You should set `statement_timeout = 0` for the migration connection only, not globally. In psql:
43
+
44
+ ```sql
45
+ SET statement_timeout = 0;
46
+ ```
47
+
48
+ Or in the Alembic migration, before the index creates:
49
+
50
+ ```python
51
+ op.execute("SET statement_timeout = 0")
52
+ ```
53
+
54
+ I'll add that at the top of the upgrade function.
55
+
56
+ [Edit: alembic/versions/0042_session_indexes.py]
57
+
58
+ Added `op.execute("SET statement_timeout = 0")` as the first statement inside `autocommit_block()`.
59
+
60
+ User: Can we run the migration now, or should we wait for off-peak?
61
+
62
+ Agent: CONCURRENTLY is safe to run any time without locking, but the index build will consume IO and CPU proportional to table size. On an 8M-row table expect 2-5 minutes. Running during peak hours won't cause downtime, but it will increase query latency while the build is in progress. Off-peak is safer for user experience.
63
+
64
+ User: We'll run it tonight. Anything else?
65
+
66
+ Agent: One thing to verify before running: check there are no long-running transactions open, as CONCURRENTLY waits for them to complete before starting. Run:
67
+
68
+ ```sql
69
+ SELECT pid, now() - pg_stat_activity.query_start AS duration, query
70
+ FROM pg_stat_activity
71
+ WHERE state = 'active' AND now() - pg_stat_activity.query_start > interval '1 minute';
72
+ ```
73
+
74
+ If any show up, investigate before starting the migration.
@@ -0,0 +1,73 @@
1
+ User: A nightly report cron job on the flarepath server stopped running three days ago. No errors in the app logs. It was set up in the user crontab. Can you help debug?
2
+
3
+ Agent: [Bash: crontab -l]
4
+
5
+ ```
6
+ 0 2 * * * /opt/flarepath/scripts/nightly-report.sh >> /var/log/flarepath/nightly.log 2>&1
7
+ ```
8
+
9
+ The crontab entry looks correct syntactically. Let me check if cron itself is running and whether it logged anything.
10
+
11
+ [Bash: systemctl status cron]
12
+
13
+ ```
14
+ Active: active (running) since Mon 2026-06-15 09:12:44 UTC; 3 days ago
15
+ ```
16
+
17
+ Cron is running. Check its log:
18
+
19
+ [Bash: grep nightly-report /var/log/syslog | tail -20]
20
+
21
+ ```
22
+ Jun 15 02:00:01 flarepath CRON[84231]: (ubuntu) CMD (/opt/flarepath/scripts/nightly-report.sh >> /var/log/flarepath/nightly.log 2>&1)
23
+ Jun 15 02:00:01 flarepath CRON[84232]: (CRON) error (grandchild #84231 failed with exit status 126)
24
+ ```
25
+
26
+ Exit code 126 means "permission denied" or "not executable". The script exists but is not executable.
27
+
28
+ [Bash: ls -la /opt/flarepath/scripts/nightly-report.sh]
29
+
30
+ ```
31
+ -rw-r--r-- 1 ubuntu ubuntu 1842 Jun 14 18:32 /opt/flarepath/scripts/nightly-report.sh
32
+ ```
33
+
34
+ No execute bit. The script was edited on June 14 and the execute permission was lost, likely from a git checkout that replaced the file.
35
+
36
+ User: Fix it.
37
+
38
+ Agent: [Bash: chmod +x /opt/flarepath/scripts/nightly-report.sh]
39
+
40
+ [Bash: ls -la /opt/flarepath/scripts/nightly-report.sh]
41
+
42
+ ```
43
+ -rwxr-xr-x 1 ubuntu ubuntu 1842 Jun 14 18:32 /opt/flarepath/scripts/nightly-report.sh
44
+ ```
45
+
46
+ Fixed. Run a manual test now to confirm it executes cleanly:
47
+
48
+ [Bash: /opt/flarepath/scripts/nightly-report.sh]
49
+
50
+ ```
51
+ [2026-06-18 14:23:01] Generating nightly report...
52
+ [2026-06-18 14:23:04] Fetched 2341 records
53
+ [2026-06-18 14:23:05] Report written to /var/log/flarepath/reports/2026-06-18.json
54
+ [2026-06-18 14:23:05] Done.
55
+ ```
56
+
57
+ Runs cleanly.
58
+
59
+ User: Should we track this in git so it doesn't happen again?
60
+
61
+ Agent: Yes. The issue is that git does not preserve execute bits by default on some checkout configurations. Two options: set `core.fileMode = true` in the repo's git config (the default on Linux, but can be overridden), or add an explicit `chmod` call to the deploy script so the script is always made executable after checkout.
62
+
63
+ The deploy script approach is more robust since it works regardless of git config.
64
+
65
+ User: Let's add it to the deploy script.
66
+
67
+ Agent: [Read: scripts/deploy.sh]
68
+
69
+ [Edit: scripts/deploy.sh]
70
+
71
+ Added `chmod +x /opt/flarepath/scripts/nightly-report.sh` after the git pull step.
72
+
73
+ The cron job will run correctly from 2am tonight. The root cause was a git checkout stripping the execute bit; the deploy script now re-applies it on every deploy.