oh-my-second-brain 0.18.3 → 0.20.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 (339) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +3 -4
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/CHANGELOG-assets.md +14 -0
  5. package/CHANGELOG-cli.md +34 -0
  6. package/CHANGELOG-kernel.md +53 -0
  7. package/CHANGELOG-mcp.md +25 -0
  8. package/CHANGELOG-vendors.md +12 -0
  9. package/CHANGELOG.md +10 -0
  10. package/README.ko.md +235 -54
  11. package/README.md +235 -54
  12. package/assets/claude/CLAUDE.md +11 -6
  13. package/assets/claude/hooks/oms-guard.mjs +23 -8
  14. package/assets/codex/AGENTS.md +5 -4
  15. package/assets/codex/rules/oms.md +6 -6
  16. package/assets/hermes/README.md +6 -6
  17. package/assets/hermes/SOUL.md +8 -7
  18. package/assets/hermes-manifest.json +2 -2
  19. package/assets/readme/hero.svg +23 -0
  20. package/assets/skills/distill/SKILL.md +1 -1
  21. package/assets/skills/doctor/SKILL.md +13 -6
  22. package/assets/skills/interview/SKILL.md +38 -0
  23. package/assets/skills/search/SKILL.md +7 -4
  24. package/assets/skills/setup/SKILL.md +14 -12
  25. package/assets/skills/write/SKILL.md +10 -6
  26. package/dist/cli/audit.js +2 -2
  27. package/dist/cli/contract-command.d.ts +18 -0
  28. package/dist/cli/contract-command.js +175 -55
  29. package/dist/cli/contract-command.js.map +1 -1
  30. package/dist/cli/doctor-command.d.ts +7 -0
  31. package/dist/cli/doctor-command.js +158 -0
  32. package/dist/cli/doctor-command.js.map +1 -0
  33. package/dist/cli/engine-session.js +1 -1
  34. package/dist/cli/engine-session.js.map +1 -1
  35. package/dist/cli/evolution-approve.d.ts +64 -0
  36. package/dist/cli/evolution-approve.js +138 -0
  37. package/dist/cli/evolution-approve.js.map +1 -0
  38. package/dist/cli/graph-command.js +5 -12
  39. package/dist/cli/graph-command.js.map +1 -1
  40. package/dist/cli/host-commands.d.ts +1 -1
  41. package/dist/cli/host-commands.js +7 -7
  42. package/dist/cli/host-commands.js.map +1 -1
  43. package/dist/cli/index-command.js +9 -9
  44. package/dist/cli/index-command.js.map +1 -1
  45. package/dist/cli/interview-command.d.ts +10 -0
  46. package/dist/cli/interview-command.js +72 -0
  47. package/dist/cli/interview-command.js.map +1 -0
  48. package/dist/cli/lineage-command.d.ts +16 -0
  49. package/dist/cli/lineage-command.js +113 -0
  50. package/dist/cli/lineage-command.js.map +1 -0
  51. package/dist/cli/model-command.js +1 -1
  52. package/dist/cli/model-command.js.map +1 -1
  53. package/dist/cli/note-command.js +1 -3
  54. package/dist/cli/note-command.js.map +1 -1
  55. package/dist/cli/oms.d.ts +0 -3
  56. package/dist/cli/oms.js +61 -95
  57. package/dist/cli/oms.js.map +1 -1
  58. package/dist/cli/package-command.js +7 -7
  59. package/dist/cli/package-command.js.map +1 -1
  60. package/dist/cli/removed-families.d.ts +6 -0
  61. package/dist/cli/removed-families.js +39 -0
  62. package/dist/cli/removed-families.js.map +1 -0
  63. package/dist/cli/search-usage.js +18 -9
  64. package/dist/cli/search-usage.js.map +1 -1
  65. package/dist/cli/search.d.ts +9 -1
  66. package/dist/cli/search.js +103 -50
  67. package/dist/cli/search.js.map +1 -1
  68. package/dist/cli/setup-command.d.ts +1 -1
  69. package/dist/cli/setup-command.js +48 -15
  70. package/dist/cli/setup-command.js.map +1 -1
  71. package/dist/cli/status-command.d.ts +5 -0
  72. package/dist/cli/status-command.js +72 -59
  73. package/dist/cli/status-command.js.map +1 -1
  74. package/dist/cli/usage.js +30 -33
  75. package/dist/cli/usage.js.map +1 -1
  76. package/dist/cli/write-command.d.ts +14 -0
  77. package/dist/cli/write-command.js +120 -0
  78. package/dist/cli/write-command.js.map +1 -0
  79. package/dist/kernel/contract/audit.d.ts +1 -1
  80. package/dist/kernel/contract/audit.js +8 -4
  81. package/dist/kernel/contract/audit.js.map +1 -1
  82. package/dist/kernel/contract/contradiction.d.ts +26 -0
  83. package/dist/kernel/contract/contradiction.js +40 -0
  84. package/dist/kernel/contract/contradiction.js.map +1 -0
  85. package/dist/kernel/contract/digest.d.ts +15 -0
  86. package/dist/kernel/contract/digest.js +24 -0
  87. package/dist/kernel/contract/digest.js.map +1 -0
  88. package/dist/kernel/contract/gap-ledger.d.ts +132 -0
  89. package/dist/kernel/contract/gap-ledger.js +398 -0
  90. package/dist/kernel/contract/gap-ledger.js.map +1 -0
  91. package/dist/kernel/contract/gaps-report.d.ts +44 -0
  92. package/dist/kernel/contract/gaps-report.js +49 -0
  93. package/dist/kernel/contract/gaps-report.js.map +1 -0
  94. package/dist/kernel/contract/generation-snapshot.d.ts +56 -0
  95. package/dist/kernel/contract/generation-snapshot.js +245 -0
  96. package/dist/kernel/contract/generation-snapshot.js.map +1 -0
  97. package/dist/kernel/contract/guard-events.d.ts +4 -0
  98. package/dist/kernel/contract/guard-events.js +3 -1
  99. package/dist/kernel/contract/guard-events.js.map +1 -1
  100. package/dist/kernel/contract/interview-log.d.ts +69 -0
  101. package/dist/kernel/contract/interview-log.js +223 -0
  102. package/dist/kernel/contract/interview-log.js.map +1 -0
  103. package/dist/kernel/contract/interview-resume.d.ts +74 -0
  104. package/dist/kernel/contract/interview-resume.js +177 -0
  105. package/dist/kernel/contract/interview-resume.js.map +1 -0
  106. package/dist/kernel/contract/interview.d.ts +58 -15
  107. package/dist/kernel/contract/interview.js +111 -179
  108. package/dist/kernel/contract/interview.js.map +1 -1
  109. package/dist/kernel/contract/judge-write.d.ts +54 -14
  110. package/dist/kernel/contract/judge-write.js +65 -33
  111. package/dist/kernel/contract/judge-write.js.map +1 -1
  112. package/dist/kernel/contract/judge.d.ts +14 -2
  113. package/dist/kernel/contract/judge.js +39 -104
  114. package/dist/kernel/contract/judge.js.map +1 -1
  115. package/dist/kernel/contract/legacy.d.ts +15 -0
  116. package/dist/kernel/contract/legacy.js +9 -0
  117. package/dist/kernel/contract/legacy.js.map +1 -0
  118. package/dist/kernel/contract/lineage-health.d.ts +20 -0
  119. package/dist/kernel/contract/lineage-health.js +71 -0
  120. package/dist/kernel/contract/lineage-health.js.map +1 -0
  121. package/dist/kernel/contract/lineage.d.ts +164 -0
  122. package/dist/kernel/contract/lineage.js +304 -0
  123. package/dist/kernel/contract/lineage.js.map +1 -0
  124. package/dist/kernel/contract/loosening.d.ts +15 -11
  125. package/dist/kernel/contract/loosening.js +13 -102
  126. package/dist/kernel/contract/loosening.js.map +1 -1
  127. package/dist/kernel/contract/mutation.d.ts +56 -0
  128. package/dist/kernel/contract/mutation.js +83 -0
  129. package/dist/kernel/contract/mutation.js.map +1 -0
  130. package/dist/kernel/contract/redact.js +0 -9
  131. package/dist/kernel/contract/redact.js.map +1 -1
  132. package/dist/kernel/contract/revision.d.ts +10 -0
  133. package/dist/kernel/contract/revision.js +14 -0
  134. package/dist/kernel/contract/revision.js.map +1 -0
  135. package/dist/kernel/contract/scripted-interview.d.ts +29 -2
  136. package/dist/kernel/contract/scripted-interview.js +72 -5
  137. package/dist/kernel/contract/scripted-interview.js.map +1 -1
  138. package/dist/kernel/contract/state-dir.d.ts +50 -0
  139. package/dist/kernel/contract/state-dir.js +234 -0
  140. package/dist/kernel/contract/state-dir.js.map +1 -0
  141. package/dist/kernel/contract/status.d.ts +32 -8
  142. package/dist/kernel/contract/status.js +62 -14
  143. package/dist/kernel/contract/status.js.map +1 -1
  144. package/dist/kernel/contract/store.d.ts +133 -9
  145. package/dist/kernel/contract/store.js +240 -47
  146. package/dist/kernel/contract/store.js.map +1 -1
  147. package/dist/kernel/contract/types.d.ts +69 -18
  148. package/dist/kernel/contract/types.js +71 -30
  149. package/dist/kernel/contract/types.js.map +1 -1
  150. package/dist/kernel/contract/vault-id.d.ts +5 -1
  151. package/dist/kernel/contract/vault-id.js +10 -3
  152. package/dist/kernel/contract/vault-id.js.map +1 -1
  153. package/dist/kernel/conventions/frontmatter.js +25 -0
  154. package/dist/kernel/conventions/frontmatter.js.map +1 -1
  155. package/dist/kernel/conventions/note-exclude.d.ts +6 -9
  156. package/dist/kernel/conventions/note-exclude.js +29 -117
  157. package/dist/kernel/conventions/note-exclude.js.map +1 -1
  158. package/dist/kernel/conventions/report.js +1 -1
  159. package/dist/kernel/conventions/report.js.map +1 -1
  160. package/dist/kernel/doctor/evolution-ops.d.ts +48 -0
  161. package/dist/kernel/doctor/evolution-ops.js +173 -0
  162. package/dist/kernel/doctor/evolution-ops.js.map +1 -0
  163. package/dist/kernel/doctor/evolution-status.d.ts +31 -0
  164. package/dist/kernel/doctor/evolution-status.js +38 -0
  165. package/dist/kernel/doctor/evolution-status.js.map +1 -0
  166. package/dist/kernel/doctor/service.d.ts +23 -3
  167. package/dist/kernel/doctor/service.js +158 -4
  168. package/dist/kernel/doctor/service.js.map +1 -1
  169. package/dist/kernel/engine/embed/config.js +3 -3
  170. package/dist/kernel/engine/embed/config.js.map +1 -1
  171. package/dist/kernel/engine/index-update.d.ts +41 -0
  172. package/dist/kernel/engine/index-update.js +133 -0
  173. package/dist/kernel/engine/index-update.js.map +1 -0
  174. package/dist/kernel/engine/retrieval/folder-context.js +1 -1
  175. package/dist/kernel/engine/retrieval/template-source.js +24 -19
  176. package/dist/kernel/engine/retrieval/template-source.js.map +1 -1
  177. package/dist/kernel/evolution/convergence.d.ts +28 -0
  178. package/dist/kernel/evolution/convergence.js +82 -0
  179. package/dist/kernel/evolution/convergence.js.map +1 -0
  180. package/dist/kernel/evolution/decision.d.ts +14 -0
  181. package/dist/kernel/evolution/decision.js +2 -0
  182. package/dist/kernel/evolution/decision.js.map +1 -0
  183. package/dist/kernel/evolution/evaluator.d.ts +47 -0
  184. package/dist/kernel/evolution/evaluator.js +34 -0
  185. package/dist/kernel/evolution/evaluator.js.map +1 -0
  186. package/dist/kernel/evolution/events.d.ts +28 -0
  187. package/dist/kernel/evolution/events.js +108 -0
  188. package/dist/kernel/evolution/events.js.map +1 -0
  189. package/dist/kernel/evolution/evolution-lock.d.ts +48 -0
  190. package/dist/kernel/evolution/evolution-lock.js +133 -0
  191. package/dist/kernel/evolution/evolution-lock.js.map +1 -0
  192. package/dist/kernel/evolution/evolve.d.ts +34 -0
  193. package/dist/kernel/evolution/evolve.js +80 -0
  194. package/dist/kernel/evolution/evolve.js.map +1 -0
  195. package/dist/kernel/evolution/human-approval.d.ts +37 -0
  196. package/dist/kernel/evolution/human-approval.js +37 -0
  197. package/dist/kernel/evolution/human-approval.js.map +1 -0
  198. package/dist/kernel/evolution/maker.d.ts +42 -0
  199. package/dist/kernel/evolution/maker.js +116 -0
  200. package/dist/kernel/evolution/maker.js.map +1 -0
  201. package/dist/kernel/evolution/mutation-direction.d.ts +31 -0
  202. package/dist/kernel/evolution/mutation-direction.js +113 -0
  203. package/dist/kernel/evolution/mutation-direction.js.map +1 -0
  204. package/dist/kernel/evolution/policy.d.ts +51 -0
  205. package/dist/kernel/evolution/policy.js +92 -0
  206. package/dist/kernel/evolution/policy.js.map +1 -0
  207. package/dist/kernel/evolution/rate-limit.d.ts +36 -0
  208. package/dist/kernel/evolution/rate-limit.js +59 -0
  209. package/dist/kernel/evolution/rate-limit.js.map +1 -0
  210. package/dist/kernel/evolution/request-state.d.ts +149 -0
  211. package/dist/kernel/evolution/request-state.js +374 -0
  212. package/dist/kernel/evolution/request-state.js.map +1 -0
  213. package/dist/kernel/evolution/revert.d.ts +30 -0
  214. package/dist/kernel/evolution/revert.js +98 -0
  215. package/dist/kernel/evolution/revert.js.map +1 -0
  216. package/dist/kernel/evolution/seal-gate-human.d.ts +29 -0
  217. package/dist/kernel/evolution/seal-gate-human.js +71 -0
  218. package/dist/kernel/evolution/seal-gate-human.js.map +1 -0
  219. package/dist/kernel/evolution/seal-gate.d.ts +70 -0
  220. package/dist/kernel/evolution/seal-gate.js +248 -0
  221. package/dist/kernel/evolution/seal-gate.js.map +1 -0
  222. package/dist/kernel/evolution/snapshot-contract.d.ts +20 -0
  223. package/dist/kernel/evolution/snapshot-contract.js +21 -0
  224. package/dist/kernel/evolution/snapshot-contract.js.map +1 -0
  225. package/dist/kernel/evolution/stage-consensus.d.ts +104 -0
  226. package/dist/kernel/evolution/stage-consensus.js +185 -0
  227. package/dist/kernel/evolution/stage-consensus.js.map +1 -0
  228. package/dist/kernel/evolution/stage-mechanical.d.ts +23 -0
  229. package/dist/kernel/evolution/stage-mechanical.js +84 -0
  230. package/dist/kernel/evolution/stage-mechanical.js.map +1 -0
  231. package/dist/kernel/evolution/stage-semantic.d.ts +35 -0
  232. package/dist/kernel/evolution/stage-semantic.js +80 -0
  233. package/dist/kernel/evolution/stage-semantic.js.map +1 -0
  234. package/dist/kernel/harness/surface-registry.d.ts +2 -0
  235. package/dist/kernel/harness/surface-registry.js +11 -19
  236. package/dist/kernel/harness/surface-registry.js.map +1 -1
  237. package/dist/kernel/install/asset-health.js +1 -1
  238. package/dist/kernel/install/asset-health.js.map +1 -1
  239. package/dist/kernel/link/convention-note.js +3 -3
  240. package/dist/kernel/link/convention-note.js.map +1 -1
  241. package/dist/kernel/search/read-exact.d.ts +45 -0
  242. package/dist/kernel/search/read-exact.js +175 -0
  243. package/dist/kernel/search/read-exact.js.map +1 -0
  244. package/dist/kernel/text/nfc.d.ts +11 -0
  245. package/dist/kernel/text/nfc.js +16 -0
  246. package/dist/kernel/text/nfc.js.map +1 -0
  247. package/dist/kernel/update/update.js +8 -8
  248. package/dist/kernel/update/update.js.map +1 -1
  249. package/dist/kernel/write/ambiguity.d.ts +91 -0
  250. package/dist/kernel/write/ambiguity.js +147 -0
  251. package/dist/kernel/write/ambiguity.js.map +1 -0
  252. package/dist/kernel/write/coerce.d.ts +42 -0
  253. package/dist/kernel/write/coerce.js +254 -0
  254. package/dist/kernel/write/coerce.js.map +1 -0
  255. package/dist/kernel/write/conform.d.ts +26 -0
  256. package/dist/kernel/write/conform.js +190 -0
  257. package/dist/kernel/write/conform.js.map +1 -0
  258. package/dist/kernel/write/frame.d.ts +44 -0
  259. package/dist/kernel/write/frame.js +66 -0
  260. package/dist/kernel/write/frame.js.map +1 -0
  261. package/dist/kernel/write/live-templates.d.ts +63 -0
  262. package/dist/kernel/write/live-templates.js +104 -0
  263. package/dist/kernel/write/live-templates.js.map +1 -0
  264. package/dist/{mcp → kernel/write}/note-write.js +1 -1
  265. package/dist/kernel/write/note-write.js.map +1 -0
  266. package/dist/kernel/write/payload.d.ts +53 -0
  267. package/dist/kernel/write/payload.js +51 -0
  268. package/dist/kernel/write/payload.js.map +1 -0
  269. package/dist/kernel/write/pipeline.d.ts +110 -0
  270. package/dist/kernel/write/pipeline.js +203 -0
  271. package/dist/kernel/write/pipeline.js.map +1 -0
  272. package/dist/kernel/write/receipt.d.ts +76 -0
  273. package/dist/kernel/write/receipt.js +26 -0
  274. package/dist/kernel/write/receipt.js.map +1 -0
  275. package/dist/mcp/server.d.ts +3 -0
  276. package/dist/mcp/server.js +72 -443
  277. package/dist/mcp/server.js.map +1 -1
  278. package/dist/mcp/tools/doctor.d.ts +4 -0
  279. package/dist/mcp/tools/doctor.js +89 -0
  280. package/dist/mcp/tools/doctor.js.map +1 -0
  281. package/dist/mcp/tools/interview.d.ts +27 -0
  282. package/dist/mcp/tools/interview.js +264 -0
  283. package/dist/mcp/tools/interview.js.map +1 -0
  284. package/dist/mcp/tools/link.d.ts +4 -0
  285. package/dist/mcp/tools/link.js +24 -0
  286. package/dist/mcp/tools/link.js.map +1 -0
  287. package/dist/mcp/tools/search.d.ts +14 -0
  288. package/dist/mcp/tools/search.js +259 -0
  289. package/dist/mcp/tools/search.js.map +1 -0
  290. package/dist/mcp/tools/shared.d.ts +28 -0
  291. package/dist/mcp/tools/shared.js +34 -0
  292. package/dist/mcp/tools/shared.js.map +1 -0
  293. package/dist/mcp/tools/status.d.ts +9 -0
  294. package/dist/mcp/tools/status.js +50 -0
  295. package/dist/mcp/tools/status.js.map +1 -0
  296. package/dist/mcp/tools/write.d.ts +9 -0
  297. package/dist/mcp/tools/write.js +41 -0
  298. package/dist/mcp/tools/write.js.map +1 -0
  299. package/dist/mcp/update-notice.js +1 -1
  300. package/dist/mcp/update-notice.js.map +1 -1
  301. package/dist/vendors/claude/hook/pre-tool-use.d.ts +19 -2
  302. package/dist/vendors/claude/hook/pre-tool-use.js +69 -18
  303. package/dist/vendors/claude/hook/pre-tool-use.js.map +1 -1
  304. package/dist/vendors/codex/codex.js +2 -2
  305. package/dist/vendors/codex/codex.js.map +1 -1
  306. package/dist/vendors/hermes/hermes.js +18 -9
  307. package/dist/vendors/hermes/hermes.js.map +1 -1
  308. package/docs/adapters.md +4 -4
  309. package/docs/architecture.md +14 -14
  310. package/docs/cli-map.md +66 -48
  311. package/docs/conventions.md +12 -13
  312. package/docs/install.md +27 -28
  313. package/docs/migration-0.19.md +48 -0
  314. package/docs/verified-target.md +5 -5
  315. package/package.json +6 -3
  316. package/skills/distill/SKILL.md +1 -1
  317. package/skills/doctor/SKILL.md +13 -6
  318. package/skills/interview/SKILL.md +38 -0
  319. package/skills/search/SKILL.md +7 -4
  320. package/skills/setup/SKILL.md +14 -12
  321. package/skills/write/SKILL.md +10 -6
  322. package/assets/skills/link/SKILL.md +0 -30
  323. package/assets/skills/status/SKILL.md +0 -34
  324. package/dist/kernel/contract/contract-vault-fixture.d.ts +0 -45
  325. package/dist/kernel/contract/contract-vault-fixture.js +0 -53
  326. package/dist/kernel/contract/contract-vault-fixture.js.map +0 -1
  327. package/dist/kernel/contract/drift.d.ts +0 -6
  328. package/dist/kernel/contract/drift.js +0 -27
  329. package/dist/kernel/contract/drift.js.map +0 -1
  330. package/dist/kernel/contract/extract.d.ts +0 -37
  331. package/dist/kernel/contract/extract.js +0 -101
  332. package/dist/kernel/contract/extract.js.map +0 -1
  333. package/dist/kernel/search/morning-test-fixtures.d.ts +0 -7
  334. package/dist/kernel/search/morning-test-fixtures.js +0 -41
  335. package/dist/kernel/search/morning-test-fixtures.js.map +0 -1
  336. package/dist/mcp/note-write.js.map +0 -1
  337. package/skills/link/SKILL.md +0 -30
  338. package/skills/status/SKILL.md +0 -34
  339. /package/dist/{mcp → kernel/write}/note-write.d.ts +0 -0
@@ -10,7 +10,7 @@
10
10
  {
11
11
  "name": "oms",
12
12
  "description": "Oh My Second Brain convention layer for Obsidian vaults — capture, retrieve, and validate knowledge under a declared semantic convention.",
13
- "version": "0.18.3",
13
+ "version": "0.20.0",
14
14
  "author": {
15
15
  "name": "gobeumsu",
16
16
  "email": "gobeumsu@gmail.com"
@@ -37,5 +37,5 @@
37
37
  ]
38
38
  }
39
39
  ],
40
- "version": "0.18.3"
40
+ "version": "0.20.0"
41
41
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "oms",
3
- "version": "0.18.3",
4
- "description": "Oh My Second Brain convention layer for Obsidian vaults — seven shared skills and five MCP tools under a user-owned contract.",
3
+ "version": "0.20.0",
4
+ "description": "Oh My Second Brain convention layer for Obsidian vaults — six shared skills and four MCP tools under a user-owned contract.",
5
5
  "author": {
6
6
  "name": "gobeumsu"
7
7
  },
@@ -17,10 +17,9 @@
17
17
  "skills": [
18
18
  "./assets/skills/distill/",
19
19
  "./assets/skills/doctor/",
20
- "./assets/skills/link/",
20
+ "./assets/skills/interview/",
21
21
  "./assets/skills/search/",
22
22
  "./assets/skills/setup/",
23
- "./assets/skills/status/",
24
23
  "./assets/skills/write/"
25
24
  ]
26
25
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "oms",
3
- "version": "0.18.3",
3
+ "version": "0.20.0",
4
4
  "description": "Oh My Second Brain convention layer for Obsidian vaults — Codex native rules, skills, and MCP adapter.",
5
- "_note": "oms host install writes Codex MCP config and provenance, installs ~/.codex/rules/oms.md, and installs the seven shared skills under ~/.codex/skills/oms-*.",
5
+ "_note": "oms setup host install writes Codex MCP config and provenance, installs ~/.codex/rules/oms.md, and installs the six shared skills under ~/.codex/skills/oms-*.",
6
6
  "skills": "./assets/skills/",
7
7
  "mcpServers": "./.mcp.codex.json"
8
8
  }
@@ -4,6 +4,20 @@ Skills, agents, templates, and host guidance changes belong here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.20.0] - 2026-09-30
8
+
9
+ - **The doctor skill covers contract evolution.** It describes `evolve`, `evolve-verdict` and `revert-propose`, requires the maker's session on `evolve` and keeps the maker from judging its own request, explains the stage-2 refusal of an overlapping or drifting candidate, says a revert always requires owner approval, says the quorum is host-attested and cannot be verified by OMS, which is why autonomy is off by default, tells a host without independent evaluator subagents to stop and send the owner to `oms setup`, and sends `reclaim-evolution-lock` and `lineage-reanchor` to the owner's terminal.
10
+ - **The setup and interview skills cover folders and properties only.** The setup skill drops the template-interpretation steps (`--interpretations`, `observedHash`, `interpretation-required`, `interpretation-rejected`) and describes `oms setup extract --template <name>` as a scaffold preview; the interview skill drops the `interpretations` parameter. The write, search and doctor skills describe templates as live files in the template folder that scaffold new notes and are never judged.
11
+ - **The setup skill no longer describes a `template-tightened` refusal.** Reseal no longer refuses a template answered more strictly, because the judge never reads a template.
12
+
13
+ ## [0.19.0] - 2026-09-28
14
+
15
+ - **Breaking: six shared skills.** `write`, `search`, `interview`, `distill`, `setup`, and `doctor`. The `link` skill is folded into `search` (suggest) and `doctor` (check), the `status` skill into `doctor` `op: "status"`, and the new `interview` skill reads the pending questions without sealing. Every skill and host guidance file uses the 0.19 command spellings.
16
+
17
+ - Refresh the English and Korean README with an original, self-contained constellation SVG inspired by beomsukoh.com, compact feature cards, a four-step quickstart, and navigable reference sections. Keep contract and host enforcement boundaries explicit, and align the setup skill count with the current seven-skill registry. Runtime behavior is unchanged.
18
+
19
+ - **The `setup` skill reads each template itself before asking anything.** It now submits `{source, observedHash, fields, headings}` per template with `--interpretations`, handles the `interpretation-required` and `interpretation-rejected` results, and is told plainly that a field left out of an interpretation is a question the owner is never asked, and that `observedHash` must come from reading the bytes rather than from copying the hash OMS printed.
20
+
7
21
  ## [0.18.3] - 2026-09-26
8
22
 
9
23
  ## [0.18.2] - 2026-09-26
package/CHANGELOG-cli.md CHANGED
@@ -4,6 +4,40 @@ Changes to the `oms` command surface belong here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.20.0] - 2026-09-30
8
+
9
+ - **The `oms doctor` evolution usage text is built from one flag table.** Each leaf's flag and its placeholder (`<id>`, `<file|->`, `<digest>`) now live together in `src/cli/lineage-command.ts`, and the printed usage is unchanged.
10
+ - **`oms doctor evolve`, `evolve-verdict`, `revert-propose` and `reclaim-evolution-lock` drive contract evolution.** `evolve --maker-session <id>` opens one evolution request from the open gaps and seals nothing; the maker session is required. `evolve-verdict --verdict <file|->` submits one evaluator verdict as JSON and runs the seal-gate. `revert-propose --target <digest>` proposes a kept generation as a new forward candidate; a revert always requires owner approval, so it is approved or rejected at `oms setup`. `reclaim-evolution-lock` releases a stale evolution lock after the owner confirms at a terminal; `lineage-reanchor` now also asks the owner and is refused without a terminal. `oms doctor status` prints the `evolution` section.
11
+ - **`oms setup` reviews pending contract evolutions, and `oms setup --autonomy on|off` controls autonomous sealing.** Before the interview, each request awaiting the owner is shown with its loosening changes marked and is sealed only on an explicit approve; a reject closes it. Autonomy is off by default and turned on only at a terminal.
12
+ - **`oms doctor contract` suggests setting `templateFolder` for an older vault.** When an older (version 1 or 2) generation sealed templates and `.oms/settings.json` names no `templateFolder`, it lists `template-folder-unset: set "templateFolder" to "<folder>" in .oms/settings.json so templates scaffold new notes`, naming the folder only when every sealed template shares it. It is a suggestion only: nothing is written, and it appears only beside `legacy-template-constraints-ignored`, which already exits 1, so no vault's exit code changes.
13
+ - **`oms setup` / `oms interview` seal folders and properties only.** Neither lists, asks about, previews or seals templates any more; templates in the `templateFolder` scaffold new notes and are never judged. A sealed result's JSON carries `warnings` when the seal raised any, such as `CONTRACT_LEGACY_TEMPLATES_DROPPED` for a reseal that drops an older generation's templates, and no longer has a `templates` field. `--interpretations` is removed and is now an unknown argument (`CONTRACT_ARGS_INVALID`), and the `interpretation-required` and `interpretation-rejected` statuses are gone. `oms setup extract --template <name>` now previews what a live template would scaffold, printing `{status: "scaffold", name, source, folder, properties, headings}`, or `{status: "missing", template, remediation}` with exit 1 for an unknown name; it no longer prints a source hash.
14
+ - **`oms doctor contract` reports `legacyTemplates` instead of `templates`.** The status payload's `templates` field is replaced by `legacyTemplates`, the number of template constraints an older (version 1 or 2) contract generation still carries and the judge now ignores; a new contract generation reports `0`. A non-zero count is also listed as the finding `legacy-template-constraints-ignored: N` with `oms setup` as guidance.
15
+ - **`oms write` prints warnings and fixes.** After the JSON result, a saved write prints `[oms] warnings: [{field, kind}]` and `[oms] fixed: [{field, kind}]` to stderr when either is non-empty, and the receipt's `fixes` now lists the lossless fixes the write applied instead of dropped keys. A write with an unknown key or an out-of-rule value is saved as written rather than drafted; only unparseable frontmatter is drafted.
16
+ - **`oms search` stops looking for `--vault` after a `--` terminator.** A query such as `oms search --vault notes -- --vault elsewhere` searches `notes` for the text `--vault elsewhere` instead of reading the second `--vault` as the flag. `oms search --link <note>` refuses a `--` terminator with `SEARCH_ARGS_INVALID` rather than forwarding the tokens after it to link suggestion as flags.
17
+ - **`oms doctor gaps` reports open contract gaps and contradictions.** It prints the same read-only report as the MCP `doctor` `op: gaps`. It exits 1 only when the contract contradicts itself, the ledger cannot be read, or a ledger line is corrupt; open gaps are the ledger doing its job, and `ledger: "truncated"` is a warning that leaves the exit code alone. `oms write` follows the new write pipeline, so a write that only adds keys the frame has no place for is saved without them and reports the dropped fields in its receipt. `oms write --check` prints the `resolution` the write would apply.
18
+ - **`oms write` saves or drafts where it used to refuse.** Only a safety refusal denies a write, reported as `status: "denied"` with `refusals`. In a sealed vault, only a note whose frontmatter does not parse is kept as a draft and printed as `status: "drafted"` with a `draftRef`; like a denial it exits 1. Every other write is saved, with lossless fixes applied and the rest kept as written. A saved note's receipt lists the saved note's `warnings` and, as `fixes`, the `{field, kind}` of each lossless fix it applied, and `--check` prints `refusals`, `warnings` and `fixes`. `oms doctor contract` prints a `hint` with a transport failure when the installed guard needs `oms setup host sync`.
19
+ - **`oms doctor lineage-recover` and `oms doctor lineage-reanchor` repair the contract lineage.** They print the same result as the MCP `doctor` ops of the same name, and they take `--vault <path>` or resolve the vault the usual way. A `cwd`-inferred vault is rejected. A refused gap, a vault that is not sealed, or bad arguments exit 1. `oms doctor contract` now reports a `lineage` field: events, snapshots, snapshot bytes, and findings, each naming the repair it needs. It exits 1 when a finding needs attention. A store sealed before the lineage existed, a snapshot kept from a crashed seal, and a cut-short last line are reported but do not fail the check.
20
+ - **Lineage repair failures no longer leak paths or unknown codes.** When `oms doctor lineage-recover` or `oms doctor lineage-reanchor` fails, a diagnostic keeps the error's code only when it is a real `CODE:` prefix; any other failure, including a thrown non-Error value, reports `CONTRACT_LINEAGE_REPAIR_FAILED`. Absolute paths in the message, quoted ones with spaces included, are printed as `<path>`.
21
+ - **`oms search --context` refuses a `--` terminator.** Context retrieval takes only flags, and the terminator used to reach its flag parser as the token `--`, which failed with the unclear `unknown context flag --`. It now fails with `--context does not accept a -- terminator`, the same way `--link` does.
22
+
23
+ ## [0.19.0] - 2026-09-28
24
+
25
+ - **`oms write` takes `--if-match sha256:<rev>` and `--check`.** Overwriting an existing note without `--if-match` exits 1 with `WRITE_IF_MATCH_REQUIRED` and leaves the file unchanged; a stale revision reports the retryable `WRITE_TARGET_CHANGED`, and `--if-match` for a note that does not exist reports the retryable `WRITE_TARGET_ABSENT`. `--check` prints the frame, the note's current revision and any violations and writes nothing, not even an index row. Each flag may be given once, and `--if-match` requires a value. The receipt is the one MCP `write` returns.
26
+
27
+ - **`oms interview` continues an interrupted interview, and `--restart` starts over.** Every answer is logged beside the contract store, so closing the terminal mid-interview no longer loses the answers already given: the next `oms interview` replays them, says how many it continued with, asks only what is left, and reports any logged answer it dropped because its question changed. `--restart` logs the earlier run as abandoned and asks everything again. It still refuses without a TTY or under `OMS_NON_INTERACTIVE=1`, and a refused run logs nothing. A vault that was never sealed gets no `.oms/settings.json` until the seal; its answers are logged under a pending key outside the vault. `--vault --restart` is refused as a missing `--vault` value rather than read as a restart. `oms doctor contract` reports corrupt interview log lines by line number under `interviewLog`, exits 1, and repairs nothing. An unsafe entry in the state directory is reported as `STATE_DIR_UNSAFE` without echoing its path.
28
+
29
+ - **Breaking: the `oms` command surface is seven families.** `search`, `interview`, `write`, `setup`, `doctor`, `serve`, and the hidden `hook` replace the fourteen 0.18 families. A removed family (`note`, `link`, `status`, `contract`, `index`, `graph`, `host`, `model`, `package`, `bridge`) is not an alias: it exits 1, runs nothing, and prints its 0.19 spelling, for example ``[oms] Command `index` was removed in 0.19. Use `oms doctor sync-embeddings --mode sync|embed|repair`, `oms doctor cleanup`, or `oms doctor status`.`` `oms search <text>` takes `--mode`, `--context`, `--path`, and `--link`; `oms doctor` owns `status` (read-only), `contract`, `audit`, `link-check`, `sync-embeddings --mode sync|embed|repair`, `cleanup`, and `build-graph`; `oms setup` keeps the interview and gains the `extract`, `status`, `host`, `model`, `package`, and `bridge` leaves. `docs/migration-0.19.md` maps every 0.18 spelling, and `test/architecture/migration-table.test.ts` dispatches each row against the built CLI.
30
+ - **`oms doctor status --view status|collections|contexts` reaches the search-index views.** The 0.18 `oms index status` views were unreachable after the family was removed. `oms doctor status` now routes to them when `--view`, `--index` or `--collection` is given, stays read-only, and reports `No engine store` instead of creating one; an unknown view exits 1 with the usage line. Without those flags it still prints the vault health report. The dead `graph status` verb is gone: graph health is the `graph` section of `oms doctor status`.
31
+ - **`oms search --link` honours the resolved arguments.** The note path and `--json` are read from the resolved argv, so `oms search --link <note>` behaves the same wherever `--vault` appears.
32
+ - **`oms write <path>` writes a note from stdin through the same judge as MCP `write`.** It runs the same write pipeline, so a `cwd`-inferred target is refused, a violation exits 1 with `{field, kind}` violations and one guidance command and leaves the file untouched, and an allowed write prints the same receipt as the MCP tool.
33
+ - **`oms interview` runs the interactive interview.** Like `oms setup`, it refuses without a TTY or under `OMS_NON_INTERACTIVE=1`.
34
+
35
+ - **`oms search --path <rel>` reads one note exactly, without opening the index or loading a model.** It is the normalization-insensitive answer to the `oms note get` gap: an NFC request finds an NFD-named note on macOS and Linux alike. The output is the document shape `{available, documents: [{target, path, content, revision}]}`; a missing, ambiguous or escaping path prints `available: false` with a reason and exits 1. `--path` must come first and is mutually exclusive with `query`, `context`, `--mode` and every other search argument; a `--path` later in the arguments is refused. `oms search query` now accepts a `--` terminator, after which every token is query text, so `oms search query -- "--path"` searches for the literal text.
36
+
37
+ - **Every `oms` command starts faster, because the entrypoint loads only the command that runs.** `oms` used to import every command module, the MCP and HTTP servers, and the search engine with its native SQLite modules before it dispatched, so even `oms --version` paid about 400 ms. Each command family is now imported when it is dispatched, and `search` loads the engine only for `query`, `context` and `index`. `oms search --path` loads 12 modules and no native dependency; on the measurement machine its p50 fell to about 88 ms, `oms --version` fell from about 410 ms to 90 ms, and `note get` and `search query` fell by roughly 300-400 ms and 100-200 ms. Output and exit codes are unchanged. See `docs/measurements/latency-search-path.md`.
38
+
39
+ - **`oms setup` takes the template interpretations an agent read.** A vault with templates now ends `interpretation-required`, listing every template source with the `sourceHash` OMS computed, until `--interpretations <file|->` supplies what each one declares; the file stays outside the vault like the answers file. `interpretation-rejected` reports the templates whose interpretation the owner did not confirm, and it is not a refusal — a corrected interpretation may be submitted. `oms contract extract --template <path>` no longer prints fields and headings, since OMS does not parse template text; it reports the source and the hash an interpretation must match.
40
+
7
41
  ## [0.18.3] - 2026-09-26
8
42
 
9
43
  ## [0.18.2] - 2026-09-26
@@ -4,6 +4,59 @@ Domain logic changes belong here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.20.0] - 2026-09-30
8
+
9
+ - **The vault contract can evolve from its write gaps, and only one seal-gate may seal it.** `src/kernel/evolution/` turns the open gaps into one maker-drafted evolution request (`evolve.ts`, `maker.ts`) with a pinned candidate, a nonce and three evaluator slots, held under an evolution lock with its own journal (`events.ts`). `seal-gate.ts` is the only path that seals an evolved candidate; the only other product caller of `sealContract` is the interview. Stage 1 (`stage-mechanical.ts`) judges the vault's existing notes against the candidate: any new refusal rejects it, and the warning delta is its score, reported on every request. A candidate that raises warnings, or that loosens the contract (an added allowed value or folder, a widened type, a removed `required`, as classified by `mutation-direction.ts`), waits for the owner. Stage 2 (`stage-semantic.ts`) is a hard gate too: a touched entry whose meaning overlaps an existing one (similarity 0.9 or more) or a candidate that drifts more than 0.3 from the first sealed generation is rejected, so `evolve` issues nothing (`EVOLUTION_STAGE2_REFUSED`) and the router and the autonomous seal-gate refuse it and journal `request.rejected`. Stage 3 is a 2-of-3 quorum of evaluator verdicts from an injected `VerdictProvider`, waiting for all three bound verdicts, and its reject is effective: the same request never seals on a rerun. A provider without subagents is `EVALUATOR_CONSENSUS_UNAVAILABLE` before anything is asked or written. `evolve` requires the maker's session id (`EVOLUTION_MAKER_SESSION_REQUIRED`), and a verdict whose session repeats another evaluator's or the maker's is discarded. The quorum is host-attested: OMS cannot verify that the evaluators were independent subagents, so autonomy is off by default and an autonomous seal records `quorum: "host-attested"` in its journal event and lineage event. An autonomous seal needs the opt-in policy (`policy.ts`, off by default, turned on only interactively, limits of 1 a day and 3 a week that can only be lowered), the quorum, a warning delta of 0 or less (a negative delta qualifies) and the rate limit (`rate-limit.ts`). The owner decides approve or reject through `human-approval.ts` and `seal-gate-human.ts`, which seal through the same gate. `revert.ts` proposes a kept generation as a new forward candidate, read only from its snapshot and projected to the version 3 form. A revert always requires owner approval: it has no maker, so no host quorum can bind to it, and every revert (tightening, neutral or loosening, with autonomy on or off) goes to `awaiting-human` for the owner to approve at `oms setup`; a verdict on it is refused with `EVOLUTION_REQUEST_CLOSED`, and the owner's seal records the proposer as `owner`. Stage 1 and stage 2 both run when a revert is proposed, so a revert that adds a refusal (`EVOLUTION_REVERT_REFUSED`) or overlaps or drifts past 0.3 (`EVOLUTION_STAGE2_REFUSED`) proposes nothing. The `maker-unknown` discard applies only to `evolve` requests. The judge is unchanged.
10
+ - **`doctor` runs the evolution ops and reports evolution in status.** `src/kernel/doctor/evolution-ops.ts` runs `evolve`, `evolve-verdict`, `revert-propose` and `reclaim-evolution-lock` on a verified target, each checking a postcondition read back from the store (the request state, the pinned candidate digest, the slot used exactly once, the linked generation unmoved or equal to the sealed candidate, the lock gone). A postcondition that does not hold throws `EVOLUTION_POSTCONDITION_FAILED`, or `EVOLUTION_POSTCONDITION_FAILED_AFTER_SEAL` naming the sealed digest and generation when the seal already happened. `reclaim-evolution-lock` needs the owner at a terminal. `src/kernel/doctor/evolution-status.ts` gives `doctor status` an `evolution` section: journal counters, requests awaiting the owner, the autonomous budget left today and this week, and whether the lineage has a gap, and `quorum: "host-attested"`, how an autonomous seal's quorum is attested. It is read-only and `null` for an unsealed vault.
11
+ - **The seal boundary is enforced.** `test/architecture/write-evolution-boundary.test.ts` now fails when a new module names `sealContract`, a test fixture is reachable from an entrypoint, anything but `src/cli/evolution-approve.ts` imports the approval modules, the approval module reads stdin or seals, the maker or evaluator stages reach the write kernel, or `kernel/contract` imports `kernel/evolution`. Non-literal dynamic imports in `src/` fail the gate.
12
+ - **An empty or comment-only frontmatter block is empty frontmatter, not malformed YAML.** `parseNote` now reads `---` followed directly by `---`, or a block that holds only comments, as a note with no properties instead of a `yaml-syntax` draft. A missing required property whose rule fixes its value, a new note's date defaults and a template's frontmatter defaults are now filled into such a block, and the inserted lines sit on their own line before the closing fence. Other readers change with it: building the axes store no longer fails on such a note, a live template whose frontmatter is `---` then `---` now loads and can add a template choice for its folder, and `oms doctor audit` no longer reports these notes as `yaml-syntax`. The block closes at the first fence, so in `---`, `---`, `k: v`, `---` the `k: v` line is body text, as Obsidian reads it; a test pins this.
13
+ - **A discarded coerce pass no longer uses up a fix pass.** `resolveTiers` in `src/kernel/write/ambiguity.ts` counts only accepted passes against its three fix passes, and a separate hard cap of twelve attempts still bounds the loop.
14
+ - **The interview types a template key Obsidian has not typed from its value.** A key set only by the live templates is offered as a `list` (a `-` item block or `[...]`), a `date` (`YYYY-MM-DD` or `{{date}}`) or a `datetime` (`YYYY-MM-DDTHH:mm` or its `{{date:...}}` format) when its value clearly is one, and as `text` otherwise or when templates disagree. An Obsidian property type still wins.
15
+ - **Scaffolded headings keep the note's line endings.** Conform appends a template's missing headings to a CRLF note with CRLF, not LF.
16
+ - **Scaffolding skips Templater tags, and the interview offers the keys templates set.** Conform never runs Templater, so a template value or heading that holds a `<% ... %>` tag is left out of a new note, the same as an unfilled `{{...}}`, instead of being saved as literal tag text. A multi-line template value is inserted with the note's own line endings, so a CRLF note stays CRLF. The interview now also offers each frontmatter key the templates in the template folder set as a property to register, typed as Obsidian types it or else `text`, so a vault whose `status` appears only in templates is asked about it; templates themselves are still never asked about or sealed. `oms doctor contract` adds the finding `template-folder-unset` when an older generation sealed templates but `.oms/settings.json` names no `templateFolder`, suggesting the folder those templates share; it writes nothing.
17
+ - **New contract generations no longer store templates; older generations still load and their template constraints are reported, not enforced.** The store now writes manifest version 3, which lists only `folders.json`, `properties.json` and an optional `declined.json` (`{version: 2, folders, properties}`); no `templates/` directory is written. Version 1 and 2 generations stay readable permanently and their digests are unchanged: their templates are projected into `view.legacy.templates` on read and never persisted. `VaultContract` no longer carries templates (the old shape is `LegacyTemplateContract`), the judge never enforces them, and template drift detection (`drift.ts`) is removed. `oms doctor contract` reports `legacy-template-constraints-ignored: N` for a head that still carries them, and a revert or reseal writes a new version 3 head rather than rewriting an old generation. Neither the judge nor the write decision reads an older generation's templates: their required properties, narrowed rules and required headings no longer shape a verdict, a fix or a contradiction. A newly sealed vault carries no templates; templates scaffold from `templateFolder` instead (see below).
18
+ - **Unknown keys and out-of-rule values are saved as written and recorded; lossless type and spelling fixes are applied with the original kept in the ledger.** `src/kernel/write/coerce.ts` fixes a new warning only when the contract reads the value the same way: a number or checkbox written as a string, a scalar for a list, a midnight datetime for a date, a number for text, an allowed value that differs only in Unicode form, spacing or case and matches exactly one allowed value, and a missing property whose rule fixes its value (or, on a new note, a date or datetime default). The fixed note is judged again, `decideWrite` returns it as `fixedContent`, and each fix is recorded as a `fixed` gap whose `wanted` is the value as written. Every other new warning, including an unknown key, an unmatched allowed value, a pattern, range or count miss, and an unregistered folder, is saved as written and recorded as a `kept` gap. Only frontmatter that does not parse is kept as a draft. The key-dropping repair is removed (`DROPPABLE`, `droppableKeys`, `dropFrontmatterKeys`, `requiredBy`), so nothing a note says is silently deleted. Coerce fills a required property whose rule fixes its value when it is missing, and conform adds a non-required default whose only rule fixes its value on a new note; an untyped property takes that value as a single value, not a list. A fix changes only the bytes of the fixed value, so every other key keeps its exact spelling, and a number is fixed to text only when its source text is exactly that number (`01234`, `1.0`, `0x1F` and integers past 2^53 are kept, and the ledger records them as written). A tagged or anchored value is kept as written, and a fix counts only when the judged note no longer reports it. The guard hook never rewrites a tool call: a fix it would make stays a warning and is recorded as `kept`.
19
+ - **A reseal drops legacy templates with a warning.** Resealing a version 1 or 2 head, interactively or through the agent (`nonLoosening`) path, is not refused over its templates: it seals, shows and returns the warning `CONTRACT_LEGACY_TEMPLATES_DROPPED: N template(s) sealed by an older generation are not carried forward; templates now scaffold new notes from the template folder and are never judged`, and records `droppedLegacyTemplates: N` on the `proposed` interview event. The seal guard and the unsafe-pattern prompt look only at properties, so a template never refuses a seal or asks a question.
20
+ - **Templates scaffold new notes from `templateFolder`, and are never sealed or judged.** `src/kernel/write/live-templates.ts` reads the Markdown templates in the `templateFolder` recorded in `.oms/settings.json` at write time, so an edited template takes effect on the next write without a reseal. The write pipeline picks the template the write names; otherwise the one template whose basename or `folder:` key matches the target folder; when two or more match it records a template choice and scaffolds nothing; when none match it scaffolds nothing. A named template that is not there scaffolds nothing and is reported as a `template-missing` conform entry, and the write proceeds. On a new note, conform adds the template's frontmatter defaults where the note has no value and appends its missing headings; the note's own values win, and a scaffolded key outside the property pool is saved and warned as `unknown-property` like any other. The frame's `required` comes only from the property contract. The interview asks about folders and properties only; `templateFolder` stays in settings. Retrieval's template axis and template-source paths now come from the live templates of a sealed vault. The template-interpretation input (`interpretations`, `observedHash`) and its modules are removed, and the sealed result no longer carries `templates` or `removedTemplates`.
21
+ - **The judge no longer compares a note with its previous version or a selected template; `template-mismatch`, `folder-mismatch` and `heading-missing` are retired.** `judge` in `src/kernel/contract/judge.ts` now depends only on the contract, the note path and its frontmatter: `JudgeInput` drops `selectedTemplate` and `previousContent`, and `decideWrite` drops its template argument for judging. A `template:` frontmatter key is judged like any other property. The unsubstituted-variable check no longer reads the note body; only frontmatter values are checked. What an edit newly breaks is still decided in `decideWrite`, which judges the note on disk as a baseline and records only warnings the edit added. Reseal loosening follows: a template answered more strictly or more loosely, or a template whose apply folder changes or overlaps another, is no longer reported, since the judge never reads a template. Removing a sealed template or moving its source is no longer refused as loosening either: a reseal writes a version 3 head, which stores no templates, so every legacy template is dropped with a warning (see below).
22
+ - **An exact read refuses a file larger than 16 MiB, and a directory is `READ_EXACT_NOT_FILE` on every platform.** `readExact` in `src/kernel/search/read-exact.ts` checks the size of the opened handle and refuses a file over `READ_EXACT_MAX_BYTES` with `READ_EXACT_TOO_LARGE` before reading it. The read itself stops at the same cap, so a file that grows after the check cannot exhaust memory. An `EISDIR` raised when opening or reading, as Windows does for a directory, is now `READ_EXACT_NOT_FILE` instead of an I/O error.
23
+ - **A write that only misses the frame is saved, and the gap it exposed is recorded.** `src/kernel/write/ambiguity.ts` sorts a judged write into tiers. An accepted note is saved as written. When more than one sealed template applies to the folder and none was chosen, the recommended one is recorded as a `choice` gap. A new warning is fixed when the fix is lossless and recorded as a `fixed` gap; every other new warning, including a missing property, an unregistered folder and a field the contract contradicts itself about, is saved as written and recorded as a `kept` gap. Only frontmatter that does not parse is kept as a draft beside the ledger, recorded as a `no-fit` gap. The judge is never bypassed: a fixed note is judged again, and nothing is saved that it refuses.
24
+ - **Gaps live in an append-only ledger outside the vault.** `src/kernel/contract/gap-ledger.ts` appends `{id, at, notePath, noteRevision, contractRevision, axis, kind, chosen, wanted, reason, draftRef?}` events to `<root>/.<id>.state/gaps/events.jsonl` through the state-dir helpers, so it inherits their symlink, ownership and 0600 checks. A cut-short or malformed line is skipped, reported by line number and never rewritten, and the next append starts on a fresh line. The trailing-newline check reads one byte, so an append costs the same however large the ledger grows. A resolved gap stays closed, and replaying the ledger always gives the same open set. A template choice gap (②) has a deterministic id derived from the note path, the sorted candidates and the contract revision, and is appended once per ledger: repeating the same ambiguous write records nothing new, and a resolved choice stays closed until the contract or the candidates change. The `.seen` markers that remember a choice are named after the current ledger by a hash of its first line, so after the ledger is moved aside the next repeat of a choice lands in the fresh ledger, and the first append to it deletes the old markers. A marker also records where its event starts, so a choice that has fallen out of the 16 MB read window is appended again on its next repeat. Markers written before this change use the old `<id>.seen` name and no longer count, so each open choice is appended once more on its next repeat; a resolved choice stays closed because the fold keeps its `resolved` event, and the old markers are deleted at the next rotation. Writing a marker is best effort, the same as pruning one: it runs only after its event is fsynced, so a failed write (a state-dir security check, a full disk) no longer rejects `recordGaps` and never loses the event, only re-appending that choice once more on its next repeat. Appends never refuse on size. A read past 16 MB reads only the newest 16 MB from its first whole line and returns `truncated: true`, keeping corrupt line numbers counted from the start of the file; to rotate, move `events.jsonl` aside and the next append starts a fresh ledger. Drafts are 0600 files named `draft-<uuid>.md`, and a ref of any other shape is refused.
25
+ - **The write pipeline reads the seal state once.** The judge, every gap a write records and the receipt all name the one `contractRevision` derived from that read, so a seal that lands mid-write cannot split a write across two revisions. `src/kernel/contract/revision.ts` computes the revision; it is the one place to switch to the seal manifest digest later. A written receipt adds `gaps: [{id?, axis, kind, field}]`. When the note was saved but its gaps were not recorded, the receipt still lists every dropped field without an id and adds `gapLedger: "failed"` (the append failed) or `gapLedger: "unavailable"` (the vault has no id to key a ledger). `check` now runs the same ambiguity resolution and reports `resolution: {action, gaps, wouldDraft, precondition?}`, fields only, while still writing no note, draft or ledger entry. The `ifMatch` check runs before a draft is kept, so a stale or missing `ifMatch` keeps no draft and records no gap. `check` folds that in: it reports `precondition: "if-match-required"`, `"changed"` or `"absent"` with `wouldDraft: false` when the write would stop there first, and a draft whose gaps could not be recorded still returns its `draftRef`. `oms doctor gaps` reports `ledger: "truncated"` as a warning when it read only the newest window.
26
+ - **A `count` rule bounds how many values a property holds.** `{kind: "count", min?, max?}` counts list members, and a scalar counts as one. An empty value counts as absent, so only `required` reports it. Narrowing a count is a tightening; widening, unbounding or removing one is reported as loosening. The contract store accepts a `count` rule whose bounds are whole numbers of zero or more and that carries no other key; any other shape still reads as unreadable and fails closed.
27
+ - **Contract mutations and contradictions are pure functions.** `src/kernel/contract/mutation.ts` applies `ADD`, `MODIFY` and `REMOVE` mutations on the folder, property, template and rule axes without changing its input. It refuses the whole list at the first conflict with `CONTRACT_MUTATION_CONFLICT`, naming the index, axis, key and kind, including when `before` does not match the parent. `src/kernel/contract/contradiction.ts` finds rules no value can satisfy: inverted count or range bounds, an empty allowed set, a fixed value outside the allowed set, and a template that requires an unregistered property. It reports each one by field and kind, never by value.
28
+ - **Only safety refuses a write; every other finding is a warning.** The judge's `Verdict` now splits findings into `refusals`, `warnings` and `fixes`, and `ok` means no refusals (`violations` still lists the refusals for older callers). Refusals are control paths, unsafe paths, paths outside the vault, unsupported input, and a tampered contract. A tampered contract is now its own kind, `contract-tampered`, and is reported only when `.oms/settings.json` names a vault id that differs from this machine's index. A missing or invalid `.oms/settings.json`, an index without a store, a damaged store, a seal that cannot be resolved, or an existing note that cannot be read no longer refuses: the write goes ahead with a `contract-unreadable` warning whose guidance is `oms interview`. An open vault writes with a `contract-open` warning. The shared `decideWrite` in `src/kernel/contract/judge-write.ts` decides MCP and hook writes alike. A warning counts as new only when the note's previous content did not already have it; in a sealed vault a write with new warnings on the frame is kept as a draft and its gaps recorded, a write that can only drop keys it added is still saved without them, and a contradiction or a draft that cannot be kept saves the note as written and records the gap. A written receipt adds `warnings` (the full set on the note as saved) and `fixes` (the keys a repair dropped). `oms doctor contract` adds a `hint` to its transport failures when the guard dropped a malformed judge answer, telling the user to run `oms setup host sync`.
29
+ - **Every seal is recorded in a contract lineage, and every sealed generation is kept as a snapshot.** `src/kernel/contract/lineage.ts` appends one event per seal to `<root>/.<id>.state/lineage/events.jsonl` under the seal lock. Each event names the generation it replaced (`parentDigest`) and the one it installed (`digest`). Both are the sha256 of the generation's exact `manifest.json` bytes (`src/kernel/contract/digest.ts`), so neither depends on the store sequence. `src/kernel/contract/generation-snapshot.ts` writes each sealed generation once to `generations/<hex>/` and never changes or removes it. Two generations with the same manifest share one snapshot. The store still keeps only N and N-1, and the snapshots are how an older generation is read back. A seal now publishes its snapshot, swaps the link, appends its lineage event, and only then writes the index and runs the GC. A failed append throws `CONTRACT_LINEAGE_APPEND_FAILED` and skips both. A caller can pass `expectedParentDigest`: if the linked generation no longer has that digest, the seal aborts with `CONTRACT_SEAL_CHANGED`, which also catches a replacement at the same sequence. When the lineage does not end at the linked generation, the seal records an anchor event before its own. That anchor is `bootstrap`, `unrecorded-seal`, `seq-restart` or `gap-anchor`. Under `lineageGapPolicy: "refuse"` a gap instead throws `CONTRACT_LINEAGE_GAP` and nothing is written. The first seal on an existing store bootstraps the lineage from the retained N and N-1. **Generations N-2 and older that the GC removed before that first seal cannot be recovered**: the lineage starts at the oldest generation still on disk.
30
+ - **A lineage whose last line was cut short is repaired on the next append.** The cut line is reported as `lineage-truncated-tail`, and a read ignores it. The next append truncates the file back to the last whole line before it writes, so the partial line is dropped instead of being joined to the new event. Before this fix, the new event was glued to the fragment and both became unreadable. `src/kernel/contract/lineage-health.ts` reports the lineage read-only: events, snapshots, snapshot bytes, and findings, each with the repair it needs or none.
31
+ - **The contract revision is now the seal's manifest digest.** `contractRevision` returns the manifest digest of the generation the contract was read from, which is the digest the lineage records and the name of its snapshot, so a receipt or gap names a revision you can look up in the lineage. A resealed but unchanged contract keeps its revision. A sealed view built in memory, with no generation behind it, still falls back to the digest of the contract itself.
32
+ - **`lineage-recover` finishes a first seal whose lineage append failed.** When the append fails on a vault's first seal, the link is already swapped but `index.json` is never written, so the vault reads as `store-without-index` (or `vault-moved`). Recovery used to refuse that vault as not sealed. It now appends the missing lineage events and then writes the index entry, both under the seal lock, so the vault reads as sealed again. A vault whose store link is missing is still refused with `CONTRACT_NOT_SEALED` and gets no index entry. A `vault-moved` vault whose original path still exists is a copy sharing the vault id, so recovery refuses it with `CONTRACT_VAULT_ID_SHARED` instead of claiming the id, as the interview already does. The recovery postcondition, that no lineage finding needing repair remains and the vault reads as sealed, is now checked under the same lock, so a concurrent seal cannot change what it verifies. When it fails, the repair throws instead of returning a receipt.
33
+ - **The interview keeps the template folder when the lineage append fails.** The seal itself landed, so `.oms/settings.json` still records `templateFolder` before `CONTRACT_LINEAGE_APPEND_FAILED` reaches the caller (if that settings write also fails, the append failure is still what is thrown), and `lineage-recover` completes the rest. A seal that fails before the link swap still writes nothing.
34
+ - **A seal lists snapshots by name alone.** `snapshotDigests` reads `generations/` with one `readdir` and no per-snapshot size walk, so seal cost no longer grows with snapshot bytes. `snapshotInventory`, with sizes, is used only by the doctor.
35
+ - **A pending interview log is read in the order its move gives it.** When a vault has an id and a pending log is still beside the id log, `vaultLog` in `src/kernel/contract/interview-resume.ts` used to put the pending events first, while `migrateInterviewLog` appends them after the id log. Both now put the id log first and the pending events after it, so resuming an interview replays the same events before and after the move. When a cut-short move already copied some pending events, `vaultLog` lists them once, as the retried move will leave them.
36
+ - **Retrying a cut-short pending-log move no longer copies events twice.** `migrateInterviewLog` in `src/kernel/contract/interview-log.ts` copies the pending events and then removes the pending log. A crash between the two used to copy every event again on the next run. It now skips the pending events that already end the id log. It matches on their time, type, question and payload, not on `seq`, because the move renumbers `seq`.
37
+ - **The proposal records the template folder chosen in its run.** The `proposed` interview event now carries `templateFolder` when this run chose one, so a retried seal can save a folder the first seal did not reach.
38
+ - **Correction to the 0.19.0 notes on the interview log.** Those notes say readers order interview events by line, not by `seq`. Events are read in line order, but `confirmedProposal` compares `seq` to decide whether a confirm came after the latest proposal. So `seq` is trusted within one writer's run and not across concurrent writers. The log's header comment now states this. It also lists the log's known limits: each append rereads the whole file (O(n²) over a run), a log over 16 MiB is refused on read and append and nothing rotates it, and there is no cross-process lock for appends or for the pending-log move.
39
+ - **Interview log line numbers now point into the right file.** `vaultLog` reports unparsable lines per file: `corrupt` for the log under the vault id and `pendingCorrupt` for a pending log kept before the first seal, each numbered as in its own file, the way `oms doctor contract` reports them. Before, both files' line numbers were merged into one `corrupt` list, so a number could name a line in either file. `resumableIO` passes both lists through, and the unreadable-line warning of `oms interview` and `oms setup` still counts both.
40
+ - **A refused revert says how to restore a drifted generation.** When `revert-propose` refuses a target that drifts more than 0.3 from the first sealed generation, the message now ends `nothing was proposed. A revert cannot restore it, so reseal that contract with \`oms setup\``. Only a human seal skips stage 2, so such a generation can only come back through the interview. The refusal code is unchanged.
41
+ - **A makerless evolve no longer seals autonomously.** An evolve request recorded before the maker session became required names no maker, so its quorum cannot exclude the proposer and the lineage could not name who proposed it. An autonomous seal of such a request is now refused with `EVOLUTION_MAKER_SESSION_REQUIRED`, nothing is sealed, and the request stays open; an owner can still approve it in a terminal, where it is recorded as proposed by `owner`. Reverts and human seals behave as before.
42
+ - **Evolution internals are simpler, with no change in behaviour.** `runEvolutionOp` in `src/kernel/doctor/evolution-ops.ts` dispatches its four operations through a `switch` instead of a nested ternary, and its test-only hooks (`afterSeal`, `afterPropose`) are declared in their own `EvolutionOpsTestHooks` interface, and its internal dispatcher takes one object instead of seven positional arguments. The seal-gate's proposer is the maker's session id or `owner`, without a fallback no request can reach. ADR-007 §5 has an amendment naming the 0.19 `write` input (`ifMatch`, `check`) and its receipt, pointing to `docs/cli-map.md`.
43
+ - **Test fixtures no longer ship in the package.** `legacy-store-fixture`, `contract-vault-fixture` and `morning-test-fixtures` moved from `src/kernel/` to `test/fixtures/`, so they are no longer compiled into `dist/` or published. An architecture test fails if a fixture module appears under `src/` again, and `npm run lint` now typechecks `test/fixtures/*.ts` through `tsconfig.fixtures.json`, since those modules left the `src/` build.
44
+
45
+ ## [0.19.0] - 2026-09-28
46
+
47
+ - **One write pipeline frames, conforms, judges, writes and indexes a note.** `runWritePipeline` in `src/kernel/write/pipeline.ts` replaces `verified-write`: it admits and resolves the target, builds the frame the note is judged in (`frame.ts`: sealed folder meaning, properties, the chosen template and its defaults), applies only mechanical fixes (`conform.ts`: `{{title}}`, `{{date}}` and `{{time}}` variables, date and datetime defaults on a new note, and the chosen template's missing headings), then runs the unchanged judge. Conform adds only that mechanical skeleton and those defaults: it never changes a value and never supplies a property the contract or a template requires (a date default is skipped for any name a template lists in `requiredProperties`, every template's when none is chosen), so a missing required property is still refused. The heading skeleton does, by design, satisfy a template's required headings, and inserted lines keep the note's CRLF or LF line endings. Overwriting an existing note needs `ifMatch`, the `sha256:` revision of its current bytes; without it the outcome is `if-match-required`, and a revision that no longer matches, a note that vanished, or an `ifMatch` sent for a note that does not exist (`absent`, retried without `ifMatch` to create it) is a retryable `retry`. `check` stops after the judge and reports the frame, the revision and the violations without touching disk. A written note returns the receipt built by `receipt.ts`, `{ok, path, revision, contractRevision, index: {keyword, vector}, conformed, missingDefaults}`, and `writePayload` in `payload.ts` shapes every outcome for both surfaces. `src/kernel/engine/index-update.ts` replaces the note's keyword rows in an existing engine store in the same call and queues its vectors in `engine_dirty`; it never creates a store, and doctor `sync-embeddings` drains the queue, reporting `queue: {drained, pending}`.
48
+ - **`TemplateContract.meaning` records what a template is for, and the seal manifest is version 2.** The field is optional; a version 1 manifest still reads, so an existing seal needs no reseal.
49
+
50
+ - **The interview keeps an append-only log beside the contract store, so an interrupted interview continues where it stopped.** `src/kernel/contract/state-dir.ts` owns `<root>/.<id>.state/{interview,evolution,generations}`: it sits next to the `<id>` link, never inside a `.<id>.<seq>` generation, and matches none of the names a seal collects, so a seal neither lists nor removes it and losing the link leaves the log in place. Every component is lstat-checked before and after creation (not a symlink, a directory owned by this user, not group- or other-writable), directories are created 0700 by chmod through a handle opened with `O_DIRECTORY | O_NOFOLLOW`, so a directory swapped for a symlink is refused rather than having its target's mode changed, and a file is opened with `O_NOFOLLOW | O_NONBLOCK` and checked through its handle to be a regular 0600 file; a symlink, FIFO, socket or foreign-owned entry is refused with `STATE_DIR_UNSAFE` and left exactly as found, without blocking; the message names the fix (`chmod go-w <path>` for a shared-writable entry, `chown` or removal for one owned by another user). Two appends that create the directory at the same moment no longer fail with `EEXIST`: the one that loses the race checks the directory the other created. `interview-log.ts` appends `asked`, `answered`, `proposed`, `sealed` and `abandoned` events as JSON lines. Appends are serialized within a process only, not across processes: `seq` is one more than the highest in the file, so two processes appending at once may write the same `seq`, and readers order events by line, not by `seq`. A truncated or malformed line is skipped and kept, and `contractDoctor` reports it read-only as `interviewLog: {corrupt, pendingCorrupt, unreadable}` with line numbers. Before the first seal a vault has no id, and the interview writes nothing into it: its log lives under a pending key derived from the vault's real path, `.pending-<sha256>.state`, and moves into `.<id>.state` when the seal mints the id, keeping any events already there. `interview-resume.ts` replays the answers logged since the last seal or restart, but only while the question still reads the same (its digest covers the prompt, options and default), and reports the rest as drift so they are asked again. An interrupted run that is resumed seals byte-for-byte the contract an uninterrupted run seals. `runInterview` takes a `record` hook, records each question as the terminal asks it (the MCP tool, which lists questions, does not) and the proposal digest before the seal question. After the seal it persists the template folder to `.oms/settings.json` first and then records `sealed` best-effort: a failed record leaves the seal in place and returns the warning `INTERVIEW_LOG_UNRECORDED`.
51
+
52
+ - **Deny guidance and the surface registry use the 0.19 spellings.** `GUIDANCE` now names `oms doctor contract`, `oms doctor contract --fix`, `oms doctor status`, `oms setup host sync`, and `oms setup`. The harness registry lists six skills, four MCP tools, and seven CLI families with `hook` hidden. `writePayload` in `src/kernel/write/payload.ts` builds the one payload shape that MCP `write` and `oms write` both print, so the two surfaces cannot drift.
53
+
54
+ - **`readExact` reads one note by its vault-relative path without the engine.** `src/kernel/search/read-exact.ts` matches each path segment against the directory listing (exact spelling, then NFC, then a single NFC-equal entry), so a note saved with an NFD Hangul name is found from its NFC spelling on every filesystem, not only where APFS happens to fold the two. It refuses `..`, absolute paths, a path ending in a separator, and symlinks that resolve outside the vault, while names that merely begin with two dots such as `..notes/x.md` read normally. Each directory is resolved and checked against the vault before it is listed, so a symlink out of the vault reports an escape whether or not the name behind it exists. The note is opened once, without following a final symlink and without blocking, and that one handle is checked to be a regular file and read, so a FIFO is refused as not a file instead of hanging. It names a missing note plainly, returns the on-disk spelling as a POSIX path and a `sha256:` revision of the bytes, and imports nothing from the engine or a native backend; `test/architecture/read-exact-isolation.test.ts` walks its import graph to keep it that way. `src/kernel/text/nfc.ts` provides the `toNfc` and `nfcEquals` helpers it uses.
55
+
56
+ - **A seal refuses when the template sources moved under the interview, and folder scope is injective.** The seal re-enumerates the template sources under the seal lock, before anything is written, and compares them with the snapshot the questions were built from; an added, removed or changed source aborts the seal with nothing sealed, instead of sealing stale bytes and reporting drift afterwards. A scoped template name now escapes each path component, so `a/b/meeting.md` and `a__b/meeting.md` no longer claim the same name and neither becomes unsealable; `rekeySealedTemplates` uses the same encoding and is idempotent. The condition-4 gate that keeps the write-checking judge away from the interpretation modules walks the TypeScript AST instead of one import regex, so a side-effect import, a re-export barrel and a literal dynamic `import()` are all followed.
57
+
58
+ - **OMS no longer reads template text; an agent's interpretation is the input.** The deterministic pre-analysis is gone, along with the in-band sentinel token it substituted for each template variable: a template can be Templater JavaScript, can put a variable where YAML expects a key, and can carry no frontmatter at all, so no mechanical reading of it was trustworthy. The interview now enumerates the template sources, computes each one's digest, and takes a submitted interpretation of each; a submission that names an unknown source, omits one, or carries an `observedHash` that is not the source's current digest is refused, and the digest the contract stores is always the one OMS computed. Template identity now carries the source's folder scope, so two templates of the same file name in different folders no longer collide, and a contract sealed under bare file names is rekeyed by source path in the same change rather than reading as removed. The first seal asks the owner to confirm each interpretation before any question is built from it, because an interpretation that leaves a field out is a question never asked; a declined interpretation seals nothing and can be resubmitted. Interpretation stays a seal-time cost: the write-checking judge does not reach it.
59
+
7
60
  ## [0.18.3] - 2026-09-26
8
61
 
9
62
  ## [0.18.2] - 2026-09-26
package/CHANGELOG-mcp.md CHANGED
@@ -4,6 +4,31 @@ MCP server tools and resources belong here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.20.0] - 2026-09-30
8
+
9
+ - **The `status` write posture is chosen by a plain `if` chain.** `writePosture` in `src/mcp/tools/status.ts` replaces a nested ternary and returns the same four strings.
10
+ - **The MCP server's schema validator no longer pulls a vulnerable `fast-uri`.** The lockfile moves the transitive `fast-uri` (via `@modelcontextprotocol/sdk` → `ajv`) from 4.1.4 to 4.2.1, fixing GHSA-hrr3-gc8f-f4qj and GHSA-jvvf-x445-j334. No behaviour changes.
11
+ - **`doctor` gains `op: "evolve"`, `"evolve-verdict"`, `"revert-propose"` and `"reclaim-evolution-lock"`.** They are repair ops, so each requires a verified target and returns a receipt whose postcondition is read back from the store. `evolve` requires `makerSessionId` in its schema (`EVOLUTION_MAKER_SESSION_REQUIRED` without it) and returns the request id, nonce, digests and evaluator slots; `evolve-verdict` takes `requestId`, `nonce`, `slotToken`, `candidateDigest`, `parentDigest`, `evaluatorSessionId`, `verdict` (`approve` or `reject`), `rubricScores` and `reasons`; `revert-propose` takes `targetDigest`, and a revert always requires owner approval, so `evolve-verdict` on it is refused with `EVOLUTION_REQUEST_CLOSED`. `reclaim-evolution-lock` and `lineage-reanchor` need the owner at a terminal and are refused over MCP with `EVOLUTION_RECLAIM_REQUIRES_TTY` and `LINEAGE_REANCHOR_REQUIRES_TTY`. No tool is added. `status` carries an `evolution` field, `null` for an unsealed vault and `{unavailable}` when it cannot be read.
12
+ - **`interview` `op: "seal"` reports dropped templates as a warning instead of refusing.** Resealing an older contract generation seals and returns `CONTRACT_LEGACY_TEMPLATES_DROPPED: N ...` under `result.warnings` rather than refusing the removal as loosening. A template-only issue no longer refuses the seal or asks an unsafe-pattern question.
13
+ - **`interview` covers folders and properties only.** The `interpretations` parameter and the `interpretation-required` status are removed, no question asks about a template, and a sealed result carries no `templates` or `removedTemplates`. `write` scaffolds a new note from the live template in the vault's template folder (the named one, or the one matching the target folder) and reports a named template that does not exist as a `template-missing` conform entry; `search` `op: "templates"` lists the live templates.
14
+ - **The `oms_doctor` contract status payload reports `legacyTemplates` instead of `templates`.** The count is the number of template constraints an older contract generation still carries, which the write judge no longer enforces; a non-zero count also appears as the finding `legacy-template-constraints-ignored: N`.
15
+ - **`write` saves with warnings instead of dropping keys or drafting.** A write with an unknown key or an out-of-rule value is saved as written, and its receipt lists the finding under `warnings`. `fixes` now means lossless value fixes, each as `{field, kind}`: a number or checkbox written as a string, a scalar for a list, a midnight datetime for a date, a number for text whose source is exactly that number, an allowed value that differs only in spelling, and a missing value whose rule fixes it. A fix changes only that value's bytes. No key is ever dropped, and only frontmatter that does not parse is drafted.
16
+ - **`search {path}` accepts echoed schema defaults.** Some clients send every field with its default on each call. A path read now accepts `limit: 10`, `rerank: false` and `minScore: 0` alongside `path`, and the tool schema advertises them in the path branch. Any other value, and any field without a default such as `mode`, is still refused with the same `SEARCH_ARGS_INVALID` error.
17
+ - **`doctor` gains `op: "gaps"`, and `write` saves a repaired form instead of refusing a gap.** `op: gaps` is read-only. It reports the sealed contract revision, the open gaps in the ledger by id, note path, axis, kind and field (never the wanted value), whether each gap is drafted or stale, the corrupt ledger lines, and the contradictions in the sealed contract. It creates no store, state directory or ledger. A `write` that only misses the frame is now saved, with lossless fixes applied and every other finding kept as written. The receipt lists each finding; a gap the ledger could not record has no `id` and the receipt carries `gapLedger: "failed"` or `"unavailable"`. A denied write whose note was kept as a draft carries an opaque `draftRef`. `check: true` adds `resolution: {action, gaps, wouldDraft, precondition?}`, the same resolution the write would apply, and still writes nothing; `precondition` names a missing or stale `ifMatch` that would stop the write first. `op: gaps` reports `ledger: "truncated"` when the ledger outgrew the read window.
18
+ - **`write` denies only on a safety refusal, and a kept draft is no longer an error.** A denied write returns `{ok: false, status: "denied", refusals, violations, reason}`, and only control paths, unsafe paths, paths outside the vault, unsupported input, a tampered contract or a stale `ifMatch` deny it. Other findings ride on the result as `warnings`: a saved note's receipt carries the saved note's `warnings` and, as `fixes`, the `{field, kind}` of each lossless fix applied. In a sealed vault, only a note whose frontmatter does not parse is kept as a draft and returns `{ok: false, status: "drafted", draftRef, warnings}` without `isError`; every other write, including a contradiction and a draft that cannot be kept, is saved. `check: true` reports `refusals`, `warnings` and `fixes`. `status` reports `writeTools` as `write-disabled-contract-tampered` for a tampered contract and `write-unverified-contract` for a broken one, and `contract.reason` says which.
19
+ - **`doctor` gains `op: "lineage-recover"` and `op: "lineage-reanchor"`.** They are repair ops, so both require a verified target and reject a `cwd`-inferred vault. Each runs under the seal lock. It snapshots the retained generations the lineage is missing and appends the events the chain can account for: a seal that crashed before its event, a lost `<id>` link, or a store sealed before the lineage existed. `lineage-recover` refuses a gap it cannot explain with `CONTRACT_LINEAGE_GAP` and writes nothing. `lineage-reanchor` records that gap as a `gap-anchor` event, so the chain continues from the linked generation. Both return the anchors they recorded and a receipt. The receipt's postcondition gives the event and snapshot counts read back after the write. A vault that is not sealed returns `CONTRACT_NOT_SEALED`, and an unsafe state directory returns `STATE_DIR_UNSAFE` without touching it. On a vault whose first seal lost its lineage append, so it reads as `store-without-index` or `vault-moved`, `lineage-recover` also writes the vault's `index.json` entry, even when the lineage itself is already current, and the receipt lists `index.json`. A `vault-moved` vault whose original path still exists is a copy, and recovery refuses it with `CONTRACT_VAULT_ID_SHARED`. On an indexed vault with a current lineage, either op writes nothing.
20
+ - **A retried `interview` `op: "seal"` saves a template folder the first seal did not.** The seal records the template folder in `.oms/settings.json` after it seals the generation. When a seal stopped between the two and the retry found the contract already sealed, the folder was lost. The retry now saves the folder recorded with the confirmed proposal if the settings do not name one. The logged folder is checked as an interview answer is checked (it must be an existing, visible folder inside the vault) and is not saved otherwise. A folder that fails that check, or a settings write that fails, is returned as the warning `INTERVIEW_TEMPLATE_FOLDER_UNRECORDED` under `result.warnings`, and the seal is still reported. Settings that already name a folder are left as they are, and a vault with no settings is not treated as already sealed. Declined templates need no repair, because they are stored in the sealed generation.
21
+
22
+ ## [0.19.0] - 2026-09-28
23
+
24
+ - **Breaking: `write` refuses to overwrite an existing note without `ifMatch`.** The input is `{path, content, template?, ifMatch?, check?}`. Replacing a note needs `ifMatch`, the `sha256:` revision from the previous receipt or a `check`; without it the call returns `WRITE_IF_MATCH_REQUIRED` and the file is unchanged, and a stale revision returns the retryable `WRITE_TARGET_CHANGED` (or `WRITE_TARGET_VANISHED`); an `ifMatch` for a note that does not exist returns the retryable `WRITE_TARGET_ABSENT`, and retrying without `ifMatch` creates it. `check: true` judges the note and returns `status: "checked"` with the frame, the current revision and any violations, writing nothing. A written note now returns the receipt `{ok, path, revision, contractRevision, index: {keyword, vector}, conformed, missingDefaults}` instead of `{ok, path, missingDefaults}`; when the vault has an engine store the note is keyword-searchable in the next call.
25
+
26
+ - **The `interview` tool runs the interview over several calls and can seal a first or non-loosening contract.** `op: "questions"` (the default) lists what is still unanswered and writes nothing. `op: "answer"` logs answers keyed by question id and, once nothing is left, returns `status: "proposed"` with the proposal digest and its preview. `op: "confirm"` records the owner's yes to that exact digest, and `op: "seal"` seals only when the log holds a confirmation of the latest proposal and the interview, replayed from the log, still proposes it; a confirmation of an older proposal is refused. Nothing is written into a vault that was never sealed before its seal: answers are logged under a pending key outside the vault. Retrying `seal` after the contract was sealed returns `sealed` without a new generation and appends the missing `sealed` event, and a seal whose log record failed still reports `sealed` with a `warnings` entry. `doctor` `op: "validate"` includes `interviewLog`, the corrupt interview log lines by line number, read-only. `answer`, `confirm` and `seal` require a verified target vault, and a vault inferred from the working directory may only list questions. Refusals are returned as data, `{ok: false, status: "rejected", rejection}`, not as tool errors. The tool never reclaims a stale seal lock: `CONTRACT_SEAL_LOCK_STALE` is returned as `INTERVIEW_SEAL_LOCK_STALE` with the terminal command that can reclaim it, while `CONTRACT_SEAL_BUSY` passes through as retryable. Loosening a sealed contract still belongs to the owner's terminal.
27
+
28
+ - **Breaking: the MCP server exposes four tools: `write`, `search`, `interview`, and `doctor`.** The `link` tool is removed; suggest links with `search` `op: "link"` and check them with `doctor` `op: "link-check"`. The `status` tool is removed; `doctor` `op: "status"` reports the same health read-only and creates no engine store. The new `interview` tool lists the questions the owner would be asked now, with the seal state, and seals nothing: answers still go through `oms setup --answers`, and loosening stays with the owner's terminal. Annotations are per tool, so `readTools` is now `[search]`. Hosts display `oms_write`, `oms_search`, `oms_interview`, and `oms_doctor`.
29
+
30
+ - **`search` accepts `{path}` with no `op` for an engine-free exact read of one note.** The call returns the same document shape as `oms search --path`, never opens the engine store or a model, and is refused when combined with `op` or any other argument. A path the caller got wrong returns `available: false`, while an I/O failure such as a missing vault root returns the same `Oh My Second Brain MCP error` result as every other tool; the tool schema advertises it as its own `oneOf` branch, so `op` is no longer top-level required for `search`.
31
+
7
32
  ## [0.18.3] - 2026-09-26
8
33
 
9
34
  ## [0.18.2] - 2026-09-26
@@ -4,6 +4,18 @@ Per-host adapter and installer changes belong here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.20.0] - 2026-09-30
8
+
9
+ - **The Claude guard reads the live templates only for a new note.** `oms hook pre` loads the template folder only when the target does not exist yet, the only case in which a template choice is recorded, so an edit of an existing note no longer reads every template. New notes get the same decision and template choice gap as MCP `write`.
10
+ - **The Claude guard records an open template choice as MCP `write` does.** `oms hook pre` now reads the live templates, so a new note in a folder that two or more templates match records the same template choice gap. The allow or deny decision and its warnings are unchanged.
11
+ - **The Claude guard warns with the whole verdict on an edit.** Since the judge is stateless, `oms hook pre` returns every warning the edited note carries, while the gap ledger still records only the warnings the edit added.
12
+ - **The Claude guard allows a write with warnings instead of denying it.** `oms hook pre` now denies only on a safety refusal (control path, unsafe path, tampered contract, unparseable or truncated input). Any other finding allows the tool call and returns the warnings as `{systemMessage, hookSpecificOutput: {hookEventName: "PreToolUse", additionalContext}}`, without a `permissionDecision`, so the normal permission flow still applies and Claude sees what the note breaks. A broken contract, an unreadable note, or an edit whose result cannot be rebuilt warns instead of denying. Warnings the write adds are recorded as kept gaps; a ledger that cannot be written never denies. `oms-guard.mjs` forwards exactly that warning shape verbatim and still treats any other output as `malformed-output`. After upgrading, run `oms setup host sync` so the installed guard understands warnings; an older guard allows the write but drops the warning and logs `malformed-output`.
13
+
14
+ ## [0.19.0] - 2026-09-28
15
+
16
+ - **An ambiguous Codex managed block names the 0.19 commands.** The error now tells the user to rerun `oms setup host install` or `oms setup host remove` after the managed blocks are removed by hand.
17
+ - **Host guidance and the Claude guard name the 0.19 commands.** Deny reasons and the guard's transport-failure warning now point at `oms doctor contract`, `oms doctor status`, and `oms setup host sync`, and a new test spawns the real guard to prove every command it prints dispatches in the built CLI. The Claude, Codex, and Hermes registrations install the six shared skills (`interview` replaces `link` and `status`); run `oms setup host sync` after upgrading.
18
+
7
19
  ## [0.18.3] - 2026-09-26
8
20
 
9
21
  ## [0.18.2] - 2026-09-26
package/CHANGELOG.md CHANGED
@@ -10,6 +10,16 @@ This aggregate changelog contains changes that span multiple layers.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.20.0] - 2026-09-30
14
+
15
+ - **`ip-address` no longer carries its `<=10.5.0` SSRF advisories.** `npm run audit` started failing again on the transitive `@modelcontextprotocol/sdk` → `express-rate-limit` → `ip-address` chain (GHSA-rpw4-54j3-4h4q, GHSA-2vr4-cq9g-pvrc). `npm audit fix` (no `--force`) resolved it to `10.7.2`, still within `express-rate-limit`'s own `^10.2.0` range, so only `package-lock.json` changed — no `package.json` edit or `overrides` entry was needed. `npm run audit` reports zero vulnerabilities again.
16
+
17
+ ## [0.19.0] - 2026-09-28
18
+
19
+ - **Upgrading from 0.18: run `oms setup host sync` first.** Installed host assets (the Claude guard hook, skills, and MCP wiring) still point at the 0.18 surface until they are re-synced. Then read [Migrating to 0.19](./docs/migration-0.19.md) for the full command map.
20
+ - **Breaking: OMS is organised around search, interview, and write.** The fourteen CLI families collapse into `search`, `interview`, `write`, `setup`, `doctor`, `serve`, and the hidden `hook`, and the five MCP tools become `write`, `search`, `interview`, and `doctor`. A removed command exits 1 and names its replacement rather than aliasing it. The judge, the sealed contract, and the verified-target rules are unchanged; `write` now runs one pipeline that conforms template variables and headings mechanically, judges, writes atomically with an optional `ifMatch` revision, and updates the keyword index in the same call while queueing vectors for `oms doctor sync-embeddings`. See the layer changelogs for details.
21
+ - **The contract stops guessing what a template declares.** OMS used to parse template text to decide which questions the seal interview asks, substituting an in-band sentinel token for each Templater variable so the rest would still parse as YAML. Real templates broke that: four of this vault's templates put a variable where YAML expects a key, and a Templater JavaScript template with no frontmatter passed silently as declaring nothing while it really declares ten properties and four headings. Reading a template is now the agent's job and deriving the contract stays the machine's: the agent submits an interpretation, OMS verifies it against the sources it enumerated and the digests it computed itself, the owner confirms the interpretation before it decides a single question, and every sealed value still comes only from the owner's answers.
22
+
13
23
  ## [0.18.3] - 2026-09-26
14
24
 
15
25
  - **Ships the Hermes skill namespace from 0.18.2.** The 0.18.2 tag failed its release check on a flaky test and was never published to npm, so 0.18.3 is the first published release where Hermes installs the OMS skills as `oms-*` with `SKILL_CAPABILITY_GUIDE.md` (see 0.18.2 below). The read-only engine store tests now use a private temporary directory, so snapshot directories from parallel test files no longer break the check.