@trac3r/oh-my-god 2.2.11

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 (638) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/INSTALL-VERIFICATION-INDEX.md +51 -0
  3. package/LICENSE +21 -0
  4. package/OMG-setup.sh +2549 -0
  5. package/QUICK-REFERENCE.md +58 -0
  6. package/README.md +207 -0
  7. package/agents/__init__.py +1 -0
  8. package/agents/__pycache__/model_roles.cpython-313.pyc +0 -0
  9. package/agents/_model_roles.yaml +26 -0
  10. package/agents/designer.md +67 -0
  11. package/agents/explore.md +60 -0
  12. package/agents/model_roles.py +196 -0
  13. package/agents/omg-api-builder.md +23 -0
  14. package/agents/omg-architect-mode.md +41 -0
  15. package/agents/omg-architect.md +13 -0
  16. package/agents/omg-backend-engineer.md +41 -0
  17. package/agents/omg-critic.md +16 -0
  18. package/agents/omg-database-engineer.md +41 -0
  19. package/agents/omg-escalation-router.md +17 -0
  20. package/agents/omg-executor.md +12 -0
  21. package/agents/omg-frontend-designer.md +41 -0
  22. package/agents/omg-implement-mode.md +49 -0
  23. package/agents/omg-infra-engineer.md +41 -0
  24. package/agents/omg-qa-tester.md +16 -0
  25. package/agents/omg-research-mode.md +41 -0
  26. package/agents/omg-security-auditor.md +41 -0
  27. package/agents/omg-testing-engineer.md +41 -0
  28. package/agents/plan.md +80 -0
  29. package/agents/quick_task.md +64 -0
  30. package/agents/reviewer.md +83 -0
  31. package/agents/task.md +71 -0
  32. package/bin/omg +41 -0
  33. package/commands/OMG:ai-commit.md +113 -0
  34. package/commands/OMG:api-twin.md +22 -0
  35. package/commands/OMG:arch.md +313 -0
  36. package/commands/OMG:browser.md +29 -0
  37. package/commands/OMG:ccg.md +22 -0
  38. package/commands/OMG:compat.md +57 -0
  39. package/commands/OMG:cost.md +181 -0
  40. package/commands/OMG:crazy.md +125 -0
  41. package/commands/OMG:create-agent.md +183 -0
  42. package/commands/OMG:deep-plan.md +18 -0
  43. package/commands/OMG:deps.md +248 -0
  44. package/commands/OMG:diagnose-plugins.md +33 -0
  45. package/commands/OMG:doctor.md +37 -0
  46. package/commands/OMG:domain-init.md +11 -0
  47. package/commands/OMG:escalate.md +52 -0
  48. package/commands/OMG:forge.md +103 -0
  49. package/commands/OMG:health-check.md +48 -0
  50. package/commands/OMG:init.md +134 -0
  51. package/commands/OMG:issue.md +56 -0
  52. package/commands/OMG:mode.md +44 -0
  53. package/commands/OMG:playwright.md +17 -0
  54. package/commands/OMG:preflight.md +26 -0
  55. package/commands/OMG:preset.md +49 -0
  56. package/commands/OMG:profile-review.md +58 -0
  57. package/commands/OMG:project-init.md +11 -0
  58. package/commands/OMG:ralph-start.md +43 -0
  59. package/commands/OMG:ralph-stop.md +23 -0
  60. package/commands/OMG:security-check.md +28 -0
  61. package/commands/OMG:session-branch.md +101 -0
  62. package/commands/OMG:session-fork.md +57 -0
  63. package/commands/OMG:session-merge.md +138 -0
  64. package/commands/OMG:setup.md +82 -0
  65. package/commands/OMG:ship.md +18 -0
  66. package/commands/OMG:stats.md +225 -0
  67. package/commands/OMG:teams.md +54 -0
  68. package/commands/OMG:theme.md +44 -0
  69. package/commands/OMG:validate.md +59 -0
  70. package/commands/__init__.py +1 -0
  71. package/docs/command-surface.md +55 -0
  72. package/docs/install/claude-code.md +53 -0
  73. package/docs/install/codex.md +45 -0
  74. package/docs/install/gemini.md +43 -0
  75. package/docs/install/github-action.md +81 -0
  76. package/docs/install/github-app-required-checks.md +107 -0
  77. package/docs/install/github-app.md +161 -0
  78. package/docs/install/kimi.md +43 -0
  79. package/docs/install/opencode.md +38 -0
  80. package/docs/proof.md +182 -0
  81. package/hooks/__init__.py +0 -0
  82. package/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  83. package/hooks/__pycache__/_agent_registry.cpython-313.pyc +0 -0
  84. package/hooks/__pycache__/_analytics.cpython-313.pyc +0 -0
  85. package/hooks/__pycache__/_budget.cpython-313.pyc +0 -0
  86. package/hooks/__pycache__/_common.cpython-313.pyc +0 -0
  87. package/hooks/__pycache__/_compression_optimizer.cpython-313.pyc +0 -0
  88. package/hooks/__pycache__/_cost_ledger.cpython-313.pyc +0 -0
  89. package/hooks/__pycache__/_learnings.cpython-313.pyc +0 -0
  90. package/hooks/__pycache__/_memory.cpython-313.pyc +0 -0
  91. package/hooks/__pycache__/_post_write.cpython-313.pyc +0 -0
  92. package/hooks/__pycache__/_protected_context.cpython-313.pyc +0 -0
  93. package/hooks/__pycache__/_token_counter.cpython-313.pyc +0 -0
  94. package/hooks/__pycache__/branch_manager.cpython-313.pyc +0 -0
  95. package/hooks/__pycache__/budget_governor.cpython-313.pyc +0 -0
  96. package/hooks/__pycache__/circuit-breaker.cpython-313.pyc +0 -0
  97. package/hooks/__pycache__/compression_feedback.cpython-313.pyc +0 -0
  98. package/hooks/__pycache__/config-guard.cpython-313.pyc +0 -0
  99. package/hooks/__pycache__/context_pressure.cpython-313.pyc +0 -0
  100. package/hooks/__pycache__/credential_store.cpython-313.pyc +0 -0
  101. package/hooks/__pycache__/fetch-rate-limits.cpython-313.pyc +0 -0
  102. package/hooks/__pycache__/firewall.cpython-313.pyc +0 -0
  103. package/hooks/__pycache__/hashline-formatter-bridge.cpython-313.pyc +0 -0
  104. package/hooks/__pycache__/hashline-injector.cpython-313.pyc +0 -0
  105. package/hooks/__pycache__/hashline-validator.cpython-313.pyc +0 -0
  106. package/hooks/__pycache__/idle-detector.cpython-313.pyc +0 -0
  107. package/hooks/__pycache__/instructions-loaded.cpython-313.pyc +0 -0
  108. package/hooks/__pycache__/intentgate-keyword-detector.cpython-313.pyc +0 -0
  109. package/hooks/__pycache__/magic-keyword-router.cpython-313.pyc +0 -0
  110. package/hooks/__pycache__/policy_engine.cpython-313.pyc +0 -0
  111. package/hooks/__pycache__/post-tool-failure.cpython-313.pyc +0 -0
  112. package/hooks/__pycache__/post-write.cpython-313.pyc +0 -0
  113. package/hooks/__pycache__/post_write.cpython-313.pyc +0 -0
  114. package/hooks/__pycache__/pre-compact.cpython-313.pyc +0 -0
  115. package/hooks/__pycache__/pre-tool-inject.cpython-313.pyc +0 -0
  116. package/hooks/__pycache__/prompt-enhancer.cpython-313.pyc +0 -0
  117. package/hooks/__pycache__/quality-runner.cpython-313.pyc +0 -0
  118. package/hooks/__pycache__/query.cpython-313.pyc +0 -0
  119. package/hooks/__pycache__/secret-guard.cpython-313.pyc +0 -0
  120. package/hooks/__pycache__/secret_audit.cpython-313.pyc +0 -0
  121. package/hooks/__pycache__/security_validators.cpython-313.pyc +0 -0
  122. package/hooks/__pycache__/session-end-capture.cpython-313.pyc +0 -0
  123. package/hooks/__pycache__/session-start.cpython-313.pyc +0 -0
  124. package/hooks/__pycache__/setup_wizard.cpython-313.pyc +0 -0
  125. package/hooks/__pycache__/shadow_manager.cpython-313.pyc +0 -0
  126. package/hooks/__pycache__/state_migration.cpython-313.pyc +0 -0
  127. package/hooks/__pycache__/stop-gate.cpython-313.pyc +0 -0
  128. package/hooks/__pycache__/stop_dispatcher.cpython-313.pyc +0 -0
  129. package/hooks/__pycache__/tdd-gate.cpython-313.pyc +0 -0
  130. package/hooks/__pycache__/terms-guard.cpython-313.pyc +0 -0
  131. package/hooks/__pycache__/test-validator.cpython-313.pyc +0 -0
  132. package/hooks/__pycache__/test_generator_hook.cpython-313.pyc +0 -0
  133. package/hooks/__pycache__/todo-state-tracker.cpython-313.pyc +0 -0
  134. package/hooks/__pycache__/tool-ledger.cpython-313.pyc +0 -0
  135. package/hooks/__pycache__/trust_review.cpython-313.pyc +0 -0
  136. package/hooks/__pycache__/user-prompt-submit.cpython-313.pyc +0 -0
  137. package/hooks/_agent_registry.py +481 -0
  138. package/hooks/_analytics.py +291 -0
  139. package/hooks/_budget.py +31 -0
  140. package/hooks/_common.py +761 -0
  141. package/hooks/_compression_optimizer.py +119 -0
  142. package/hooks/_cost_ledger.py +176 -0
  143. package/hooks/_learnings.py +126 -0
  144. package/hooks/_memory.py +103 -0
  145. package/hooks/_post_write.py +46 -0
  146. package/hooks/_protected_context.py +150 -0
  147. package/hooks/_token_counter.py +221 -0
  148. package/hooks/branch_manager.py +255 -0
  149. package/hooks/budget_governor.py +326 -0
  150. package/hooks/circuit-breaker.py +270 -0
  151. package/hooks/compression_feedback.py +254 -0
  152. package/hooks/config-guard.py +193 -0
  153. package/hooks/context_pressure.py +119 -0
  154. package/hooks/credential_store.py +970 -0
  155. package/hooks/fetch-rate-limits.py +212 -0
  156. package/hooks/firewall.py +323 -0
  157. package/hooks/hashline-formatter-bridge.py +224 -0
  158. package/hooks/hashline-injector.py +273 -0
  159. package/hooks/hashline-validator.py +216 -0
  160. package/hooks/idle-detector.py +97 -0
  161. package/hooks/instructions-loaded.py +26 -0
  162. package/hooks/intentgate-keyword-detector.py +200 -0
  163. package/hooks/magic-keyword-router.py +195 -0
  164. package/hooks/policy_engine.py +767 -0
  165. package/hooks/post-tool-failure.py +19 -0
  166. package/hooks/post-write.py +233 -0
  167. package/hooks/pre-compact.py +470 -0
  168. package/hooks/pre-tool-inject.py +98 -0
  169. package/hooks/prompt-enhancer.py +879 -0
  170. package/hooks/quality-runner.py +191 -0
  171. package/hooks/query.py +512 -0
  172. package/hooks/secret-guard.py +120 -0
  173. package/hooks/secret_audit.py +144 -0
  174. package/hooks/security_validators.py +93 -0
  175. package/hooks/session-end-capture.py +505 -0
  176. package/hooks/session-start.py +261 -0
  177. package/hooks/setup_wizard.py +1101 -0
  178. package/hooks/shadow_manager.py +476 -0
  179. package/hooks/state_migration.py +228 -0
  180. package/hooks/stop-gate.py +7 -0
  181. package/hooks/stop_dispatcher.py +1259 -0
  182. package/hooks/tdd-gate.py +10 -0
  183. package/hooks/terms-guard.py +98 -0
  184. package/hooks/test-validator.py +462 -0
  185. package/hooks/test_generator_hook.py +123 -0
  186. package/hooks/todo-state-tracker.py +114 -0
  187. package/hooks/tool-ledger.py +165 -0
  188. package/hooks/trust_review.py +662 -0
  189. package/hooks/user-prompt-submit.py +12 -0
  190. package/hud/omg-hud.mjs +1571 -0
  191. package/lab/__init__.py +1 -0
  192. package/lab/__pycache__/__init__.cpython-313.pyc +0 -0
  193. package/lab/__pycache__/axolotl_adapter.cpython-313.pyc +0 -0
  194. package/lab/__pycache__/forge_runner.cpython-313.pyc +0 -0
  195. package/lab/__pycache__/gazebo_adapter.cpython-313.pyc +0 -0
  196. package/lab/__pycache__/isaac_gym_adapter.cpython-313.pyc +0 -0
  197. package/lab/__pycache__/mock_isaac_env.cpython-313.pyc +0 -0
  198. package/lab/__pycache__/pipeline.cpython-313.pyc +0 -0
  199. package/lab/__pycache__/policies.cpython-313.pyc +0 -0
  200. package/lab/__pycache__/pybullet_adapter.cpython-313.pyc +0 -0
  201. package/lab/axolotl_adapter.py +531 -0
  202. package/lab/forge_runner.py +103 -0
  203. package/lab/gazebo_adapter.py +168 -0
  204. package/lab/isaac_gym_adapter.py +190 -0
  205. package/lab/mock_isaac_env.py +47 -0
  206. package/lab/pipeline.py +712 -0
  207. package/lab/policies.py +52 -0
  208. package/lab/pybullet_adapter.py +192 -0
  209. package/package.json +61 -0
  210. package/plugins/README.md +78 -0
  211. package/plugins/__init__.py +1 -0
  212. package/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
  213. package/plugins/advanced/commands/OMG-code-review.md +114 -0
  214. package/plugins/advanced/commands/OMG-deep-plan.md +266 -0
  215. package/plugins/advanced/commands/OMG-handoff.md +115 -0
  216. package/plugins/advanced/commands/OMG-learn.md +110 -0
  217. package/plugins/advanced/commands/OMG-maintainer.md +31 -0
  218. package/plugins/advanced/commands/OMG-ralph-start.md +43 -0
  219. package/plugins/advanced/commands/OMG-ralph-stop.md +23 -0
  220. package/plugins/advanced/commands/OMG-security-review.md +16 -0
  221. package/plugins/advanced/commands/OMG-sequential-thinking.md +20 -0
  222. package/plugins/advanced/commands/OMG-ship.md +46 -0
  223. package/plugins/advanced/commands/OMG:code-review.md +114 -0
  224. package/plugins/advanced/commands/OMG:deep-plan.md +266 -0
  225. package/plugins/advanced/commands/OMG:handoff.md +115 -0
  226. package/plugins/advanced/commands/OMG:learn.md +110 -0
  227. package/plugins/advanced/commands/OMG:maintainer.md +31 -0
  228. package/plugins/advanced/commands/OMG:ralph-start.md +43 -0
  229. package/plugins/advanced/commands/OMG:ralph-stop.md +23 -0
  230. package/plugins/advanced/commands/OMG:security-review.md +16 -0
  231. package/plugins/advanced/commands/OMG:sequential-thinking.md +20 -0
  232. package/plugins/advanced/commands/OMG:ship.md +46 -0
  233. package/plugins/advanced/plugin.json +104 -0
  234. package/plugins/core/plugin.json +204 -0
  235. package/plugins/dephealth/__init__.py +0 -0
  236. package/plugins/dephealth/__pycache__/__init__.cpython-313.pyc +0 -0
  237. package/plugins/dephealth/__pycache__/cve_scanner.cpython-313.pyc +0 -0
  238. package/plugins/dephealth/__pycache__/license_checker.cpython-313.pyc +0 -0
  239. package/plugins/dephealth/__pycache__/manifest_detector.cpython-313.pyc +0 -0
  240. package/plugins/dephealth/__pycache__/vuln_analyzer.cpython-313.pyc +0 -0
  241. package/plugins/dephealth/cve_scanner.py +279 -0
  242. package/plugins/dephealth/license_checker.py +135 -0
  243. package/plugins/dephealth/manifest_detector.py +423 -0
  244. package/plugins/dephealth/vuln_analyzer.py +176 -0
  245. package/plugins/testgen/__init__.py +0 -0
  246. package/plugins/testgen/__pycache__/__init__.cpython-313.pyc +0 -0
  247. package/plugins/testgen/__pycache__/codamosa_engine.cpython-313.pyc +0 -0
  248. package/plugins/testgen/__pycache__/edge_case_synthesizer.cpython-313.pyc +0 -0
  249. package/plugins/testgen/__pycache__/framework_detector.cpython-313.pyc +0 -0
  250. package/plugins/testgen/__pycache__/skeleton_generator.cpython-313.pyc +0 -0
  251. package/plugins/testgen/codamosa_engine.py +402 -0
  252. package/plugins/testgen/edge_case_synthesizer.py +184 -0
  253. package/plugins/testgen/framework_detector.py +271 -0
  254. package/plugins/testgen/skeleton_generator.py +219 -0
  255. package/plugins/viz/__init__.py +0 -0
  256. package/plugins/viz/__pycache__/__init__.cpython-313.pyc +0 -0
  257. package/plugins/viz/__pycache__/ast_parser.cpython-313.pyc +0 -0
  258. package/plugins/viz/__pycache__/diagram_generator.cpython-313.pyc +0 -0
  259. package/plugins/viz/__pycache__/graph_builder.cpython-313.pyc +0 -0
  260. package/plugins/viz/__pycache__/native_parsers.cpython-313.pyc +0 -0
  261. package/plugins/viz/__pycache__/regex_parser.cpython-313.pyc +0 -0
  262. package/plugins/viz/ast_parser.py +139 -0
  263. package/plugins/viz/diagram_generator.py +192 -0
  264. package/plugins/viz/graph_builder.py +444 -0
  265. package/plugins/viz/native_parsers.py +259 -0
  266. package/plugins/viz/regex_parser.py +112 -0
  267. package/pyproject.toml +143 -0
  268. package/registry/__init__.py +1 -0
  269. package/registry/__pycache__/__init__.cpython-313.pyc +0 -0
  270. package/registry/__pycache__/approval_artifact.cpython-313.pyc +0 -0
  271. package/registry/__pycache__/verify_artifact.cpython-313.pyc +0 -0
  272. package/registry/approval_artifact.py +236 -0
  273. package/registry/bundles/algorithms.yaml +45 -0
  274. package/registry/bundles/api-twin.yaml +48 -0
  275. package/registry/bundles/ast-pack.yaml +80 -0
  276. package/registry/bundles/claim-judge.yaml +49 -0
  277. package/registry/bundles/control-plane.yaml +192 -0
  278. package/registry/bundles/data-lineage.yaml +47 -0
  279. package/registry/bundles/delta-classifier.yaml +47 -0
  280. package/registry/bundles/eval-gate.yaml +47 -0
  281. package/registry/bundles/hash-edit.yaml +73 -0
  282. package/registry/bundles/health.yaml +45 -0
  283. package/registry/bundles/hook-governor.yaml +101 -0
  284. package/registry/bundles/incident-replay.yaml +47 -0
  285. package/registry/bundles/lsp-pack.yaml +80 -0
  286. package/registry/bundles/mcp-fabric.yaml +53 -0
  287. package/registry/bundles/plan-council.yaml +56 -0
  288. package/registry/bundles/preflight.yaml +48 -0
  289. package/registry/bundles/proof-gate.yaml +49 -0
  290. package/registry/bundles/remote-supervisor.yaml +49 -0
  291. package/registry/bundles/robotics.yaml +45 -0
  292. package/registry/bundles/secure-worktree-pipeline.yaml +69 -0
  293. package/registry/bundles/security-check.yaml +50 -0
  294. package/registry/bundles/terminal-lane.yaml +61 -0
  295. package/registry/bundles/test-intent-lock.yaml +49 -0
  296. package/registry/bundles/tracebank.yaml +47 -0
  297. package/registry/bundles/vision.yaml +45 -0
  298. package/registry/omg-capability.schema.json +378 -0
  299. package/registry/policy-packs/airgapped.lock.json +11 -0
  300. package/registry/policy-packs/airgapped.signature.json +10 -0
  301. package/registry/policy-packs/airgapped.yaml +16 -0
  302. package/registry/policy-packs/fintech.lock.json +11 -0
  303. package/registry/policy-packs/fintech.signature.json +10 -0
  304. package/registry/policy-packs/fintech.yaml +15 -0
  305. package/registry/policy-packs/locked-prod.lock.json +11 -0
  306. package/registry/policy-packs/locked-prod.signature.json +10 -0
  307. package/registry/policy-packs/locked-prod.yaml +18 -0
  308. package/registry/trusted_signers.json +44 -0
  309. package/registry/verify_artifact.py +493 -0
  310. package/runtime/__init__.py +36 -0
  311. package/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
  312. package/runtime/__pycache__/adoption.cpython-313.pyc +0 -0
  313. package/runtime/__pycache__/agent_selector.cpython-313.pyc +0 -0
  314. package/runtime/__pycache__/api_twin.cpython-313.pyc +0 -0
  315. package/runtime/__pycache__/architecture_signal.cpython-313.pyc +0 -0
  316. package/runtime/__pycache__/artifact_parsers.cpython-313.pyc +0 -0
  317. package/runtime/__pycache__/asset_loader.cpython-313.pyc +0 -0
  318. package/runtime/__pycache__/background_verification.cpython-313.pyc +0 -0
  319. package/runtime/__pycache__/budget_envelopes.cpython-313.pyc +0 -0
  320. package/runtime/__pycache__/business_workflow.cpython-313.pyc +0 -0
  321. package/runtime/__pycache__/canonical_surface.cpython-313.pyc +0 -0
  322. package/runtime/__pycache__/canonical_taxonomy.cpython-313.pyc +0 -0
  323. package/runtime/__pycache__/claim_judge.cpython-313.pyc +0 -0
  324. package/runtime/__pycache__/cli_provider.cpython-313.pyc +0 -0
  325. package/runtime/__pycache__/compat.cpython-313.pyc +0 -0
  326. package/runtime/__pycache__/complexity_scorer.cpython-313.pyc +0 -0
  327. package/runtime/__pycache__/compliance_governor.cpython-313.pyc +0 -0
  328. package/runtime/__pycache__/config_transaction.cpython-313.pyc +0 -0
  329. package/runtime/__pycache__/context_compiler.cpython-313.pyc +0 -0
  330. package/runtime/__pycache__/context_engine.cpython-313.pyc +0 -0
  331. package/runtime/__pycache__/context_limits.cpython-313.pyc +0 -0
  332. package/runtime/__pycache__/contract_compiler.cpython-313.pyc +0 -0
  333. package/runtime/__pycache__/custom_agent_loader.cpython-313.pyc +0 -0
  334. package/runtime/__pycache__/data_lineage.cpython-313.pyc +0 -0
  335. package/runtime/__pycache__/defense_state.cpython-313.pyc +0 -0
  336. package/runtime/__pycache__/delta_classifier.cpython-313.pyc +0 -0
  337. package/runtime/__pycache__/dispatcher.cpython-313.pyc +0 -0
  338. package/runtime/__pycache__/doc_generator.cpython-313.pyc +0 -0
  339. package/runtime/__pycache__/domain_packs.cpython-313.pyc +0 -0
  340. package/runtime/__pycache__/ecosystem.cpython-313.pyc +0 -0
  341. package/runtime/__pycache__/equalizer.cpython-313.pyc +0 -0
  342. package/runtime/__pycache__/eval_gate.cpython-313.pyc +0 -0
  343. package/runtime/__pycache__/evidence_narrator.cpython-313.pyc +0 -0
  344. package/runtime/__pycache__/evidence_query.cpython-313.pyc +0 -0
  345. package/runtime/__pycache__/evidence_registry.cpython-313.pyc +0 -0
  346. package/runtime/__pycache__/evidence_requirements.cpython-313.pyc +0 -0
  347. package/runtime/__pycache__/exec_kernel.cpython-313.pyc +0 -0
  348. package/runtime/__pycache__/explainer_formatter.cpython-313.pyc +0 -0
  349. package/runtime/__pycache__/feature_registry.cpython-313.pyc +0 -0
  350. package/runtime/__pycache__/forge_agents.cpython-313.pyc +0 -0
  351. package/runtime/__pycache__/forge_contracts.cpython-313.pyc +0 -0
  352. package/runtime/__pycache__/forge_domains.cpython-313.pyc +0 -0
  353. package/runtime/__pycache__/forge_run_id.cpython-313.pyc +0 -0
  354. package/runtime/__pycache__/github_integration.cpython-313.pyc +0 -0
  355. package/runtime/__pycache__/github_review_bot.cpython-313.pyc +0 -0
  356. package/runtime/__pycache__/github_review_contract.cpython-313.pyc +0 -0
  357. package/runtime/__pycache__/github_review_formatter.cpython-313.pyc +0 -0
  358. package/runtime/__pycache__/guide_assert.cpython-313.pyc +0 -0
  359. package/runtime/__pycache__/hook_governor.cpython-313.pyc +0 -0
  360. package/runtime/__pycache__/host_parity.cpython-313.pyc +0 -0
  361. package/runtime/__pycache__/incident_replay.cpython-313.pyc +0 -0
  362. package/runtime/__pycache__/install_planner.cpython-313.pyc +0 -0
  363. package/runtime/__pycache__/interaction_journal.cpython-313.pyc +0 -0
  364. package/runtime/__pycache__/issue_surface.cpython-313.pyc +0 -0
  365. package/runtime/__pycache__/legacy_compat.cpython-313.pyc +0 -0
  366. package/runtime/__pycache__/mcp_config_writers.cpython-313.pyc +0 -0
  367. package/runtime/__pycache__/mcp_lifecycle.cpython-313.pyc +0 -0
  368. package/runtime/__pycache__/mcp_memory_server.cpython-313.pyc +0 -0
  369. package/runtime/__pycache__/memory_store.cpython-313.pyc +0 -0
  370. package/runtime/__pycache__/merge_writer.cpython-313.pyc +0 -0
  371. package/runtime/__pycache__/music_omr_testbed.cpython-313.pyc +0 -0
  372. package/runtime/__pycache__/mutation_gate.cpython-313.pyc +0 -0
  373. package/runtime/__pycache__/omc_compat.cpython-313.pyc +0 -0
  374. package/runtime/__pycache__/omg_browser_cli.cpython-313.pyc +0 -0
  375. package/runtime/__pycache__/omg_mcp_server.cpython-313.pyc +0 -0
  376. package/runtime/__pycache__/opus_plan.cpython-313.pyc +0 -0
  377. package/runtime/__pycache__/playwright_adapter.cpython-313.pyc +0 -0
  378. package/runtime/__pycache__/playwright_pack.cpython-313.pyc +0 -0
  379. package/runtime/__pycache__/plugin_diagnostics.cpython-313.pyc +0 -0
  380. package/runtime/__pycache__/plugin_interop.cpython-313.pyc +0 -0
  381. package/runtime/__pycache__/policy_pack_loader.cpython-313.pyc +0 -0
  382. package/runtime/__pycache__/preflight.cpython-313.pyc +0 -0
  383. package/runtime/__pycache__/profile_io.cpython-313.pyc +0 -0
  384. package/runtime/__pycache__/prompt_compiler.cpython-313.pyc +0 -0
  385. package/runtime/__pycache__/proof_chain.cpython-313.pyc +0 -0
  386. package/runtime/__pycache__/proof_gate.cpython-313.pyc +0 -0
  387. package/runtime/__pycache__/provider_parity_eval.cpython-313.pyc +0 -0
  388. package/runtime/__pycache__/release_artifact_audit.cpython-313.pyc +0 -0
  389. package/runtime/__pycache__/release_run_coordinator.cpython-313.pyc +0 -0
  390. package/runtime/__pycache__/release_surface_compiler.cpython-313.pyc +0 -0
  391. package/runtime/__pycache__/release_surface_registry.cpython-313.pyc +0 -0
  392. package/runtime/__pycache__/release_surfaces.cpython-313.pyc +0 -0
  393. package/runtime/__pycache__/remote_supervisor.cpython-313.pyc +0 -0
  394. package/runtime/__pycache__/repro_pack.cpython-313.pyc +0 -0
  395. package/runtime/__pycache__/rollback_manifest.cpython-313.pyc +0 -0
  396. package/runtime/__pycache__/router_critics.cpython-313.pyc +0 -0
  397. package/runtime/__pycache__/router_executor.cpython-313.pyc +0 -0
  398. package/runtime/__pycache__/router_selector.cpython-313.pyc +0 -0
  399. package/runtime/__pycache__/runtime_contracts.cpython-313.pyc +0 -0
  400. package/runtime/__pycache__/runtime_profile.cpython-313.pyc +0 -0
  401. package/runtime/__pycache__/security_check.cpython-313.pyc +0 -0
  402. package/runtime/__pycache__/session_health.cpython-313.pyc +0 -0
  403. package/runtime/__pycache__/skill_evolution.cpython-313.pyc +0 -0
  404. package/runtime/__pycache__/skill_registry.cpython-313.pyc +0 -0
  405. package/runtime/__pycache__/subagent_dispatcher.cpython-313.pyc +0 -0
  406. package/runtime/__pycache__/subscription_tiers.cpython-313.pyc +0 -0
  407. package/runtime/__pycache__/team_router.cpython-313.pyc +0 -0
  408. package/runtime/__pycache__/test_intent_lock.cpython-313-pytest-9.0.2.pyc +0 -0
  409. package/runtime/__pycache__/test_intent_lock.cpython-313.pyc +0 -0
  410. package/runtime/__pycache__/tmux_session_manager.cpython-313.pyc +0 -0
  411. package/runtime/__pycache__/tool_fabric.cpython-313.pyc +0 -0
  412. package/runtime/__pycache__/tool_plan_gate.cpython-313.pyc +0 -0
  413. package/runtime/__pycache__/tool_relevance.cpython-313.pyc +0 -0
  414. package/runtime/__pycache__/tracebank.cpython-313.pyc +0 -0
  415. package/runtime/__pycache__/untrusted_content.cpython-313.pyc +0 -0
  416. package/runtime/__pycache__/validate.cpython-313.pyc +0 -0
  417. package/runtime/__pycache__/verdict_schema.cpython-313.pyc +0 -0
  418. package/runtime/__pycache__/verification_controller.cpython-313.pyc +0 -0
  419. package/runtime/__pycache__/verification_loop.cpython-313.pyc +0 -0
  420. package/runtime/__pycache__/vision_artifacts.cpython-313.pyc +0 -0
  421. package/runtime/__pycache__/vision_cache.cpython-313.pyc +0 -0
  422. package/runtime/__pycache__/vision_jobs.cpython-313.pyc +0 -0
  423. package/runtime/__pycache__/worker_watchdog.cpython-313.pyc +0 -0
  424. package/runtime/adapters/__init__.py +13 -0
  425. package/runtime/adapters/__pycache__/__init__.cpython-313.pyc +0 -0
  426. package/runtime/adapters/__pycache__/claude.cpython-313.pyc +0 -0
  427. package/runtime/adapters/__pycache__/gpt.cpython-313.pyc +0 -0
  428. package/runtime/adapters/__pycache__/local.cpython-313.pyc +0 -0
  429. package/runtime/adapters/claude.py +63 -0
  430. package/runtime/adapters/gpt.py +56 -0
  431. package/runtime/adapters/local.py +56 -0
  432. package/runtime/adoption.py +280 -0
  433. package/runtime/api_twin.py +450 -0
  434. package/runtime/architecture_signal.py +226 -0
  435. package/runtime/artifact_parsers.py +161 -0
  436. package/runtime/asset_loader.py +62 -0
  437. package/runtime/background_verification.py +178 -0
  438. package/runtime/budget_envelopes.py +398 -0
  439. package/runtime/business_workflow.py +234 -0
  440. package/runtime/canonical_surface.py +53 -0
  441. package/runtime/canonical_taxonomy.py +27 -0
  442. package/runtime/claim_judge.py +648 -0
  443. package/runtime/cli_provider.py +105 -0
  444. package/runtime/compat.py +2222 -0
  445. package/runtime/complexity_scorer.py +148 -0
  446. package/runtime/compliance_governor.py +505 -0
  447. package/runtime/config_transaction.py +304 -0
  448. package/runtime/context_compiler.py +131 -0
  449. package/runtime/context_engine.py +708 -0
  450. package/runtime/context_limits.py +363 -0
  451. package/runtime/contract_compiler.py +3664 -0
  452. package/runtime/custom_agent_loader.py +366 -0
  453. package/runtime/data_lineage.py +244 -0
  454. package/runtime/defense_state.py +261 -0
  455. package/runtime/delta_classifier.py +231 -0
  456. package/runtime/dispatcher.py +47 -0
  457. package/runtime/doc_generator.py +319 -0
  458. package/runtime/domain_packs.py +75 -0
  459. package/runtime/ecosystem.py +371 -0
  460. package/runtime/equalizer.py +268 -0
  461. package/runtime/eval_gate.py +96 -0
  462. package/runtime/evidence_narrator.py +147 -0
  463. package/runtime/evidence_query.py +303 -0
  464. package/runtime/evidence_registry.py +16 -0
  465. package/runtime/evidence_requirements.py +157 -0
  466. package/runtime/exec_kernel.py +267 -0
  467. package/runtime/explainer_formatter.py +82 -0
  468. package/runtime/feature_registry.py +109 -0
  469. package/runtime/forge_agents.py +915 -0
  470. package/runtime/forge_contracts.py +519 -0
  471. package/runtime/forge_domains.py +68 -0
  472. package/runtime/forge_run_id.py +86 -0
  473. package/runtime/guide_assert.py +135 -0
  474. package/runtime/hook_governor.py +156 -0
  475. package/runtime/host_parity.py +373 -0
  476. package/runtime/incident_replay.py +310 -0
  477. package/runtime/install_planner.py +617 -0
  478. package/runtime/interaction_journal.py +566 -0
  479. package/runtime/issue_surface.py +472 -0
  480. package/runtime/legacy_compat.py +7 -0
  481. package/runtime/mcp_config_writers.py +360 -0
  482. package/runtime/mcp_lifecycle.py +175 -0
  483. package/runtime/mcp_memory_server.py +220 -0
  484. package/runtime/memory_parsers/__init__.py +0 -0
  485. package/runtime/memory_parsers/__pycache__/__init__.cpython-313.pyc +0 -0
  486. package/runtime/memory_parsers/__pycache__/chatgpt_parser.cpython-313.pyc +0 -0
  487. package/runtime/memory_parsers/__pycache__/claude_import.cpython-313.pyc +0 -0
  488. package/runtime/memory_parsers/__pycache__/export.cpython-313.pyc +0 -0
  489. package/runtime/memory_parsers/__pycache__/gemini_import.cpython-313.pyc +0 -0
  490. package/runtime/memory_parsers/__pycache__/kimi_import.cpython-313.pyc +0 -0
  491. package/runtime/memory_parsers/chatgpt_parser.py +257 -0
  492. package/runtime/memory_parsers/claude_import.py +107 -0
  493. package/runtime/memory_parsers/export.py +97 -0
  494. package/runtime/memory_parsers/gemini_import.py +91 -0
  495. package/runtime/memory_parsers/kimi_import.py +91 -0
  496. package/runtime/memory_store.py +1182 -0
  497. package/runtime/merge_writer.py +445 -0
  498. package/runtime/music_omr_testbed.py +336 -0
  499. package/runtime/mutation_gate.py +320 -0
  500. package/runtime/omc_compat.py +7 -0
  501. package/runtime/omg_browser_cli.py +95 -0
  502. package/runtime/omg_compat_contract_snapshot.json +936 -0
  503. package/runtime/omg_contract_snapshot.json +936 -0
  504. package/runtime/omg_mcp_server.py +306 -0
  505. package/runtime/playwright_adapter.py +39 -0
  506. package/runtime/playwright_pack.py +253 -0
  507. package/runtime/plugin_diagnostics.py +308 -0
  508. package/runtime/plugin_interop.py +1060 -0
  509. package/runtime/policy_pack_loader.py +147 -0
  510. package/runtime/preflight.py +135 -0
  511. package/runtime/profile_io.py +328 -0
  512. package/runtime/proof_chain.py +472 -0
  513. package/runtime/proof_gate.py +442 -0
  514. package/runtime/provider_parity_eval.py +109 -0
  515. package/runtime/providers/__init__.py +0 -0
  516. package/runtime/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  517. package/runtime/providers/__pycache__/codex_provider.cpython-313.pyc +0 -0
  518. package/runtime/providers/__pycache__/gemini_provider.cpython-313.pyc +0 -0
  519. package/runtime/providers/__pycache__/kimi_provider.cpython-313.pyc +0 -0
  520. package/runtime/providers/__pycache__/opencode_provider.cpython-313.pyc +0 -0
  521. package/runtime/providers/codex_provider.py +129 -0
  522. package/runtime/providers/gemini_provider.py +143 -0
  523. package/runtime/providers/kimi_provider.py +167 -0
  524. package/runtime/providers/opencode_provider.py +99 -0
  525. package/runtime/release_artifact_audit.py +556 -0
  526. package/runtime/release_run_coordinator.py +574 -0
  527. package/runtime/release_surface_compiler.py +643 -0
  528. package/runtime/release_surface_registry.py +283 -0
  529. package/runtime/release_surfaces.py +320 -0
  530. package/runtime/remote_supervisor.py +79 -0
  531. package/runtime/repro_pack.py +398 -0
  532. package/runtime/rollback_manifest.py +143 -0
  533. package/runtime/router_critics.py +229 -0
  534. package/runtime/router_executor.py +142 -0
  535. package/runtime/router_selector.py +99 -0
  536. package/runtime/runtime_contracts.py +292 -0
  537. package/runtime/runtime_profile.py +133 -0
  538. package/runtime/security_check.py +1094 -0
  539. package/runtime/session_health.py +546 -0
  540. package/runtime/skill_evolution.py +221 -0
  541. package/runtime/skill_registry.py +53 -0
  542. package/runtime/subagent_dispatcher.py +604 -0
  543. package/runtime/subscription_tiers.py +258 -0
  544. package/runtime/team_router.py +1399 -0
  545. package/runtime/test_intent_lock.py +543 -0
  546. package/runtime/tmux_session_manager.py +172 -0
  547. package/runtime/tool_fabric.py +570 -0
  548. package/runtime/tool_plan_gate.py +460 -0
  549. package/runtime/tracebank.py +125 -0
  550. package/runtime/untrusted_content.py +360 -0
  551. package/runtime/validate.py +293 -0
  552. package/runtime/verdict_schema.py +198 -0
  553. package/runtime/verification_controller.py +235 -0
  554. package/runtime/verification_loop.py +73 -0
  555. package/runtime/vision_artifacts.py +31 -0
  556. package/runtime/vision_cache.py +38 -0
  557. package/runtime/vision_jobs.py +92 -0
  558. package/runtime/worker_watchdog.py +526 -0
  559. package/scripts/__pycache__/audit-published-artifact.cpython-313.pyc +0 -0
  560. package/scripts/__pycache__/check-doc-parity.cpython-313.pyc +0 -0
  561. package/scripts/__pycache__/check-omg-standalone-clean.cpython-313.pyc +0 -0
  562. package/scripts/__pycache__/github_review_helpers.cpython-313.pyc +0 -0
  563. package/scripts/__pycache__/omg.cpython-313.pyc +0 -0
  564. package/scripts/__pycache__/prepare-release-proof-fixtures.cpython-313.pyc +0 -0
  565. package/scripts/__pycache__/sync-release-identity.cpython-313.pyc +0 -0
  566. package/scripts/__pycache__/validate-release-identity.cpython-313.pyc +0 -0
  567. package/scripts/audit-published-artifact.py +59 -0
  568. package/scripts/check-omg-compat-contract-snapshot.py +137 -0
  569. package/scripts/check-omg-contract-snapshot.py +12 -0
  570. package/scripts/check-omg-public-ready.py +273 -0
  571. package/scripts/check-omg-standalone-clean.py +133 -0
  572. package/scripts/emit_host_parity.py +72 -0
  573. package/scripts/legacy_to_omg_migrate.py +29 -0
  574. package/scripts/migrate-legacy.py +464 -0
  575. package/scripts/omc_to_omg_migrate.py +12 -0
  576. package/scripts/omg.py +2962 -0
  577. package/scripts/pre-release-check.sh +38 -0
  578. package/scripts/prepare-release-proof-fixtures.py +602 -0
  579. package/scripts/print-canonical-version.py +80 -0
  580. package/scripts/settings-merge.py +289 -0
  581. package/scripts/sync-release-identity.py +481 -0
  582. package/scripts/validate-release-identity.py +632 -0
  583. package/scripts/verify-no-omc.sh +5 -0
  584. package/scripts/verify-standalone.sh +35 -0
  585. package/settings.json +751 -0
  586. package/tools/__init__.py +2 -0
  587. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  588. package/tools/__pycache__/browser_consent.cpython-313.pyc +0 -0
  589. package/tools/__pycache__/browser_stealth.cpython-313.pyc +0 -0
  590. package/tools/__pycache__/browser_tool.cpython-313.pyc +0 -0
  591. package/tools/__pycache__/changelog_generator.cpython-313.pyc +0 -0
  592. package/tools/__pycache__/commit_splitter.cpython-313.pyc +0 -0
  593. package/tools/__pycache__/config_discovery.cpython-313.pyc +0 -0
  594. package/tools/__pycache__/config_merger.cpython-313.pyc +0 -0
  595. package/tools/__pycache__/dashboard_generator.cpython-313.pyc +0 -0
  596. package/tools/__pycache__/git_inspector.cpython-313.pyc +0 -0
  597. package/tools/__pycache__/lsp_client.cpython-313.pyc +0 -0
  598. package/tools/__pycache__/lsp_operations.cpython-313.pyc +0 -0
  599. package/tools/__pycache__/pr_generator.cpython-313.pyc +0 -0
  600. package/tools/__pycache__/python_repl.cpython-313.pyc +0 -0
  601. package/tools/__pycache__/python_sandbox.cpython-313.pyc +0 -0
  602. package/tools/__pycache__/session_snapshot.cpython-313.pyc +0 -0
  603. package/tools/__pycache__/ssh_manager.cpython-313.pyc +0 -0
  604. package/tools/__pycache__/theme_engine.cpython-313.pyc +0 -0
  605. package/tools/__pycache__/theme_selector.cpython-313.pyc +0 -0
  606. package/tools/__pycache__/web_search.cpython-313.pyc +0 -0
  607. package/tools/browser_consent.py +289 -0
  608. package/tools/browser_stealth.py +481 -0
  609. package/tools/browser_tool.py +448 -0
  610. package/tools/changelog_generator.py +347 -0
  611. package/tools/commit_splitter.py +749 -0
  612. package/tools/config_discovery.py +151 -0
  613. package/tools/config_merger.py +449 -0
  614. package/tools/dashboard_generator.py +300 -0
  615. package/tools/git_inspector.py +298 -0
  616. package/tools/lsp_client.py +275 -0
  617. package/tools/lsp_discovery.py +231 -0
  618. package/tools/lsp_operations.py +392 -0
  619. package/tools/pr_generator.py +404 -0
  620. package/tools/python_repl.py +712 -0
  621. package/tools/python_sandbox.py +768 -0
  622. package/tools/search_providers/__init__.py +77 -0
  623. package/tools/search_providers/__pycache__/__init__.cpython-313.pyc +0 -0
  624. package/tools/search_providers/__pycache__/brave.cpython-313.pyc +0 -0
  625. package/tools/search_providers/__pycache__/exa.cpython-313.pyc +0 -0
  626. package/tools/search_providers/__pycache__/jina.cpython-313.pyc +0 -0
  627. package/tools/search_providers/__pycache__/perplexity.cpython-313.pyc +0 -0
  628. package/tools/search_providers/__pycache__/synthetic.cpython-313.pyc +0 -0
  629. package/tools/search_providers/brave.py +115 -0
  630. package/tools/search_providers/exa.py +116 -0
  631. package/tools/search_providers/jina.py +104 -0
  632. package/tools/search_providers/perplexity.py +139 -0
  633. package/tools/search_providers/synthetic.py +74 -0
  634. package/tools/session_snapshot.py +851 -0
  635. package/tools/ssh_manager.py +912 -0
  636. package/tools/theme_engine.py +296 -0
  637. package/tools/theme_selector.py +137 -0
  638. package/tools/web_search.py +675 -0
@@ -0,0 +1,1182 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ import sqlite3
7
+ import uuid
8
+ import base64
9
+ import hashlib
10
+ import socket
11
+ from datetime import datetime, timedelta, timezone
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from runtime.profile_io import classify_preference_section, is_destructive_preference
16
+
17
+ try:
18
+ from cryptography.fernet import Fernet, InvalidToken
19
+
20
+ _has_fernet = True
21
+ except ModuleNotFoundError:
22
+ Fernet = None # type: ignore[assignment]
23
+ InvalidToken = Exception # type: ignore[assignment]
24
+ _has_fernet = False
25
+
26
+
27
+ class MemoryStoreFullError(Exception):
28
+ pass
29
+
30
+
31
+ _MAX_ITEMS = 10_000
32
+ _PREFERENCE_SIGNAL_LIMIT = 12
33
+ _PREFERENCE_VALUE_MAX = 160
34
+ _PREFERENCE_ALLOWED_FIELDS = (
35
+ "preferences.architecture_requests",
36
+ "preferences.constraints.",
37
+ "user_vector.tags",
38
+ )
39
+ _INLINE_METADATA_MAX = 512
40
+ _DEFAULT_NAMESPACE = "default"
41
+ _DEFAULT_MEMORY_HOST = "127.0.0.1"
42
+ _ENCRYPTED_PREFIX = "enc:v1:"
43
+ _JSON_ENVELOPE_FORMAT = "omg.memory.json.v1"
44
+ _PII_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
45
+ (re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b"), "[REDACTED:EMAIL]"),
46
+ (re.compile(r"\b(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)\d{3}[-.\s]?\d{4}\b"), "[REDACTED:PHONE]"),
47
+ (re.compile(r"\b\d{3}-\d{2}-\d{4}\b"), "[REDACTED:SSN]"),
48
+ )
49
+ _Item = dict[str, Any] # pyright: ignore[reportExplicitAny]
50
+
51
+
52
+ class MemoryStore:
53
+ def __init__(self, store_path: str | None = None) -> None:
54
+ if store_path is None:
55
+ store_path = str(Path.home() / ".omg" / "shared-memory" / "store.sqlite3")
56
+ self._memory_host = (os.environ.get("OMG_MEMORY_HOST", _DEFAULT_MEMORY_HOST).strip() or _DEFAULT_MEMORY_HOST)
57
+ self._conn: sqlite3.Connection | None = None
58
+ self._fts_enabled = False
59
+ self._items: list[_Item] = []
60
+ self._store_path = ""
61
+ self._backend = "json"
62
+
63
+ self.store_path = store_path
64
+
65
+ @property
66
+ def store_path(self) -> str:
67
+ return self._store_path
68
+
69
+ @store_path.setter
70
+ def store_path(self, store_path: str) -> None:
71
+ normalized_path = str(store_path)
72
+ if normalized_path == self._store_path:
73
+ return
74
+
75
+ self.close()
76
+ self._store_path = normalized_path
77
+ self._backend = "json" if Path(normalized_path).suffix.lower() == ".json" else "sqlite"
78
+ self._items = []
79
+ self._fts_enabled = False
80
+
81
+ if self._backend == "json":
82
+ self._items = self._load_json_items()
83
+ else:
84
+ self._init_sqlite()
85
+
86
+ def close(self) -> None:
87
+ if self._conn is not None:
88
+ self._conn.close()
89
+ self._conn = None
90
+
91
+ def __del__(self) -> None:
92
+ try:
93
+ self.close()
94
+ except Exception:
95
+ return
96
+
97
+ def add(
98
+ self,
99
+ key: str,
100
+ content: str,
101
+ source_cli: str,
102
+ tags: list[str] | None = None,
103
+ *,
104
+ run_id: str = "",
105
+ profile_id: str = "",
106
+ namespace: str = _DEFAULT_NAMESPACE,
107
+ retention_days: int | None = None,
108
+ ) -> _Item:
109
+ if self.count() >= _MAX_ITEMS:
110
+ raise MemoryStoreFullError(
111
+ f"Memory store is full ({_MAX_ITEMS} items). "
112
+ "Delete items before adding new ones."
113
+ )
114
+
115
+ now = _utc_now_iso()
116
+ canonical_namespace = _qualify_namespace(namespace)
117
+ canonical_retention_days = _normalize_retention_days(retention_days)
118
+ expires_at = _compute_expires_at(now, canonical_retention_days)
119
+ redacted_content = _redact_pii(content)
120
+ item: _Item = {
121
+ "id": str(uuid.uuid4()),
122
+ "key": key,
123
+ "content": redacted_content,
124
+ "source_cli": source_cli,
125
+ "tags": tags if tags is not None else [],
126
+ "run_id": run_id,
127
+ "profile_id": profile_id,
128
+ "namespace": canonical_namespace,
129
+ "retention_days": canonical_retention_days,
130
+ "expires_at": expires_at,
131
+ "created_at": now,
132
+ "updated_at": now,
133
+ "quarantined": False,
134
+ }
135
+ if self._backend == "json":
136
+ self._items.append(item)
137
+ self._save_json_items()
138
+ return item
139
+
140
+ conn = self._sqlite_conn()
141
+ conn.execute(
142
+ """
143
+ INSERT INTO memories(
144
+ id, key, content, source_cli, tags_json, run_id, profile_id,
145
+ namespace, retention_days, expires_at, created_at, updated_at
146
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
147
+ """,
148
+ (
149
+ item["id"],
150
+ item["key"],
151
+ self._encrypt_text(item["content"], purpose="sqlite-content"),
152
+ item["source_cli"],
153
+ json.dumps(item["tags"], ensure_ascii=True),
154
+ run_id,
155
+ profile_id,
156
+ canonical_namespace,
157
+ canonical_retention_days,
158
+ expires_at,
159
+ now,
160
+ now,
161
+ ),
162
+ )
163
+ self._upsert_fts(item)
164
+ conn.commit()
165
+ return item
166
+
167
+ def get(self, item_id: str) -> _Item | None:
168
+ if self._backend == "json":
169
+ for item in self._items:
170
+ if item["id"] == item_id:
171
+ return item
172
+ return None
173
+
174
+ row = self._sqlite_conn().execute(
175
+ "SELECT * FROM memories WHERE id = ?",
176
+ (item_id,),
177
+ ).fetchone()
178
+ return self._row_to_item(row) if row is not None else None
179
+
180
+ def update(
181
+ self,
182
+ item_id: str,
183
+ content: str | None = None,
184
+ tags: list[str] | None = None,
185
+ ) -> _Item | None:
186
+ if self._backend == "json":
187
+ item = self.get(item_id)
188
+ if item is None:
189
+ return None
190
+
191
+ if content is not None:
192
+ item["content"] = content
193
+ if tags is not None:
194
+ item["tags"] = tags
195
+ item["updated_at"] = _utc_now_iso()
196
+ self._save_json_items()
197
+ return item
198
+
199
+ item = self.get(item_id)
200
+ if item is None:
201
+ return None
202
+ new_content = content if content is not None else str(item.get("content", ""))
203
+ new_tags = tags if tags is not None else item.get("tags", [])
204
+ updated_at = _utc_now_iso()
205
+ self._sqlite_conn().execute(
206
+ "UPDATE memories SET content = ?, tags_json = ?, updated_at = ? WHERE id = ?",
207
+ (self._encrypt_text(new_content, purpose="sqlite-content"), json.dumps(new_tags, ensure_ascii=True), updated_at, item_id),
208
+ )
209
+ updated = {
210
+ **item,
211
+ "content": new_content,
212
+ "tags": new_tags,
213
+ "updated_at": updated_at,
214
+ }
215
+ self._upsert_fts(updated)
216
+ self._sqlite_conn().commit()
217
+ return updated
218
+
219
+ def delete(self, item_id: str) -> bool:
220
+ if self._backend == "json":
221
+ for idx, item in enumerate(self._items):
222
+ if item["id"] == item_id:
223
+ del self._items[idx]
224
+ self._save_json_items()
225
+ return True
226
+ return False
227
+
228
+ cur = self._sqlite_conn().execute("DELETE FROM memories WHERE id = ?", (item_id,))
229
+ self._delete_fts(item_id)
230
+ self._sqlite_conn().commit()
231
+ return cur.rowcount > 0
232
+
233
+ def search(
234
+ self,
235
+ query: str,
236
+ source_cli: str | None = None,
237
+ *,
238
+ namespace: str | None = None,
239
+ include_quarantined: bool = False,
240
+ ) -> list[_Item]:
241
+ if self._backend == "json":
242
+ q = query.lower()
243
+ results: list[_Item] = []
244
+ canonical_namespace = _qualify_namespace(namespace) if namespace is not None else None
245
+ for item in self._items:
246
+ normalized_item = _normalize_item(item)
247
+ if _is_expired(normalized_item):
248
+ continue
249
+ if source_cli is not None and item["source_cli"] != source_cli:
250
+ continue
251
+ if canonical_namespace is not None and normalized_item.get("namespace") != canonical_namespace:
252
+ continue
253
+ if not include_quarantined and bool(normalized_item.get("quarantined", False)):
254
+ continue
255
+ key_str = str(item.get("key", "")).lower()
256
+ content_str = str(item.get("content", "")).lower()
257
+ tags_raw = item.get("tags", [])
258
+ tags_str = " ".join(str(t).lower() for t in tags_raw) if isinstance(tags_raw, list) else ""
259
+ if q in key_str or q in content_str or q in tags_str:
260
+ results.append(normalized_item)
261
+ return results
262
+
263
+ rows = self.list_all(
264
+ source_cli=source_cli,
265
+ namespace=namespace,
266
+ include_quarantined=include_quarantined,
267
+ )
268
+ q = query.lower()
269
+ matches: list[_Item] = []
270
+ for item in rows:
271
+ key_str = str(item.get("key", "")).lower()
272
+ content_str = str(item.get("content", "")).lower()
273
+ tags_raw = item.get("tags", [])
274
+ tags_str = " ".join(str(t).lower() for t in tags_raw) if isinstance(tags_raw, list) else ""
275
+ if q in key_str or q in content_str or q in tags_str:
276
+ matches.append(item)
277
+ return matches
278
+
279
+ def list_all(
280
+ self,
281
+ source_cli: str | None = None,
282
+ *,
283
+ run_id: str | None = None,
284
+ profile_id: str | None = None,
285
+ namespace: str | None = None,
286
+ include_quarantined: bool = False,
287
+ ) -> list[_Item]:
288
+ if self._backend == "json":
289
+ out = [_normalize_item(item) for item in self._items if not _is_expired(item)]
290
+ if source_cli is not None:
291
+ out = [i for i in out if i.get("source_cli") == source_cli]
292
+ if run_id is not None:
293
+ out = [i for i in out if str(i.get("run_id", "")) == run_id]
294
+ if profile_id is not None:
295
+ out = [i for i in out if str(i.get("profile_id", "")) == profile_id]
296
+ if namespace is not None:
297
+ canonical_namespace = _qualify_namespace(namespace)
298
+ out = [i for i in out if str(i.get("namespace", "")) == canonical_namespace]
299
+ if not include_quarantined:
300
+ out = [i for i in out if not bool(i.get("quarantined", False))]
301
+ return out
302
+
303
+ where: list[str] = []
304
+ params: list[Any] = []
305
+ if source_cli is not None:
306
+ where.append("source_cli = ?")
307
+ params.append(source_cli)
308
+ if run_id is not None:
309
+ where.append("run_id = ?")
310
+ params.append(run_id)
311
+ if profile_id is not None:
312
+ where.append("profile_id = ?")
313
+ params.append(profile_id)
314
+ if namespace is not None:
315
+ where.append("namespace = ?")
316
+ params.append(_qualify_namespace(namespace))
317
+ if not include_quarantined:
318
+ where.append("quarantined = 0")
319
+ where.append("(expires_at IS NULL OR expires_at = '' OR expires_at > ?)")
320
+ params.append(_utc_now_iso())
321
+
322
+ sql = "SELECT * FROM memories"
323
+ if where:
324
+ sql += " WHERE " + " AND ".join(where)
325
+ sql += " ORDER BY updated_at DESC"
326
+ rows = self._sqlite_conn().execute(sql, tuple(params)).fetchall()
327
+ return [self._row_to_item(row) for row in rows]
328
+
329
+ def export_all(self, *, include_quarantined: bool = False) -> list[_Item]:
330
+ return self.list_all(include_quarantined=include_quarantined)
331
+
332
+ def import_items(self, items: list[_Item], *, quarantined: bool = False) -> int:
333
+ if self._backend == "json":
334
+ existing_ids = {str(i["id"]) for i in self._items}
335
+ added = 0
336
+ for item in items:
337
+ item_id = str(item.get("id", ""))
338
+ if item_id and item_id not in existing_ids:
339
+ normalized = _normalize_item(item)
340
+ normalized["quarantined"] = bool(quarantined)
341
+ self._items.append(normalized)
342
+ existing_ids.add(item_id)
343
+ added += 1
344
+ if added:
345
+ self._save_json_items()
346
+ return added
347
+
348
+ conn = self._sqlite_conn()
349
+ added = 0
350
+ for item in items:
351
+ item_id = str(item.get("id", "")).strip()
352
+ if not item_id:
353
+ continue
354
+ existing = conn.execute("SELECT 1 FROM memories WHERE id = ?", (item_id,)).fetchone()
355
+ if existing is not None:
356
+ continue
357
+ key = str(item.get("key", ""))
358
+ content = str(item.get("content", ""))
359
+ source_cli = str(item.get("source_cli", ""))
360
+ tags = item.get("tags") if isinstance(item.get("tags"), list) else []
361
+ created_at = str(item.get("created_at", "")) or _utc_now_iso()
362
+ updated_at = str(item.get("updated_at", "")) or created_at
363
+ namespace = _qualify_namespace(item.get("namespace", _DEFAULT_NAMESPACE))
364
+ retention_days = _normalize_retention_days(item.get("retention_days"))
365
+ expires_at = str(item.get("expires_at", "")).strip() or _compute_expires_at(created_at, retention_days)
366
+ redacted_content = _redact_pii(content)
367
+ run_id = str(item.get("run_id", ""))
368
+ profile_id = str(item.get("profile_id", ""))
369
+ quarantined_flag = bool(quarantined)
370
+ conn.execute(
371
+ """
372
+ INSERT INTO memories(
373
+ id, key, content, source_cli, tags_json, run_id, profile_id,
374
+ namespace, retention_days, expires_at, created_at, updated_at, quarantined
375
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
376
+ """,
377
+ (
378
+ item_id,
379
+ key,
380
+ self._encrypt_text(redacted_content, purpose="sqlite-content"),
381
+ source_cli,
382
+ json.dumps(tags, ensure_ascii=True),
383
+ run_id,
384
+ profile_id,
385
+ namespace,
386
+ retention_days,
387
+ expires_at,
388
+ created_at,
389
+ updated_at,
390
+ 1 if quarantined_flag else 0,
391
+ ),
392
+ )
393
+ self._upsert_fts(
394
+ {
395
+ "id": item_id,
396
+ "key": key,
397
+ "content": redacted_content,
398
+ "tags": tags,
399
+ }
400
+ )
401
+ added += 1
402
+ if added:
403
+ conn.commit()
404
+ return added
405
+
406
+ def count(self) -> int:
407
+ if self._backend == "json":
408
+ return len(self._items)
409
+ row = self._sqlite_conn().execute("SELECT COUNT(*) AS n FROM memories").fetchone()
410
+ if row is None:
411
+ return 0
412
+ return int(row["n"])
413
+
414
+ def clear(self) -> int:
415
+ n = self.count()
416
+ if self._backend == "json":
417
+ self._items.clear()
418
+ self._save_json_items()
419
+ return n
420
+
421
+ conn = self._sqlite_conn()
422
+ conn.execute("DELETE FROM memories")
423
+ conn.execute("DELETE FROM artifacts")
424
+ conn.execute("DELETE FROM lineage_edges")
425
+ if self._fts_enabled:
426
+ conn.execute("DELETE FROM memories_fts")
427
+ conn.commit()
428
+ return n
429
+
430
+ def promote_item(self, item_id: str) -> bool:
431
+ if self._backend == "json":
432
+ item = self.get(item_id)
433
+ if item is None:
434
+ return False
435
+ if bool(item.get("quarantined", False)):
436
+ item["quarantined"] = False
437
+ item["updated_at"] = _utc_now_iso()
438
+ self._save_json_items()
439
+ return True
440
+
441
+ cur = self._sqlite_conn().execute(
442
+ "UPDATE memories SET quarantined = 0, updated_at = ? WHERE id = ?",
443
+ (_utc_now_iso(), item_id),
444
+ )
445
+ self._sqlite_conn().commit()
446
+ return cur.rowcount > 0
447
+
448
+ def query_scoped(
449
+ self,
450
+ query: str,
451
+ *,
452
+ run_id: str,
453
+ profile_id: str,
454
+ limit: int = 20,
455
+ ) -> list[_Item]:
456
+ bounded_limit = max(1, min(int(limit), 200))
457
+ if self._backend == "json":
458
+ rows = self.search(query)
459
+ scoped = [
460
+ row
461
+ for row in rows
462
+ if str(row.get("run_id", "")) == run_id
463
+ and str(row.get("profile_id", "")) == profile_id
464
+ ]
465
+ return scoped[:bounded_limit]
466
+
467
+ q = f"%{query.lower()}%"
468
+ rows = self._sqlite_conn().execute(
469
+ """
470
+ SELECT * FROM memories
471
+ WHERE run_id = ?
472
+ AND profile_id = ?
473
+ AND quarantined = 0
474
+ AND (lower(key) LIKE ? OR lower(content) LIKE ? OR lower(tags_json) LIKE ?)
475
+ ORDER BY updated_at DESC
476
+ LIMIT ?
477
+ """,
478
+ (run_id, profile_id, q, q, q, bounded_limit),
479
+ ).fetchall()
480
+ return [self._row_to_item(row) for row in rows]
481
+
482
+ def hybrid_retrieve(
483
+ self,
484
+ query: str,
485
+ *,
486
+ run_id: str,
487
+ profile_id: str,
488
+ limit: int = 10,
489
+ ) -> list[dict[str, Any]]:
490
+ bounded_limit = max(1, min(int(limit), 100))
491
+ if self._backend == "json":
492
+ rows = self.query_scoped(query, run_id=run_id, profile_id=profile_id, limit=bounded_limit)
493
+ return [
494
+ {
495
+ **row,
496
+ "score": float(_token_overlap_score(query, row)),
497
+ }
498
+ for row in rows
499
+ ]
500
+
501
+ candidates: list[tuple[_Item, float]] = []
502
+ conn = self._sqlite_conn()
503
+ if self._fts_enabled:
504
+ rows = conn.execute(
505
+ """
506
+ SELECT m.*, bm25(memories_fts) AS rank
507
+ FROM memories_fts
508
+ JOIN memories AS m ON m.id = memories_fts.id
509
+ WHERE memories_fts MATCH ?
510
+ AND m.run_id = ?
511
+ AND m.profile_id = ?
512
+ ORDER BY rank ASC
513
+ LIMIT ?
514
+ """,
515
+ (_fts_query(query), run_id, profile_id, bounded_limit),
516
+ ).fetchall()
517
+ for row in rows:
518
+ item = self._row_to_item(row)
519
+ rank = row["rank"] if isinstance(row["rank"], (int, float)) else 0.0
520
+ keyword_score = 1.0 / (1.0 + max(float(rank), 0.0))
521
+ semantic_score = _token_overlap_score(query, item)
522
+ candidates.append((item, (0.7 * keyword_score) + (0.3 * semantic_score)))
523
+
524
+ if not candidates:
525
+ for item in self.query_scoped(query, run_id=run_id, profile_id=profile_id, limit=bounded_limit):
526
+ candidates.append((item, _token_overlap_score(query, item)))
527
+
528
+ candidates.sort(key=lambda pair: pair[1], reverse=True)
529
+ return [{**item, "score": round(score, 6)} for item, score in candidates[:bounded_limit]]
530
+
531
+ def index_artifact(
532
+ self,
533
+ *,
534
+ run_id: str,
535
+ profile_id: str,
536
+ kind: str,
537
+ path: str,
538
+ summary: str,
539
+ size_bytes: int = 0,
540
+ metadata: dict[str, Any] | None = None,
541
+ ) -> dict[str, Any]:
542
+ now = _utc_now_iso()
543
+ artifact_id = f"artifact-{uuid.uuid4().hex}"
544
+ clean_metadata = _sanitize_metadata(metadata or {})
545
+ handle = {
546
+ "artifact_id": artifact_id,
547
+ "run_id": run_id,
548
+ "profile_id": profile_id,
549
+ "kind": kind,
550
+ "path": path,
551
+ "summary": summary,
552
+ "size_bytes": int(size_bytes),
553
+ "metadata": clean_metadata,
554
+ "created_at": now,
555
+ }
556
+
557
+ if self._backend == "json":
558
+ payload = {
559
+ "field": "artifact.handle",
560
+ "value": json.dumps(handle, ensure_ascii=True),
561
+ "source": "artifact_index",
562
+ "project_scope": "",
563
+ "run_id": run_id,
564
+ "profile_id": profile_id,
565
+ "updated_at": now,
566
+ }
567
+ self.add(
568
+ key=f"artifact:{artifact_id}",
569
+ content=json.dumps(payload, ensure_ascii=True),
570
+ source_cli="runtime",
571
+ tags=[f"artifact_kind:{kind}"],
572
+ run_id=run_id,
573
+ profile_id=profile_id,
574
+ )
575
+ return handle
576
+
577
+ self._sqlite_conn().execute(
578
+ """
579
+ INSERT INTO artifacts(
580
+ artifact_id, run_id, profile_id, kind, path, summary, size_bytes, metadata_json, created_at
581
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
582
+ """,
583
+ (
584
+ artifact_id,
585
+ run_id,
586
+ profile_id,
587
+ kind,
588
+ path,
589
+ summary,
590
+ int(size_bytes),
591
+ json.dumps(clean_metadata, ensure_ascii=True),
592
+ now,
593
+ ),
594
+ )
595
+ self._sqlite_conn().commit()
596
+ return handle
597
+
598
+ def query_artifacts(
599
+ self,
600
+ *,
601
+ run_id: str,
602
+ profile_id: str | None = None,
603
+ kind: str | None = None,
604
+ limit: int = 50,
605
+ ) -> list[dict[str, Any]]:
606
+ bounded_limit = max(1, min(int(limit), 200))
607
+ if self._backend == "json":
608
+ handles: list[dict[str, Any]] = []
609
+ for item in self.list_all(run_id=run_id, profile_id=profile_id):
610
+ key = str(item.get("key", ""))
611
+ if not key.startswith("artifact:"):
612
+ continue
613
+ try:
614
+ payload = json.loads(str(item.get("content", "")))
615
+ except (TypeError, ValueError, json.JSONDecodeError):
616
+ continue
617
+ value_raw = payload.get("value")
618
+ if not isinstance(value_raw, str):
619
+ continue
620
+ try:
621
+ handle = json.loads(value_raw)
622
+ except (TypeError, ValueError, json.JSONDecodeError):
623
+ continue
624
+ if not isinstance(handle, dict):
625
+ continue
626
+ if profile_id is not None and str(handle.get("profile_id", "")) != profile_id:
627
+ continue
628
+ if kind is not None and str(handle.get("kind", "")) != kind:
629
+ continue
630
+ handles.append(_sanitize_artifact_handle(handle))
631
+ return handles[:bounded_limit]
632
+
633
+ where = ["run_id = ?"]
634
+ params: list[Any] = [run_id]
635
+ if profile_id is not None:
636
+ where.append("profile_id = ?")
637
+ params.append(profile_id)
638
+ if kind is not None:
639
+ where.append("kind = ?")
640
+ params.append(kind)
641
+ params.append(bounded_limit)
642
+ rows = self._sqlite_conn().execute(
643
+ f"""
644
+ SELECT artifact_id, run_id, profile_id, kind, path, summary, size_bytes, metadata_json, created_at
645
+ FROM artifacts
646
+ WHERE {' AND '.join(where)}
647
+ ORDER BY created_at DESC
648
+ LIMIT ?
649
+ """,
650
+ tuple(params),
651
+ ).fetchall()
652
+ handles: list[dict[str, Any]] = []
653
+ for row in rows:
654
+ metadata = _parse_json_object(row["metadata_json"])
655
+ handles.append(
656
+ {
657
+ "artifact_id": row["artifact_id"],
658
+ "run_id": row["run_id"],
659
+ "profile_id": row["profile_id"],
660
+ "kind": row["kind"],
661
+ "path": row["path"],
662
+ "summary": row["summary"],
663
+ "size_bytes": int(row["size_bytes"]),
664
+ "metadata": _sanitize_metadata(metadata),
665
+ "created_at": row["created_at"],
666
+ }
667
+ )
668
+ return handles
669
+
670
+ def _load_json_items(self) -> list[_Item]:
671
+ path = Path(self.store_path)
672
+ if not path.exists():
673
+ return []
674
+ try:
675
+ raw = json.loads(path.read_text())
676
+ if isinstance(raw, list):
677
+ return raw # type: ignore[return-value]
678
+ if isinstance(raw, dict) and str(raw.get("format", "")) == _JSON_ENVELOPE_FORMAT:
679
+ encrypted_payload = str(raw.get("payload", ""))
680
+ integrity = raw.get("integrity", {})
681
+ expected_sha = ""
682
+ if isinstance(integrity, dict):
683
+ expected_sha = str(integrity.get("sha256", ""))
684
+ if not encrypted_payload or hashlib.sha256(encrypted_payload.encode("utf-8")).hexdigest() != expected_sha:
685
+ return []
686
+ decrypted = self._decrypt_text(encrypted_payload, purpose="json-at-rest")
687
+ if not decrypted:
688
+ return []
689
+ payload = json.loads(decrypted)
690
+ if isinstance(payload, list):
691
+ return payload # type: ignore[return-value]
692
+ return []
693
+ except (json.JSONDecodeError, ValueError):
694
+ return []
695
+
696
+ def _save_json_items(self) -> None:
697
+ path = Path(self.store_path)
698
+ path.parent.mkdir(parents=True, exist_ok=True)
699
+ tmp_path = path.with_name(f"{path.name}.tmp")
700
+ _ = tmp_path.write_text(json.dumps(self._items, indent=2, ensure_ascii=True) + "\n")
701
+ _ = os.replace(tmp_path, path)
702
+
703
+ def _sqlite_conn(self) -> sqlite3.Connection:
704
+ if self._conn is None:
705
+ raise RuntimeError("SQLite backend is not initialized")
706
+ return self._conn
707
+
708
+ def _init_sqlite(self) -> None:
709
+ path = Path(self.store_path)
710
+ path.parent.mkdir(parents=True, exist_ok=True)
711
+ conn = sqlite3.connect(path)
712
+ conn.row_factory = sqlite3.Row
713
+ conn.execute("PRAGMA journal_mode=WAL")
714
+ conn.execute("PRAGMA foreign_keys=ON")
715
+ conn.execute(
716
+ """
717
+ CREATE TABLE IF NOT EXISTS memories (
718
+ id TEXT PRIMARY KEY,
719
+ key TEXT NOT NULL,
720
+ content TEXT NOT NULL,
721
+ source_cli TEXT NOT NULL,
722
+ tags_json TEXT NOT NULL,
723
+ run_id TEXT NOT NULL DEFAULT '',
724
+ profile_id TEXT NOT NULL DEFAULT '',
725
+ namespace TEXT NOT NULL DEFAULT '127.0.0.1:default',
726
+ retention_days INTEGER,
727
+ expires_at TEXT,
728
+ created_at TEXT NOT NULL,
729
+ updated_at TEXT NOT NULL,
730
+ quarantined INTEGER NOT NULL DEFAULT 0
731
+ )
732
+ """
733
+ )
734
+ _ensure_memory_columns(conn)
735
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(run_id, profile_id, updated_at)")
736
+ conn.execute(
737
+ """
738
+ CREATE TABLE IF NOT EXISTS artifacts (
739
+ artifact_id TEXT PRIMARY KEY,
740
+ run_id TEXT NOT NULL,
741
+ profile_id TEXT NOT NULL,
742
+ kind TEXT NOT NULL,
743
+ path TEXT NOT NULL,
744
+ summary TEXT NOT NULL,
745
+ size_bytes INTEGER NOT NULL DEFAULT 0,
746
+ metadata_json TEXT NOT NULL DEFAULT '{}',
747
+ created_at TEXT NOT NULL
748
+ )
749
+ """
750
+ )
751
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_artifacts_scope ON artifacts(run_id, profile_id, created_at)")
752
+ conn.execute(
753
+ """
754
+ CREATE TABLE IF NOT EXISTS lineage_edges (
755
+ edge_id INTEGER PRIMARY KEY AUTOINCREMENT,
756
+ parent_node TEXT NOT NULL,
757
+ child_node TEXT NOT NULL,
758
+ edge_type TEXT NOT NULL,
759
+ run_id TEXT NOT NULL,
760
+ profile_id TEXT NOT NULL,
761
+ metadata_json TEXT NOT NULL DEFAULT '{}',
762
+ created_at TEXT NOT NULL
763
+ )
764
+ """
765
+ )
766
+ conn.execute(
767
+ """
768
+ CREATE INDEX IF NOT EXISTS idx_lineage_scope
769
+ ON lineage_edges(run_id, profile_id, parent_node, child_node)
770
+ """
771
+ )
772
+
773
+ try:
774
+ conn.execute(
775
+ """
776
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts
777
+ USING fts5(id UNINDEXED, key, content, tags)
778
+ """
779
+ )
780
+ self._fts_enabled = True
781
+ except sqlite3.OperationalError:
782
+ self._fts_enabled = False
783
+
784
+ self._conn = conn
785
+ self._rebuild_fts_if_available()
786
+ conn.commit()
787
+
788
+ def _rebuild_fts_if_available(self) -> None:
789
+ if not self._fts_enabled:
790
+ return
791
+ conn = self._sqlite_conn()
792
+ conn.execute("DELETE FROM memories_fts")
793
+ rows = conn.execute("SELECT id, key, content, tags_json FROM memories").fetchall()
794
+ for row in rows:
795
+ tags = _parse_json_array(row["tags_json"])
796
+ conn.execute(
797
+ "INSERT INTO memories_fts(id, key, content, tags) VALUES (?, ?, ?, ?)",
798
+ (row["id"], row["key"], row["content"], " ".join(str(t) for t in tags)),
799
+ )
800
+
801
+ def _upsert_fts(self, item: _Item) -> None:
802
+ if not self._fts_enabled:
803
+ return
804
+ conn = self._sqlite_conn()
805
+ conn.execute("DELETE FROM memories_fts WHERE id = ?", (item["id"],))
806
+ tags = item.get("tags", [])
807
+ tags_text = " ".join(str(tag) for tag in tags) if isinstance(tags, list) else ""
808
+ conn.execute(
809
+ "INSERT INTO memories_fts(id, key, content, tags) VALUES (?, ?, ?, ?)",
810
+ (item["id"], str(item.get("key", "")), str(item.get("content", "")), tags_text),
811
+ )
812
+
813
+ def _delete_fts(self, item_id: str) -> None:
814
+ if not self._fts_enabled:
815
+ return
816
+ self._sqlite_conn().execute("DELETE FROM memories_fts WHERE id = ?", (item_id,))
817
+
818
+ def _row_to_item(self, row: sqlite3.Row) -> _Item:
819
+ raw_expires_at = row["expires_at"] if "expires_at" in row.keys() else None
820
+ expires_at = _normalize_expires_at(raw_expires_at)
821
+ return {
822
+ "id": row["id"],
823
+ "key": row["key"],
824
+ "content": self._decrypt_text(str(row["content"]), purpose="sqlite-content"),
825
+ "source_cli": row["source_cli"],
826
+ "tags": _parse_json_array(row["tags_json"]),
827
+ "run_id": row["run_id"],
828
+ "profile_id": row["profile_id"],
829
+ "namespace": _qualify_namespace(row["namespace"] if "namespace" in row.keys() else _DEFAULT_NAMESPACE),
830
+ "retention_days": _normalize_retention_days(row["retention_days"] if "retention_days" in row.keys() else None),
831
+ "expires_at": expires_at,
832
+ "created_at": row["created_at"],
833
+ "updated_at": row["updated_at"],
834
+ "quarantined": bool(row["quarantined"] if "quarantined" in row.keys() else 0),
835
+ }
836
+
837
+ def _derive_key_bytes(self, *, purpose: str) -> bytes:
838
+ secret_seed = os.environ.get("OMG_MEMORY_SECRET", "").strip()
839
+ if not secret_seed:
840
+ secret_seed = f"{os.path.expanduser('~')}|{self._memory_host}|{socket.gethostname()}"
841
+ material = f"{secret_seed}|{self._memory_host}|{purpose}".encode("utf-8")
842
+ return hashlib.sha256(material).digest()
843
+
844
+ def _encrypt_text(self, text: str, *, purpose: str) -> str:
845
+ if text.startswith(_ENCRYPTED_PREFIX):
846
+ return text
847
+ key_bytes = self._derive_key_bytes(purpose=purpose)
848
+ if _has_fernet and Fernet is not None:
849
+ fernet_key = base64.urlsafe_b64encode(key_bytes)
850
+ token = Fernet(fernet_key).encrypt(text.encode("utf-8")).decode("utf-8")
851
+ return f"{_ENCRYPTED_PREFIX}{token}"
852
+ encoded = text.encode("utf-8")
853
+ cipher = bytes(byte ^ key_bytes[idx % len(key_bytes)] for idx, byte in enumerate(encoded))
854
+ return f"{_ENCRYPTED_PREFIX}{base64.urlsafe_b64encode(cipher).decode('ascii')}"
855
+
856
+ def _decrypt_text(self, text: str, *, purpose: str) -> str:
857
+ if not text.startswith(_ENCRYPTED_PREFIX):
858
+ return text
859
+ payload = text[len(_ENCRYPTED_PREFIX) :]
860
+ key_bytes = self._derive_key_bytes(purpose=purpose)
861
+ if _has_fernet and Fernet is not None:
862
+ try:
863
+ fernet_key = base64.urlsafe_b64encode(key_bytes)
864
+ return Fernet(fernet_key).decrypt(payload.encode("utf-8")).decode("utf-8")
865
+ except InvalidToken:
866
+ return ""
867
+ try:
868
+ cipher = base64.urlsafe_b64decode(payload.encode("ascii"))
869
+ except ValueError:
870
+ return ""
871
+ plain = bytes(byte ^ key_bytes[idx % len(key_bytes)] for idx, byte in enumerate(cipher))
872
+ try:
873
+ return plain.decode("utf-8")
874
+ except UnicodeDecodeError:
875
+ return ""
876
+
877
+
878
+ def project_preference_signals(
879
+ project_dir: str,
880
+ *,
881
+ store_path: str | None = None,
882
+ max_signals: int = _PREFERENCE_SIGNAL_LIMIT,
883
+ ) -> list[dict[str, Any]]:
884
+ canonical_project = os.path.realpath(project_dir)
885
+ if not canonical_project:
886
+ return []
887
+
888
+ limit = max(1, min(int(max_signals), _PREFERENCE_SIGNAL_LIMIT))
889
+ resolved_store_path = store_path
890
+ if resolved_store_path is None:
891
+ legacy_json_path = Path.home() / ".omg" / "shared-memory" / "store.json"
892
+ if legacy_json_path.exists():
893
+ resolved_store_path = str(legacy_json_path)
894
+
895
+ store = MemoryStore(store_path=resolved_store_path)
896
+ items = list(reversed(store.list_all()))
897
+ results: list[dict[str, Any]] = []
898
+ seen: set[tuple[str, str, str, str]] = set()
899
+
900
+ for item in items:
901
+ signal = _extract_project_signal(item, canonical_project)
902
+ if signal is None:
903
+ continue
904
+
905
+ signature = (
906
+ str(signal.get("field", "")),
907
+ str(signal.get("value", "")),
908
+ str(signal.get("source", "")),
909
+ str(signal.get("run_id", "")),
910
+ )
911
+ if signature in seen:
912
+ continue
913
+ seen.add(signature)
914
+ results.append(signal)
915
+ if len(results) >= limit:
916
+ break
917
+
918
+ return results
919
+
920
+
921
+ def _extract_project_signal(item: _Item, canonical_project: str) -> dict[str, Any] | None:
922
+ raw_content = item.get("content")
923
+ if not isinstance(raw_content, str):
924
+ return None
925
+
926
+ try:
927
+ payload = json.loads(raw_content)
928
+ except (TypeError, ValueError, json.JSONDecodeError):
929
+ return None
930
+ if not isinstance(payload, dict):
931
+ return None
932
+
933
+ scope = _extract_scope(payload, item)
934
+ if os.path.realpath(scope) != canonical_project:
935
+ return None
936
+
937
+ field = str(payload.get("field", "")).strip()
938
+ if not _is_allowed_field(field):
939
+ return None
940
+
941
+ value = _normalize_value(payload.get("value"))
942
+ if not value:
943
+ return None
944
+
945
+ source = str(payload.get("source", "inferred_observation")).strip().lower() or "inferred_observation"
946
+ confidence = _clamp_confidence(payload.get("confidence"))
947
+ contradicted = bool(payload.get("contradicted") is True)
948
+
949
+ section = classify_preference_section(field, value)
950
+ destructive = is_destructive_preference(field, value)
951
+
952
+ signal: dict[str, Any] = {
953
+ "field": field,
954
+ "value": value,
955
+ "source": source,
956
+ "confidence": confidence,
957
+ "project_scope": canonical_project,
958
+ "run_id": str(payload.get("run_id", "")).strip(),
959
+ "updated_at": str(payload.get("updated_at", item.get("updated_at", ""))).strip(),
960
+ "contradicted": contradicted,
961
+ "section": section,
962
+ "destructive": destructive,
963
+ }
964
+ return signal
965
+
966
+
967
+ def _extract_scope(payload: dict[str, Any], item: _Item) -> str:
968
+ scope = str(payload.get("project_scope", "")).strip()
969
+ if scope:
970
+ return scope
971
+ tags = item.get("tags", [])
972
+ if isinstance(tags, list):
973
+ for tag in tags:
974
+ text = str(tag)
975
+ if text.startswith("project_scope:"):
976
+ return text.split(":", 1)[1].strip()
977
+ return ""
978
+
979
+
980
+ def _is_allowed_field(field: str) -> bool:
981
+ if field == "preferences.architecture_requests":
982
+ return True
983
+ if field == "user_vector.tags":
984
+ return True
985
+ return field.startswith("preferences.constraints.")
986
+
987
+
988
+ def _normalize_value(raw: Any) -> str:
989
+ text = " ".join(str(raw).strip().split())
990
+ if not text:
991
+ return ""
992
+ return text[:_PREFERENCE_VALUE_MAX]
993
+
994
+
995
+ def _clamp_confidence(raw: Any) -> float:
996
+ try:
997
+ score = float(raw)
998
+ except (TypeError, ValueError):
999
+ score = 0.0
1000
+ if score < 0.0:
1001
+ return 0.0
1002
+ if score > 1.0:
1003
+ return 1.0
1004
+ return round(score, 3)
1005
+
1006
+
1007
+ def _token_overlap_score(query: str, item: dict[str, Any]) -> float:
1008
+ query_tokens = {token for token in query.lower().split() if token}
1009
+ if not query_tokens:
1010
+ return 0.0
1011
+ tags_value = item.get("tags", [])
1012
+ tags_text = " ".join(str(tag) for tag in tags_value) if isinstance(tags_value, list) else ""
1013
+ corpus = " ".join([str(item.get("key", "")), str(item.get("content", "")), tags_text]).lower()
1014
+ corpus_tokens = {token for token in corpus.split() if token}
1015
+ if not corpus_tokens:
1016
+ return 0.0
1017
+ overlap = len(query_tokens.intersection(corpus_tokens))
1018
+ return float(overlap) / float(len(query_tokens))
1019
+
1020
+
1021
+ def _fts_query(query: str) -> str:
1022
+ tokens = [token for token in query.replace('"', " ").split() if token]
1023
+ if not tokens:
1024
+ return ""
1025
+ return " AND ".join(tokens)
1026
+
1027
+
1028
+ def _parse_json_array(raw: object) -> list[Any]:
1029
+ if isinstance(raw, list):
1030
+ return raw
1031
+ if not isinstance(raw, str):
1032
+ return []
1033
+ try:
1034
+ payload = json.loads(raw)
1035
+ except (TypeError, ValueError, json.JSONDecodeError):
1036
+ return []
1037
+ return payload if isinstance(payload, list) else []
1038
+
1039
+
1040
+ def _parse_json_object(raw: object) -> dict[str, Any]:
1041
+ if isinstance(raw, dict):
1042
+ return raw
1043
+ if not isinstance(raw, str):
1044
+ return {}
1045
+ try:
1046
+ payload = json.loads(raw)
1047
+ except (TypeError, ValueError, json.JSONDecodeError):
1048
+ return {}
1049
+ return payload if isinstance(payload, dict) else {}
1050
+
1051
+
1052
+ def _sanitize_metadata(metadata: dict[str, Any]) -> dict[str, Any]:
1053
+ clean: dict[str, Any] = {}
1054
+ omitted_payload = False
1055
+ for key, value in metadata.items():
1056
+ key_text = str(key)
1057
+ if key_text == "payload":
1058
+ omitted_payload = True
1059
+ continue
1060
+ if isinstance(value, str) and len(value) > _INLINE_METADATA_MAX:
1061
+ clean[key_text] = value[:_INLINE_METADATA_MAX] + "..."
1062
+ continue
1063
+ clean[key_text] = value
1064
+ if omitted_payload:
1065
+ clean["omitted_payload"] = True
1066
+ return clean
1067
+
1068
+
1069
+ def _sanitize_artifact_handle(handle: dict[str, Any]) -> dict[str, Any]:
1070
+ sanitized = dict(handle)
1071
+ metadata_raw = sanitized.get("metadata")
1072
+ metadata = metadata_raw if isinstance(metadata_raw, dict) else {}
1073
+ sanitized["metadata"] = _sanitize_metadata(metadata)
1074
+ sanitized.pop("payload", None)
1075
+ return sanitized
1076
+
1077
+
1078
+ def _normalize_item(item: _Item) -> _Item:
1079
+ normalized = dict(item)
1080
+ normalized["content"] = _redact_pii(str(item.get("content", "")))
1081
+ normalized["namespace"] = _qualify_namespace(item.get("namespace", _DEFAULT_NAMESPACE))
1082
+ normalized["retention_days"] = _normalize_retention_days(item.get("retention_days"))
1083
+ created_at = str(item.get("created_at", "")).strip() or _utc_now_iso()
1084
+ normalized_expires_at = _normalize_expires_at(item.get("expires_at"))
1085
+ normalized["expires_at"] = normalized_expires_at or _compute_expires_at(created_at, normalized["retention_days"])
1086
+ normalized["quarantined"] = bool(item.get("quarantined", False))
1087
+ return normalized
1088
+
1089
+
1090
+ def _redact_pii(content: str) -> str:
1091
+ redacted = content
1092
+ for pattern, token in _PII_PATTERNS:
1093
+ redacted = pattern.sub(token, redacted)
1094
+ return redacted
1095
+
1096
+
1097
+ def _qualify_namespace(namespace: object) -> str:
1098
+ text = str(namespace or "").strip()
1099
+ if not text:
1100
+ text = _DEFAULT_NAMESPACE
1101
+ if ":" in text:
1102
+ host, local = text.split(":", 1)
1103
+ if host.strip() and local.strip():
1104
+ return f"{host.strip()}:{local.strip()}"
1105
+ host = os.environ.get("OMG_MEMORY_HOST", _DEFAULT_MEMORY_HOST).strip() or _DEFAULT_MEMORY_HOST
1106
+ return f"{host}:{text}"
1107
+
1108
+
1109
+ def _normalize_retention_days(raw: object) -> int | None:
1110
+ if raw is None or raw == "":
1111
+ return None
1112
+ value = int(str(raw).strip())
1113
+ if value < 0:
1114
+ raise ValueError("retention_days must be >= 0")
1115
+ return value
1116
+
1117
+
1118
+ def _compute_expires_at(created_at: str, retention_days: int | None) -> str | None:
1119
+ if retention_days is None:
1120
+ return None
1121
+ try:
1122
+ created_dt = datetime.fromisoformat(created_at)
1123
+ except ValueError:
1124
+ created_dt = datetime.now(timezone.utc)
1125
+ if created_dt.tzinfo is None:
1126
+ created_dt = created_dt.replace(tzinfo=timezone.utc)
1127
+ return (created_dt + timedelta(days=retention_days)).isoformat()
1128
+
1129
+
1130
+ def _is_expired(item: _Item) -> bool:
1131
+ expires_at = _normalize_expires_at(item.get("expires_at")) or ""
1132
+ if not expires_at:
1133
+ return False
1134
+ try:
1135
+ expires_dt = datetime.fromisoformat(expires_at)
1136
+ except ValueError:
1137
+ return False
1138
+ if expires_dt.tzinfo is None:
1139
+ expires_dt = expires_dt.replace(tzinfo=timezone.utc)
1140
+ return expires_dt <= datetime.now(timezone.utc)
1141
+
1142
+
1143
+ def _normalize_expires_at(raw: object) -> str | None:
1144
+ if raw is None:
1145
+ return None
1146
+ text = str(raw).strip()
1147
+ if not text or text.lower() == "none":
1148
+ return None
1149
+ return text
1150
+
1151
+
1152
+ def _ensure_memory_columns(conn: sqlite3.Connection) -> None:
1153
+ columns = {
1154
+ str(row["name"]): str(row["name"])
1155
+ for row in conn.execute("PRAGMA table_info(memories)").fetchall()
1156
+ }
1157
+ if "namespace" not in columns:
1158
+ _safe_add_memory_column(
1159
+ conn,
1160
+ "ALTER TABLE memories ADD COLUMN namespace TEXT NOT NULL DEFAULT '127.0.0.1:default'",
1161
+ )
1162
+ if "retention_days" not in columns:
1163
+ _safe_add_memory_column(conn, "ALTER TABLE memories ADD COLUMN retention_days INTEGER")
1164
+ if "expires_at" not in columns:
1165
+ _safe_add_memory_column(conn, "ALTER TABLE memories ADD COLUMN expires_at TEXT")
1166
+ if "quarantined" not in columns:
1167
+ _safe_add_memory_column(conn, "ALTER TABLE memories ADD COLUMN quarantined INTEGER NOT NULL DEFAULT 0")
1168
+
1169
+
1170
+ def _safe_add_memory_column(conn: sqlite3.Connection, ddl: str) -> None:
1171
+ try:
1172
+ conn.execute(ddl)
1173
+ except sqlite3.OperationalError as exc:
1174
+ if "duplicate column name" not in str(exc).lower():
1175
+ raise
1176
+
1177
+
1178
+ def _utc_now_iso() -> str:
1179
+ return datetime.now(timezone.utc).isoformat()
1180
+
1181
+
1182
+ __all__ = ["MemoryStore", "MemoryStoreFullError", "project_preference_signals"]