nlm-memory 0.20.0 → 0.21.0

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 (471) hide show
  1. package/README.md +19 -10
  2. package/dist/cli/digest.js +60 -1
  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 +872 -61
  9. package/dist/cli/nlm.js.map +1 -1
  10. package/dist/cli/scope-backfill.d.ts +34 -0
  11. package/dist/cli/scope-backfill.js +237 -0
  12. package/dist/cli/scope-backfill.js.map +1 -0
  13. package/dist/cli/scope-coverage.d.ts +44 -0
  14. package/dist/cli/scope-coverage.js +205 -0
  15. package/dist/cli/scope-coverage.js.map +1 -0
  16. package/dist/core/actions/overlay.d.ts +20 -6
  17. package/dist/core/actions/overlay.js +32 -18
  18. package/dist/core/actions/overlay.js.map +1 -1
  19. package/dist/core/adapters/claude-code.d.ts +14 -0
  20. package/dist/core/adapters/claude-code.js +36 -0
  21. package/dist/core/adapters/claude-code.js.map +1 -1
  22. package/dist/core/classifier/prompt.js +3 -3
  23. package/dist/core/dataset/build-dataset.js +1 -0
  24. package/dist/core/dataset/build-dataset.js.map +1 -1
  25. package/dist/core/digest/compose.d.ts +13 -3
  26. package/dist/core/digest/compose.js +23 -18
  27. package/dist/core/digest/compose.js.map +1 -1
  28. package/dist/core/digest/hook-injection.d.ts +43 -0
  29. package/dist/core/digest/hook-injection.js +79 -0
  30. package/dist/core/digest/hook-injection.js.map +1 -0
  31. package/dist/core/embedding/embed-backfill.d.ts +13 -2
  32. package/dist/core/embedding/embed-backfill.js +181 -43
  33. package/dist/core/embedding/embed-backfill.js.map +1 -1
  34. package/dist/core/embedding/embedding-config.d.ts +17 -0
  35. package/dist/core/embedding/embedding-config.js +12 -0
  36. package/dist/core/embedding/embedding-config.js.map +1 -0
  37. package/dist/core/embedding/pg-embed-backfill.d.ts +31 -0
  38. package/dist/core/embedding/pg-embed-backfill.js +264 -0
  39. package/dist/core/embedding/pg-embed-backfill.js.map +1 -0
  40. package/dist/core/entities/dedup-suggest.d.ts +25 -0
  41. package/dist/core/entities/dedup-suggest.js +107 -0
  42. package/dist/core/entities/dedup-suggest.js.map +1 -0
  43. package/dist/core/eval/classifier-fixture-eval.d.ts +29 -0
  44. package/dist/core/eval/classifier-fixture-eval.js +217 -0
  45. package/dist/core/eval/classifier-fixture-eval.js.map +1 -0
  46. package/dist/core/eval/extraction-scoring.d.ts +63 -0
  47. package/dist/core/eval/extraction-scoring.js +79 -0
  48. package/dist/core/eval/extraction-scoring.js.map +1 -0
  49. package/dist/core/eval/run-eval.d.ts +40 -0
  50. package/dist/core/eval/run-eval.js +34 -0
  51. package/dist/core/eval/run-eval.js.map +1 -0
  52. package/dist/core/exemplars/capture-from-session.d.ts +2 -0
  53. package/dist/core/exemplars/capture-from-session.js +7 -3
  54. package/dist/core/exemplars/capture-from-session.js.map +1 -1
  55. package/dist/core/exemplars/diff-parse.d.ts +23 -0
  56. package/dist/core/exemplars/diff-parse.js +73 -0
  57. package/dist/core/exemplars/diff-parse.js.map +1 -0
  58. package/dist/core/exemplars/embed-backfill.d.ts +36 -0
  59. package/dist/core/exemplars/embed-backfill.js +85 -0
  60. package/dist/core/exemplars/embed-backfill.js.map +1 -0
  61. package/dist/core/exemplars/embed-text.d.ts +15 -0
  62. package/dist/core/exemplars/embed-text.js +18 -0
  63. package/dist/core/exemplars/embed-text.js.map +1 -0
  64. package/dist/core/exemplars/extract-exemplar.d.ts +2 -0
  65. package/dist/core/exemplars/extract-exemplar.js +9 -66
  66. package/dist/core/exemplars/extract-exemplar.js.map +1 -1
  67. package/dist/core/exemplars/ingest-exemplar.d.ts +1 -0
  68. package/dist/core/exemplars/ingest-exemplar.js +1 -0
  69. package/dist/core/exemplars/ingest-exemplar.js.map +1 -1
  70. package/dist/core/exemplars/recall-code.js +4 -0
  71. package/dist/core/exemplars/recall-code.js.map +1 -1
  72. package/dist/core/exemplars/warm-embedder.d.ts +14 -0
  73. package/dist/core/exemplars/warm-embedder.js +18 -0
  74. package/dist/core/exemplars/warm-embedder.js.map +1 -0
  75. package/dist/core/health/corpus-state.d.ts +8 -0
  76. package/dist/core/health/corpus-state.js +11 -0
  77. package/dist/core/health/corpus-state.js.map +1 -0
  78. package/dist/core/health/embed-inflight.d.ts +9 -0
  79. package/dist/core/health/embed-inflight.js +30 -0
  80. package/dist/core/health/embed-inflight.js.map +1 -0
  81. package/dist/core/health/embedding-lane-state.d.ts +6 -0
  82. package/dist/core/health/embedding-lane-state.js +18 -0
  83. package/dist/core/health/embedding-lane-state.js.map +1 -0
  84. package/dist/core/health/warmup-state.d.ts +12 -0
  85. package/dist/core/health/warmup-state.js +12 -0
  86. package/dist/core/health/warmup-state.js.map +1 -0
  87. package/dist/core/hook/claude-settings.d.ts +1 -1
  88. package/dist/core/hook/claude-settings.js +0 -1
  89. package/dist/core/hook/claude-settings.js.map +1 -1
  90. package/dist/core/hook/gate.d.ts +13 -6
  91. package/dist/core/hook/gate.js +47 -6
  92. package/dist/core/hook/gate.js.map +1 -1
  93. package/dist/core/hook/hook-log.d.ts +5 -0
  94. package/dist/core/hook/hook-log.js.map +1 -1
  95. package/dist/core/hook/memo.d.ts +12 -0
  96. package/dist/core/hook/memo.js +41 -1
  97. package/dist/core/hook/memo.js.map +1 -1
  98. package/dist/core/hook/pointer-block.d.ts +1 -0
  99. package/dist/core/hook/pointer-block.js +23 -2
  100. package/dist/core/hook/pointer-block.js.map +1 -1
  101. package/dist/core/hook/query-extract.d.ts +2 -1
  102. package/dist/core/hook/query-extract.js +10 -1
  103. package/dist/core/hook/query-extract.js.map +1 -1
  104. package/dist/core/hook/resolve-conversation-by-query.d.ts +28 -0
  105. package/dist/core/hook/resolve-conversation-by-query.js +98 -0
  106. package/dist/core/hook/resolve-conversation-by-query.js.map +1 -0
  107. package/dist/core/hook/select.d.ts +8 -0
  108. package/dist/core/hook/select.js +9 -2
  109. package/dist/core/hook/select.js.map +1 -1
  110. package/dist/core/hook/transcript.js +16 -2
  111. package/dist/core/hook/transcript.js.map +1 -1
  112. package/dist/core/ingest/backfill-derivables.d.ts +92 -0
  113. package/dist/core/ingest/backfill-derivables.js +175 -0
  114. package/dist/core/ingest/backfill-derivables.js.map +1 -0
  115. package/dist/core/ingest/ingest-session.d.ts +5 -0
  116. package/dist/core/ingest/ingest-session.js +23 -0
  117. package/dist/core/ingest/ingest-session.js.map +1 -1
  118. package/dist/core/ingest/reclassify-oversized.d.ts +5 -0
  119. package/dist/core/ingest/reclassify-oversized.js +9 -0
  120. package/dist/core/ingest/reclassify-oversized.js.map +1 -1
  121. package/dist/core/ingest/reprocess.d.ts +96 -0
  122. package/dist/core/ingest/reprocess.js +280 -0
  123. package/dist/core/ingest/reprocess.js.map +1 -0
  124. package/dist/core/ingest/transcript-derivables.d.ts +27 -0
  125. package/dist/core/ingest/transcript-derivables.js +138 -0
  126. package/dist/core/ingest/transcript-derivables.js.map +1 -0
  127. package/dist/core/integrity/check-invariants.d.ts +6 -1
  128. package/dist/core/integrity/check-invariants.js +209 -3
  129. package/dist/core/integrity/check-invariants.js.map +1 -1
  130. package/dist/core/metrics/corpus-stats.d.ts +49 -0
  131. package/dist/core/metrics/corpus-stats.js +111 -0
  132. package/dist/core/metrics/corpus-stats.js.map +1 -0
  133. package/dist/core/metrics/re-derivation.d.ts +48 -0
  134. package/dist/core/metrics/re-derivation.js +87 -0
  135. package/dist/core/metrics/re-derivation.js.map +1 -0
  136. package/dist/core/outcome/coverage.d.ts +33 -0
  137. package/dist/core/outcome/coverage.js +46 -0
  138. package/dist/core/outcome/coverage.js.map +1 -0
  139. package/dist/core/outcome/rollup.d.ts +33 -0
  140. package/dist/core/outcome/rollup.js +113 -0
  141. package/dist/core/outcome/rollup.js.map +1 -0
  142. package/dist/core/recall/citation-log.d.ts +8 -0
  143. package/dist/core/recall/citation-log.js +17 -0
  144. package/dist/core/recall/citation-log.js.map +1 -1
  145. package/dist/core/recall/hook-recall-log.d.ts +2 -2
  146. package/dist/core/recall/hook-recall-log.js +2 -2
  147. package/dist/core/recall/query-intent.d.ts +7 -0
  148. package/dist/core/recall/query-intent.js +25 -0
  149. package/dist/core/recall/query-intent.js.map +1 -0
  150. package/dist/core/recall/query-log.d.ts +4 -0
  151. package/dist/core/recall/query-log.js +4 -0
  152. package/dist/core/recall/query-log.js.map +1 -1
  153. package/dist/core/recall/recall-service.d.ts +37 -1
  154. package/dist/core/recall/recall-service.js +106 -63
  155. package/dist/core/recall/recall-service.js.map +1 -1
  156. package/dist/core/recall/related-exemplars.d.ts +1 -0
  157. package/dist/core/recall/related-exemplars.js +4 -1
  158. package/dist/core/recall/related-exemplars.js.map +1 -1
  159. package/dist/core/recall/related-facts.js +2 -2
  160. package/dist/core/recall/related-facts.js.map +1 -1
  161. package/dist/core/recall/reranker.d.ts +2 -1
  162. package/dist/core/recall/reranker.js +3 -3
  163. package/dist/core/recall/reranker.js.map +1 -1
  164. package/dist/core/recall-facts/fact-query-log.d.ts +7 -0
  165. package/dist/core/recall-facts/fact-query-log.js +2 -0
  166. package/dist/core/recall-facts/fact-query-log.js.map +1 -1
  167. package/dist/core/recall-facts/fact-recall-service.d.ts +9 -1
  168. package/dist/core/recall-facts/fact-recall-service.js +22 -8
  169. package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
  170. package/dist/core/scheduler/scan-once.d.ts +6 -6
  171. package/dist/core/scheduler/scan-once.js +15 -49
  172. package/dist/core/scheduler/scan-once.js.map +1 -1
  173. package/dist/core/scheduler/scheduler.d.ts +10 -0
  174. package/dist/core/scheduler/scheduler.js +109 -13
  175. package/dist/core/scheduler/scheduler.js.map +1 -1
  176. package/dist/core/scope/alias-map.d.ts +10 -0
  177. package/dist/core/scope/alias-map.js +41 -0
  178. package/dist/core/scope/alias-map.js.map +1 -0
  179. package/dist/core/scope/derive-scope.d.ts +8 -0
  180. package/dist/core/scope/derive-scope.js +54 -0
  181. package/dist/core/scope/derive-scope.js.map +1 -0
  182. package/dist/core/scope/scope-clause.d.ts +16 -0
  183. package/dist/core/scope/scope-clause.js +21 -0
  184. package/dist/core/scope/scope-clause.js.map +1 -0
  185. package/dist/core/scope/stamp-flag.d.ts +1 -0
  186. package/dist/core/scope/stamp-flag.js +4 -0
  187. package/dist/core/scope/stamp-flag.js.map +1 -0
  188. package/dist/core/signals/code-signal.d.ts +50 -0
  189. package/dist/core/signals/code-signal.js +54 -0
  190. package/dist/core/signals/code-signal.js.map +1 -0
  191. package/dist/core/signals/ingest-signal.d.ts +1 -1
  192. package/dist/core/signals/ingest-signal.js +3 -1
  193. package/dist/core/signals/ingest-signal.js.map +1 -1
  194. package/dist/core/signals/stamp-scope.d.ts +18 -0
  195. package/dist/core/signals/stamp-scope.js +28 -0
  196. package/dist/core/signals/stamp-scope.js.map +1 -0
  197. package/dist/core/storage/db-restore.js +3 -1
  198. package/dist/core/storage/db-restore.js.map +1 -1
  199. package/dist/core/storage/fact-batch.d.ts +9 -0
  200. package/dist/core/storage/fact-batch.js +14 -0
  201. package/dist/core/storage/fact-batch.js.map +1 -0
  202. package/dist/core/storage/pg-code-exemplar-store.d.ts +1 -0
  203. package/dist/core/storage/pg-code-exemplar-store.js +13 -2
  204. package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
  205. package/dist/core/storage/pg-embedding-config.d.ts +29 -0
  206. package/dist/core/storage/pg-embedding-config.js +53 -0
  207. package/dist/core/storage/pg-embedding-config.js.map +1 -0
  208. package/dist/core/storage/pg-entity-store.d.ts +12 -0
  209. package/dist/core/storage/pg-entity-store.js +79 -0
  210. package/dist/core/storage/pg-entity-store.js.map +1 -0
  211. package/dist/core/storage/pg-fact-ingest.d.ts +15 -0
  212. package/dist/core/storage/pg-fact-ingest.js +34 -0
  213. package/dist/core/storage/pg-fact-ingest.js.map +1 -0
  214. package/dist/core/storage/pg-fact-store.d.ts +3 -0
  215. package/dist/core/storage/pg-fact-store.js +26 -30
  216. package/dist/core/storage/pg-fact-store.js.map +1 -1
  217. package/dist/core/storage/pg-migrate.d.ts +18 -0
  218. package/dist/core/storage/pg-migrate.js +73 -0
  219. package/dist/core/storage/pg-migrate.js.map +1 -0
  220. package/dist/core/storage/pg-session-store.d.ts +11 -2
  221. package/dist/core/storage/pg-session-store.js +224 -96
  222. package/dist/core/storage/pg-session-store.js.map +1 -1
  223. package/dist/core/storage/pg-signal-store.js +4 -3
  224. package/dist/core/storage/pg-signal-store.js.map +1 -1
  225. package/dist/core/storage/pg-storage.d.ts +9 -7
  226. package/dist/core/storage/pg-storage.js +20 -50
  227. package/dist/core/storage/pg-storage.js.map +1 -1
  228. package/dist/core/storage/pg-workstream-store.d.ts +25 -0
  229. package/dist/core/storage/pg-workstream-store.js +97 -0
  230. package/dist/core/storage/pg-workstream-store.js.map +1 -0
  231. package/dist/core/storage/sqlite-code-exemplar-store.d.ts +1 -0
  232. package/dist/core/storage/sqlite-code-exemplar-store.js +15 -2
  233. package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
  234. package/dist/core/storage/sqlite-embedding-config.d.ts +10 -0
  235. package/dist/core/storage/sqlite-embedding-config.js +27 -0
  236. package/dist/core/storage/sqlite-embedding-config.js.map +1 -0
  237. package/dist/core/storage/sqlite-entity-store.d.ts +13 -0
  238. package/dist/core/storage/sqlite-entity-store.js +96 -0
  239. package/dist/core/storage/sqlite-entity-store.js.map +1 -0
  240. package/dist/core/storage/sqlite-fact-store.d.ts +15 -7
  241. package/dist/core/storage/sqlite-fact-store.js +81 -27
  242. package/dist/core/storage/sqlite-fact-store.js.map +1 -1
  243. package/dist/core/storage/sqlite-outcome-store.d.ts +89 -0
  244. package/dist/core/storage/sqlite-outcome-store.js +158 -0
  245. package/dist/core/storage/sqlite-outcome-store.js.map +1 -0
  246. package/dist/core/storage/sqlite-session-store.d.ts +42 -7
  247. package/dist/core/storage/sqlite-session-store.js +208 -95
  248. package/dist/core/storage/sqlite-session-store.js.map +1 -1
  249. package/dist/core/storage/sqlite-signal-store.js +4 -2
  250. package/dist/core/storage/sqlite-signal-store.js.map +1 -1
  251. package/dist/core/storage/sqlite-storage.d.ts +8 -8
  252. package/dist/core/storage/sqlite-storage.js +21 -32
  253. package/dist/core/storage/sqlite-storage.js.map +1 -1
  254. package/dist/core/storage/sqlite-workstream-store.d.ts +25 -0
  255. package/dist/core/storage/sqlite-workstream-store.js +99 -0
  256. package/dist/core/storage/sqlite-workstream-store.js.map +1 -0
  257. package/dist/core/storage/supersedence-log.js +2 -0
  258. package/dist/core/storage/supersedence-log.js.map +1 -1
  259. package/dist/core/telemetry/probe-filter.d.ts +35 -0
  260. package/dist/core/telemetry/probe-filter.js +56 -0
  261. package/dist/core/telemetry/probe-filter.js.map +1 -0
  262. package/dist/core/update-check/check.d.ts +6 -0
  263. package/dist/core/update-check/check.js +35 -0
  264. package/dist/core/update-check/check.js.map +1 -1
  265. package/dist/core/work-digest/active-spans.d.ts +8 -0
  266. package/dist/core/work-digest/active-spans.js +29 -0
  267. package/dist/core/work-digest/active-spans.js.map +1 -0
  268. package/dist/core/work-digest/attribute.d.ts +15 -0
  269. package/dist/core/work-digest/attribute.js +76 -0
  270. package/dist/core/work-digest/attribute.js.map +1 -0
  271. package/dist/core/work-digest/build-work-digest.d.ts +12 -0
  272. package/dist/core/work-digest/build-work-digest.js +69 -0
  273. package/dist/core/work-digest/build-work-digest.js.map +1 -0
  274. package/dist/core/work-digest/compose-work-digest.d.ts +3 -0
  275. package/dist/core/work-digest/compose-work-digest.js +52 -0
  276. package/dist/core/work-digest/compose-work-digest.js.map +1 -0
  277. package/dist/core/work-digest/merge-active.d.ts +10 -0
  278. package/dist/core/work-digest/merge-active.js +22 -0
  279. package/dist/core/work-digest/merge-active.js.map +1 -0
  280. package/dist/core/work-digest/read-transcript-timestamps.d.ts +7 -0
  281. package/dist/core/work-digest/read-transcript-timestamps.js +40 -0
  282. package/dist/core/work-digest/read-transcript-timestamps.js.map +1 -0
  283. package/dist/core/work-digest/topics.d.ts +18 -0
  284. package/dist/core/work-digest/topics.js +26 -0
  285. package/dist/core/work-digest/topics.js.map +1 -0
  286. package/dist/core/work-digest/types.d.ts +45 -0
  287. package/dist/core/work-digest/types.js +2 -0
  288. package/dist/core/work-digest/types.js.map +1 -0
  289. package/dist/core/workstream/bind.d.ts +26 -0
  290. package/dist/core/workstream/bind.js +42 -0
  291. package/dist/core/workstream/bind.js.map +1 -0
  292. package/dist/core/workstream/compose-recall.d.ts +2 -0
  293. package/dist/core/workstream/compose-recall.js +39 -0
  294. package/dist/core/workstream/compose-recall.js.map +1 -0
  295. package/dist/core/workstream/merge-suggest.d.ts +19 -0
  296. package/dist/core/workstream/merge-suggest.js +61 -0
  297. package/dist/core/workstream/merge-suggest.js.map +1 -0
  298. package/dist/core/workstream/model.d.ts +21 -0
  299. package/dist/core/workstream/model.js +9 -0
  300. package/dist/core/workstream/model.js.map +1 -0
  301. package/dist/core/workstream/name-match.d.ts +8 -0
  302. package/dist/core/workstream/name-match.js +18 -0
  303. package/dist/core/workstream/name-match.js.map +1 -0
  304. package/dist/core/workstream/resolve.d.ts +10 -0
  305. package/dist/core/workstream/resolve.js +21 -0
  306. package/dist/core/workstream/resolve.js.map +1 -0
  307. package/dist/core/workstream/rollup.d.ts +12 -0
  308. package/dist/core/workstream/rollup.js +17 -0
  309. package/dist/core/workstream/rollup.js.map +1 -0
  310. package/dist/core/workstream/work-topics.d.ts +10 -0
  311. package/dist/core/workstream/work-topics.js +55 -0
  312. package/dist/core/workstream/work-topics.js.map +1 -0
  313. package/dist/hook/hook-helpers.d.ts +4 -0
  314. package/dist/hook/hook-helpers.js +36 -0
  315. package/dist/hook/hook-helpers.js.map +1 -0
  316. package/dist/hook/pre-compact-hook.d.ts +23 -0
  317. package/dist/hook/pre-compact-hook.js +67 -0
  318. package/dist/hook/pre-compact-hook.js.map +1 -0
  319. package/dist/hook/prompt-recall-hook.d.ts +38 -0
  320. package/dist/hook/prompt-recall-hook.js +124 -20
  321. package/dist/hook/prompt-recall-hook.js.map +1 -1
  322. package/dist/hook/recall-gate.d.ts +29 -0
  323. package/dist/hook/recall-gate.js +50 -0
  324. package/dist/hook/recall-gate.js.map +1 -0
  325. package/dist/hook/recall-over-http.d.ts +3 -2
  326. package/dist/hook/recall-over-http.js +26 -13
  327. package/dist/hook/recall-over-http.js.map +1 -1
  328. package/dist/hook/recent-context.d.ts +25 -0
  329. package/dist/hook/recent-context.js +104 -0
  330. package/dist/hook/recent-context.js.map +1 -0
  331. package/dist/hook/score-floor.d.ts +27 -0
  332. package/dist/hook/score-floor.js +42 -0
  333. package/dist/hook/score-floor.js.map +1 -0
  334. package/dist/hook/session-end-hook.js +8 -29
  335. package/dist/hook/session-end-hook.js.map +1 -1
  336. package/dist/hook/session-start-hook.js +26 -60
  337. package/dist/hook/session-start-hook.js.map +1 -1
  338. package/dist/hook/stop-hook.js +24 -60
  339. package/dist/hook/stop-hook.js.map +1 -1
  340. package/dist/http/app.d.ts +6 -0
  341. package/dist/http/app.js +52 -44
  342. package/dist/http/app.js.map +1 -1
  343. package/dist/install/setup.js +2 -0
  344. package/dist/install/setup.js.map +1 -1
  345. package/dist/llm/build-classifier.d.ts +22 -0
  346. package/dist/llm/build-classifier.js +42 -0
  347. package/dist/llm/build-classifier.js.map +1 -0
  348. package/dist/llm/build-embedder.d.ts +11 -0
  349. package/dist/llm/build-embedder.js +42 -0
  350. package/dist/llm/build-embedder.js.map +1 -0
  351. package/dist/llm/bundled-embedder-client.d.ts +37 -0
  352. package/dist/llm/bundled-embedder-client.js +90 -0
  353. package/dist/llm/bundled-embedder-client.js.map +1 -0
  354. package/dist/llm/classifier-box.d.ts +13 -5
  355. package/dist/llm/classifier-box.js +28 -6
  356. package/dist/llm/classifier-box.js.map +1 -1
  357. package/dist/llm/classifier-egress.d.ts +18 -0
  358. package/dist/llm/classifier-egress.js +59 -0
  359. package/dist/llm/classifier-egress.js.map +1 -0
  360. package/dist/llm/client-shared.d.ts +4 -0
  361. package/dist/llm/client-shared.js +39 -0
  362. package/dist/llm/client-shared.js.map +1 -0
  363. package/dist/llm/deepseek-client.d.ts +20 -0
  364. package/dist/llm/deepseek-client.js +59 -45
  365. package/dist/llm/deepseek-client.js.map +1 -1
  366. package/dist/llm/embedder-info.d.ts +15 -0
  367. package/dist/llm/embedder-info.js +27 -0
  368. package/dist/llm/embedder-info.js.map +1 -0
  369. package/dist/llm/model-quirks.d.ts +4 -0
  370. package/dist/llm/model-quirks.js +7 -0
  371. package/dist/llm/model-quirks.js.map +1 -0
  372. package/dist/llm/naming.d.ts +5 -0
  373. package/dist/llm/naming.js +20 -0
  374. package/dist/llm/naming.js.map +1 -0
  375. package/dist/llm/ollama-client.d.ts +7 -8
  376. package/dist/llm/ollama-client.js +59 -51
  377. package/dist/llm/ollama-client.js.map +1 -1
  378. package/dist/llm/ollama-code-embedder.d.ts +8 -3
  379. package/dist/llm/ollama-code-embedder.js +14 -24
  380. package/dist/llm/ollama-code-embedder.js.map +1 -1
  381. package/dist/llm/openai-code-embedder-client.d.ts +25 -0
  382. package/dist/llm/openai-code-embedder-client.js +52 -0
  383. package/dist/llm/openai-code-embedder-client.js.map +1 -0
  384. package/dist/llm/openai-embedder-client.d.ts +41 -0
  385. package/dist/llm/openai-embedder-client.js +83 -0
  386. package/dist/llm/openai-embedder-client.js.map +1 -0
  387. package/dist/mcp/server.d.ts +69 -2
  388. package/dist/mcp/server.js +404 -19
  389. package/dist/mcp/server.js.map +1 -1
  390. package/dist/ports/code-embedder.d.ts +6 -1
  391. package/dist/ports/code-exemplar-store.d.ts +5 -0
  392. package/dist/ports/entity-store.d.ts +35 -0
  393. package/dist/ports/entity-store.js +7 -0
  394. package/dist/ports/entity-store.js.map +1 -0
  395. package/dist/ports/fact-store.d.ts +0 -0
  396. package/dist/ports/fact-store.js.map +1 -1
  397. package/dist/ports/llm-client.d.ts +9 -1
  398. package/dist/ports/outcome.d.ts +85 -0
  399. package/dist/ports/outcome.js +11 -0
  400. package/dist/ports/outcome.js.map +1 -0
  401. package/dist/ports/session-store.d.ts +12 -6
  402. package/dist/ports/storage.d.ts +7 -30
  403. package/dist/ports/storage.js +3 -7
  404. package/dist/ports/storage.js.map +1 -1
  405. package/dist/ports/transcript-adapter.d.ts +2 -0
  406. package/dist/ports/workstream-store.d.ts +22 -0
  407. package/dist/ports/workstream-store.js +2 -0
  408. package/dist/ports/workstream-store.js.map +1 -0
  409. package/dist/shared/net.d.ts +3 -0
  410. package/dist/shared/net.js +4 -0
  411. package/dist/shared/net.js.map +1 -0
  412. package/dist/shared/types.d.ts +56 -2
  413. package/dist/shared/types.js +4 -1
  414. package/dist/shared/types.js.map +1 -1
  415. package/dist/ui/assets/{index-CMpQkbQy.js → index-CSaBtgXD.js} +1 -1
  416. package/dist/ui/index.html +1 -1
  417. package/fixtures/classifier-gold/README.md +94 -0
  418. package/fixtures/classifier-gold/reference.json +318 -0
  419. package/fixtures/classifier-gold/transcripts/01-bug-fix-null-pointer-api.txt +75 -0
  420. package/fixtures/classifier-gold/transcripts/02-bug-fix-race-condition-queue.txt +77 -0
  421. package/fixtures/classifier-gold/transcripts/03-bug-fix-jwt-clock-skew.txt +86 -0
  422. package/fixtures/classifier-gold/transcripts/04-feature-webhook-retry.txt +95 -0
  423. package/fixtures/classifier-gold/transcripts/05-feature-csv-export.txt +86 -0
  424. package/fixtures/classifier-gold/transcripts/06-feature-rate-limiting.txt +61 -0
  425. package/fixtures/classifier-gold/transcripts/07-refactor-db-abstraction.txt +72 -0
  426. package/fixtures/classifier-gold/transcripts/08-refactor-config-loading.txt +89 -0
  427. package/fixtures/classifier-gold/transcripts/09-ops-docker-staging.txt +94 -0
  428. package/fixtures/classifier-gold/transcripts/10-ops-nginx-ssl.txt +121 -0
  429. package/fixtures/classifier-gold/transcripts/11-ops-postgres-migration.txt +74 -0
  430. package/fixtures/classifier-gold/transcripts/12-ops-cron-debugging.txt +73 -0
  431. package/fixtures/classifier-gold/transcripts/13-research-vector-db-comparison.txt +59 -0
  432. package/fixtures/classifier-gold/transcripts/14-research-oauth-provider.txt +74 -0
  433. package/fixtures/classifier-gold/transcripts/15-writing-api-docs.txt +154 -0
  434. package/fixtures/classifier-gold/transcripts/16-meeting-sprint-planning.txt +69 -0
  435. package/fixtures/classifier-gold/transcripts/17-trivial-list-files.txt +14 -0
  436. package/fixtures/classifier-gold/transcripts/18-trivial-version-check.txt +21 -0
  437. package/fixtures/classifier-gold/transcripts/19-trivial-syntax-help.txt +29 -0
  438. package/fixtures/classifier-gold/transcripts/20-trivial-read-package-json.txt +41 -0
  439. package/migrations/000_initial_schema.sql +1 -1
  440. package/migrations/024_backfill_exemplar_repo_basename.sql +32 -0
  441. package/migrations/025_workstreams.sql +30 -0
  442. package/migrations/026_embedding_config.sql +12 -0
  443. package/migrations/027_drop_legacy_session_embeddings.sql +7 -0
  444. package/migrations/028_classifier_provenance.sql +5 -0
  445. package/migrations/029_project_scope.sql +21 -0
  446. package/migrations/032_session_derivables.sql +11 -0
  447. package/migrations/pg/001_initial.sql +34 -0
  448. package/migrations/pg/019_split_replaces.sql +16 -7
  449. package/migrations/pg/025_workstreams.sql +34 -0
  450. package/migrations/pg/026_embedding_config.sql +12 -0
  451. package/migrations/pg/028_classifier_provenance.sql +5 -0
  452. package/migrations/pg/029_entity_variants.sql +9 -0
  453. package/migrations/pg/030_project_scope.sql +23 -0
  454. package/migrations/pg/031_widen_session_edges_kind.sql +11 -0
  455. package/migrations/pg/032_session_derivables.sql +17 -0
  456. package/nlm/index.js +405 -39
  457. package/package.json +12 -2
  458. package/plugin/.codex-plugin/plugin.json +2 -2
  459. package/plugin/scripts/prompt-recall-hook.mjs +413 -43
  460. package/plugin/scripts/session-start-hook.mjs +295 -61
  461. package/plugin/scripts/stop-hook.mjs +89 -66
  462. package/plugin-hermes-agent/__init__.py +2 -1
  463. package/templates/agent-contract/README.md +24 -0
  464. package/templates/agent-contract/claude-code.md +40 -0
  465. package/templates/agent-contract/generic.md +38 -0
  466. package/dist/core/embedding/embed-normalize.d.ts +0 -28
  467. package/dist/core/embedding/embed-normalize.js +0 -98
  468. package/dist/core/embedding/embed-normalize.js.map +0 -1
  469. package/dist/core/storage/pg-tx-context.d.ts +0 -52
  470. package/dist/core/storage/pg-tx-context.js +0 -149
  471. package/dist/core/storage/pg-tx-context.js.map +0 -1
@@ -0,0 +1,95 @@
1
+ User: taskstream needs exponential backoff retry for failed webhook deliveries. Right now if a delivery fails it just drops the event. We need up to 5 retry attempts with exponential backoff, delays stored in Redis so retries survive restarts.
2
+
3
+ Agent: I'll design this before writing code. The approach: use a Redis sorted set where the score is the Unix timestamp of the next attempt. A background worker polls for jobs whose score is <= now, attempts delivery, and either marks the job done or reschedules it with a doubled delay. After 5 attempts the job is moved to a dead-letter key.
4
+
5
+ Does this fit the existing architecture? Let me check what's already in place.
6
+
7
+ [Read: src/delivery/deliver.ts]
8
+ [Read: src/worker/retry-worker.ts]
9
+
10
+ The deliver.ts module handles HTTP POST; retry-worker.ts exists but is a stub. Good starting point.
11
+
12
+ User: That architecture sounds right. Go ahead.
13
+
14
+ Agent: [Edit: src/delivery/retry-queue.ts] (new file)
15
+
16
+ ```typescript
17
+ import { createClient } from "redis";
18
+
19
+ const RETRY_KEY = "taskstream:retry";
20
+ const DEAD_KEY = "taskstream:dead";
21
+ const MAX_ATTEMPTS = 5;
22
+ const BASE_DELAY_MS = 5_000;
23
+
24
+ export async function enqueueRetry(
25
+ redis: ReturnType<typeof createClient>,
26
+ jobId: string,
27
+ attempt: number,
28
+ ): Promise<void> {
29
+ if (attempt >= MAX_ATTEMPTS) {
30
+ await redis.lPush(DEAD_KEY, jobId);
31
+ return;
32
+ }
33
+ const delayMs = BASE_DELAY_MS * Math.pow(2, attempt);
34
+ const score = Date.now() + delayMs;
35
+ await redis.zAdd(RETRY_KEY, { score, value: jobId });
36
+ }
37
+
38
+ export async function pollDueRetries(
39
+ redis: ReturnType<typeof createClient>,
40
+ ): Promise<string[]> {
41
+ const now = Date.now();
42
+ return redis.zRangeByScore(RETRY_KEY, "-inf", now);
43
+ }
44
+
45
+ export async function removeFromQueue(
46
+ redis: ReturnType<typeof createClient>,
47
+ jobId: string,
48
+ ): Promise<void> {
49
+ await redis.zRem(RETRY_KEY, jobId);
50
+ }
51
+ ```
52
+
53
+ [Edit: src/worker/retry-worker.ts]
54
+
55
+ Updated the stub to poll every 5 seconds, attempt delivery for each due job, and call enqueueRetry with incremented attempt count on failure or removeFromQueue on success.
56
+
57
+ User: What's the delay schedule for 5 attempts?
58
+
59
+ Agent: With BASE_DELAY_MS = 5000 and doubling per attempt:
60
+ - Attempt 0 -> 1: 5 seconds
61
+ - Attempt 1 -> 2: 10 seconds
62
+ - Attempt 2 -> 3: 20 seconds
63
+ - Attempt 3 -> 4: 40 seconds
64
+ - Attempt 4 -> 5: dead letter (no 6th attempt)
65
+
66
+ Total window before dead-letter: ~75 seconds. If you want longer, we can increase BASE_DELAY_MS or switch from 2x to a larger multiplier.
67
+
68
+ User: 75 seconds is fine for v1. What key format is used for job IDs?
69
+
70
+ Agent: [Read: src/jobs/job-id.ts]
71
+
72
+ Job IDs are UUIDs generated at event creation time, e.g. `evt_a3f7c290`. The retry queue stores these as ZADD values directly.
73
+
74
+ User: Add a unit test for the delay schedule.
75
+
76
+ Agent: [Write: tests/unit/delivery/retry-queue.test.ts]
77
+
78
+ Test covers: first enqueue sets correct score within 100ms tolerance, fifth attempt goes to dead letter, pollDueRetries returns jobs whose score is in the past, removeFromQueue removes only the target job.
79
+
80
+ User: Run it.
81
+
82
+ Agent: [Bash: npm test -- tests/unit/delivery/retry-queue.test.ts]
83
+
84
+ ```
85
+ PASS tests/unit/delivery/retry-queue.test.ts
86
+ enqueueRetry
87
+ ✓ schedules retry with exponential delay (12ms)
88
+ ✓ moves to dead letter after MAX_ATTEMPTS (8ms)
89
+ pollDueRetries
90
+ ✓ returns only due jobs (5ms)
91
+ removeFromQueue
92
+ ✓ removes target job only (4ms)
93
+ ```
94
+
95
+ All passing.
@@ -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.