wiki-viewer 1.4.2 → 1.5.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 (280) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +5 -0
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +3 -27
  5. package/.next/standalone/.next/routes-manifest.json +34 -0
  6. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  25. package/.next/standalone/.next/server/app/api/agent/activity/route.js +5 -5
  26. package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js +6 -6
  28. package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js +6 -6
  30. package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js +6 -6
  32. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js +4 -4
  34. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js +4 -4
  36. package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js +5 -5
  38. package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js +11 -10
  40. package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route/app-paths-manifest.json +3 -0
  42. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route/build-manifest.json +9 -0
  43. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route/server-reference-manifest.json +4 -0
  44. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js +17 -0
  45. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.map +5 -0
  46. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.nft.json +1 -0
  47. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route_client-reference-manifest.js +3 -0
  48. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route/app-paths-manifest.json +3 -0
  49. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route/build-manifest.json +9 -0
  50. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route/server-reference-manifest.json +4 -0
  51. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js +15 -0
  52. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.map +5 -0
  53. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.nft.json +1 -0
  54. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route_client-reference-manifest.js +3 -0
  55. package/.next/standalone/.next/server/app/api/agent/fs/move/route/app-paths-manifest.json +3 -0
  56. package/.next/standalone/.next/server/app/api/agent/fs/move/route/build-manifest.json +9 -0
  57. package/.next/standalone/.next/server/app/api/agent/fs/move/route/server-reference-manifest.json +4 -0
  58. package/.next/standalone/.next/server/app/api/agent/fs/move/route.js +15 -0
  59. package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.map +5 -0
  60. package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.nft.json +1 -0
  61. package/.next/standalone/.next/server/app/api/agent/fs/move/route_client-reference-manifest.js +3 -0
  62. package/.next/standalone/.next/server/app/api/agent/fs/search/route/app-paths-manifest.json +3 -0
  63. package/.next/standalone/.next/server/app/api/agent/fs/search/route/build-manifest.json +9 -0
  64. package/.next/standalone/.next/server/app/api/agent/fs/search/route/server-reference-manifest.json +4 -0
  65. package/.next/standalone/.next/server/app/api/agent/fs/search/route.js +15 -0
  66. package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.map +5 -0
  67. package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.nft.json +1 -0
  68. package/.next/standalone/.next/server/app/api/agent/fs/search/route_client-reference-manifest.js +3 -0
  69. package/.next/standalone/.next/server/app/api/agent/internal/span/route.js +8 -8
  70. package/.next/standalone/.next/server/app/api/agent/internal/span/route.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/api/agent/settings/route.js +8 -8
  72. package/.next/standalone/.next/server/app/api/agent/settings/route.js.nft.json +1 -1
  73. package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js +7 -7
  74. package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js +1 -1
  76. package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js.nft.json +1 -1
  77. package/.next/standalone/.next/server/app/api/assets/[...path]/route.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/api/auth/[...all]/route.js +3 -3
  79. package/.next/standalone/.next/server/app/api/auth/[...all]/route.js.nft.json +1 -1
  80. package/.next/standalone/.next/server/app/api/system/auth-config/route.js +5 -5
  81. package/.next/standalone/.next/server/app/api/system/auth-config/route.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/api/system/auth-settings/route.js +4 -4
  83. package/.next/standalone/.next/server/app/api/system/auth-settings/route.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/api/system/browse/route.js +6 -6
  85. package/.next/standalone/.next/server/app/api/system/browse/route.js.nft.json +1 -1
  86. package/.next/standalone/.next/server/app/api/system/clear-root/route.js +5 -5
  87. package/.next/standalone/.next/server/app/api/system/clear-root/route.js.nft.json +1 -1
  88. package/.next/standalone/.next/server/app/api/system/config/route.js +5 -5
  89. package/.next/standalone/.next/server/app/api/system/config/route.js.nft.json +1 -1
  90. package/.next/standalone/.next/server/app/api/system/pins/route.js +4 -4
  91. package/.next/standalone/.next/server/app/api/system/pins/route.js.nft.json +1 -1
  92. package/.next/standalone/.next/server/app/api/system/reveal/route.js +4 -4
  93. package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
  94. package/.next/standalone/.next/server/app/api/system/root-status/route.js +4 -4
  95. package/.next/standalone/.next/server/app/api/system/root-status/route.js.nft.json +1 -1
  96. package/.next/standalone/.next/server/app/api/system/set-root/route.js +5 -5
  97. package/.next/standalone/.next/server/app/api/system/set-root/route.js.nft.json +1 -1
  98. package/.next/standalone/.next/server/app/api/upload/[...path]/route.js.nft.json +1 -1
  99. package/.next/standalone/.next/server/app/api/wiki/app/route.js +6 -6
  100. package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
  101. package/.next/standalone/.next/server/app/api/wiki/content/route.js +6 -6
  102. package/.next/standalone/.next/server/app/api/wiki/content/route.js.nft.json +1 -1
  103. package/.next/standalone/.next/server/app/api/wiki/file/route.js.nft.json +1 -1
  104. package/.next/standalone/.next/server/app/api/wiki/folder/route.js +5 -5
  105. package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
  106. package/.next/standalone/.next/server/app/api/wiki/move/route.js +6 -6
  107. package/.next/standalone/.next/server/app/api/wiki/move/route.js.nft.json +1 -1
  108. package/.next/standalone/.next/server/app/api/wiki/new-file/route.js +5 -5
  109. package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
  110. package/.next/standalone/.next/server/app/api/wiki/page/route.js +5 -5
  111. package/.next/standalone/.next/server/app/api/wiki/page/route.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/api/wiki/presence/route/app-paths-manifest.json +3 -0
  113. package/.next/standalone/.next/server/app/api/wiki/presence/route/build-manifest.json +9 -0
  114. package/.next/standalone/.next/server/app/api/wiki/presence/route/server-reference-manifest.json +4 -0
  115. package/.next/standalone/.next/server/app/api/wiki/presence/route.js +14 -0
  116. package/.next/standalone/.next/server/app/api/wiki/presence/route.js.map +5 -0
  117. package/.next/standalone/.next/server/app/api/wiki/presence/route.js.nft.json +1 -0
  118. package/.next/standalone/.next/server/app/api/wiki/presence/route_client-reference-manifest.js +3 -0
  119. package/.next/standalone/.next/server/app/api/wiki/route.js +5 -5
  120. package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
  121. package/.next/standalone/.next/server/app/api/wiki/slugs/route.js +5 -5
  122. package/.next/standalone/.next/server/app/api/wiki/slugs/route.js.nft.json +1 -1
  123. package/.next/standalone/.next/server/app/api/wiki/upload/route.js +5 -5
  124. package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
  125. package/.next/standalone/.next/server/app/api/wiki/watch/route.js +6 -6
  126. package/.next/standalone/.next/server/app/api/wiki/watch/route.js.nft.json +1 -1
  127. package/.next/standalone/.next/server/app/index.html +1 -1
  128. package/.next/standalone/.next/server/app/index.rsc +2 -2
  129. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  130. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  131. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  132. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  133. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  134. package/.next/standalone/.next/server/app/page.js +4 -4
  135. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  136. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  137. package/.next/standalone/.next/server/app/signin/page.js +9 -4
  138. package/.next/standalone/.next/server/app/signin/page.js.nft.json +1 -1
  139. package/.next/standalone/.next/server/app/signin/page_client-reference-manifest.js +1 -1
  140. package/.next/standalone/.next/server/app-paths-manifest.json +5 -0
  141. package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0d2jbo5.js +3 -0
  142. package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0ytral2.js +3 -0
  143. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0269d2e._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__03h3~3e._.js → [root-of-the-server]__04udrya._.js} +2 -2
  145. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05f6x0i._.js +3 -0
  146. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06-dtd0._.js +3 -0
  147. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06sek6_._.js +1 -1
  148. package/.next/standalone/.next/server/chunks/[root-of-the-server]__09s9qkd._.js +1 -1
  149. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d75a5q._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d_da6q._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e7si9p._.js +1 -1
  152. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0hhhvm7._.js +1 -1
  153. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__04g0j8i._.js → [root-of-the-server]__0kdn1le._.js} +2 -2
  154. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kk26wc._.js +3 -0
  155. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kur4ro._.js +15 -0
  156. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0p12-6~._.js +1 -1
  157. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qc2~6l._.js +15 -0
  158. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s~i040._.js +3 -0
  159. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w47wil._.js +1 -1
  160. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xq124z._.js +3 -0
  161. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l..bxz._.js → [root-of-the-server]__0xz-_0v._.js} +2 -2
  162. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10w2q~o._.js +1 -1
  163. package/.next/standalone/.next/server/chunks/_079xuln._.js +3 -0
  164. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_file_[___path]_route_actions_0gb11t~.js +3 -0
  165. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_ls_[[___path]]_route_actions_0wfg4nl.js +3 -0
  166. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_move_route_actions_0z34mqq.js +3 -0
  167. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_search_route_actions_0ut_ug..js +3 -0
  168. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_wiki_presence_route_actions_0x9d7f4.js +3 -0
  169. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0d89o98._.js → src_lib_auth_server_ts_00f4e0h._.js} +2 -2
  170. package/.next/standalone/.next/server/chunks/ssr/0biy_@better-auth_core_dist_121m_lh._.js +5 -0
  171. package/.next/standalone/.next/server/chunks/ssr/0dhv_better-auth_dist_adapters_kysely-adapter_index_mjs_08ymlcb._.js +3 -0
  172. package/.next/standalone/.next/server/chunks/ssr/0dti_kysely_dist_esm_0-t3o9q._.js +3 -0
  173. package/.next/standalone/.next/server/chunks/ssr/0dti_kysely_dist_esm_01._58q._.js +3 -0
  174. package/.next/standalone/.next/server/chunks/ssr/0p91_next_0f40gy0._.js +6 -0
  175. package/.next/standalone/.next/server/chunks/ssr/{0p91_next_dist_0~2d1tl._.js → 0p91_next_dist_10ud_sa._.js} +2 -2
  176. package/.next/standalone/.next/server/chunks/ssr/0sag_@better-auth_memory-adapter_dist_index_mjs_07-_ka6._.js +3 -0
  177. package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_bun-sqlite-dialect-DzNwOpKv_mjs_0lruvb8._.js +3 -0
  178. package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_d1-sqlite-dialect-C2B7YsIT_mjs_03n6~tc._.js +3 -0
  179. package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_index_mjs_0gi5w.w._.js +4 -0
  180. package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_node-sqlite-dialect_mjs_0l4.y~0._.js +3 -0
  181. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_diagram-OG6HWLK6_mjs_0t5sus6._.js +1 -1
  182. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_sankeyDiagram-5OEKKPKP_mjs_11ifrnu._.js +1 -1
  183. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__026s8~z._.js +3 -0
  184. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06mzg2t._.js +3 -0
  185. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0btcd4o._.js +12 -0
  186. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0c~og-9._.js +3 -0
  187. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__134_q7l._.js +1 -1
  188. package/.next/standalone/.next/server/chunks/ssr/_0pqaawz._.js +3 -3
  189. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_051tcva._.js +3 -0
  190. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0o~d81.._.js +1 -1
  191. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0y9k6h~._.js +1 -1
  192. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0znncy9._.js +1 -1
  193. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_12-ni1n._.js +1 -1
  194. package/.next/standalone/.next/server/chunks/ssr/src_app_signin_00yk0vm._.js +452 -0
  195. package/.next/standalone/.next/server/chunks/ssr/src_app_signin_signin-form_tsx_0eznlbd._.js +3 -0
  196. package/.next/standalone/.next/server/functions-config-manifest.json +5 -0
  197. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  198. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  199. package/.next/standalone/.next/server/pages/404.html +1 -1
  200. package/.next/standalone/.next/server/pages/500.html +1 -1
  201. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  202. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  203. package/.next/standalone/.next/static/chunks/09-jtayuhrq~b.js +1 -0
  204. package/.next/standalone/.next/static/chunks/0uv7ozzpiyp2_.js +1 -0
  205. package/.next/standalone/.next/static/chunks/135c~rq_ux73w.js +1 -0
  206. package/.next/standalone/.next/static/chunks/{11s9zwr7edrs7.js → 14luv~jq7e6-n.js} +12 -12
  207. package/.next/standalone/README.md +79 -16
  208. package/.next/standalone/agents/bootstrap-prompt.md +12 -1
  209. package/.next/standalone/agents/wiki-viewer-skill/SKILL.md +199 -6
  210. package/.next/standalone/docs/agent-fs-plan.md +65 -7
  211. package/.next/standalone/docs/file-vs-collab-authority.md +124 -0
  212. package/.next/standalone/package.json +1 -1
  213. package/.next/standalone/packages/wiki-viewer-mcp/README.md +194 -0
  214. package/.next/standalone/packages/wiki-viewer-mcp/dist/http-client.js +220 -0
  215. package/.next/standalone/packages/wiki-viewer-mcp/dist/index.js +472 -0
  216. package/.next/standalone/packages/wiki-viewer-mcp/dist/register.js +94 -0
  217. package/.next/standalone/packages/wiki-viewer-mcp/dist/state-cache.js +26 -0
  218. package/.next/standalone/packages/wiki-viewer-mcp/package-lock.json +1716 -0
  219. package/.next/standalone/packages/wiki-viewer-mcp/package.json +41 -0
  220. package/.next/standalone/packages/wiki-viewer-mcp/src/http-client.ts +344 -0
  221. package/.next/standalone/packages/wiki-viewer-mcp/src/index.ts +575 -0
  222. package/.next/standalone/packages/wiki-viewer-mcp/src/register.ts +144 -0
  223. package/.next/standalone/packages/wiki-viewer-mcp/src/state-cache.ts +45 -0
  224. package/.next/standalone/packages/wiki-viewer-mcp/tsconfig.json +19 -0
  225. package/.next/standalone/pnpm-lock.yaml +9 -9
  226. package/.next/standalone/src/app/api/agent/files/[...path]/route.ts +11 -1
  227. package/.next/standalone/src/app/api/agent/fs/file/[...path]/route.ts +344 -0
  228. package/.next/standalone/src/app/api/agent/fs/ls/[[...path]]/route.ts +147 -0
  229. package/.next/standalone/src/app/api/agent/fs/move/route.ts +125 -0
  230. package/.next/standalone/src/app/api/agent/fs/search/route.ts +236 -0
  231. package/.next/standalone/src/app/api/agent/register/route.ts +3 -3
  232. package/.next/standalone/src/app/api/agents/install/route.ts +44 -2
  233. package/.next/standalone/src/app/api/wiki/move/route.ts +10 -1
  234. package/.next/standalone/src/app/api/wiki/presence/route.ts +55 -0
  235. package/.next/standalone/src/app/signin/page.tsx +10 -225
  236. package/.next/standalone/src/app/signin/signin-form.tsx +238 -0
  237. package/.next/standalone/src/components/ai-panel/ai-panel.tsx +49 -0
  238. package/.next/standalone/src/components/editor/editor.tsx +38 -0
  239. package/.next/standalone/src/lib/proof/audit.ts +79 -0
  240. package/.next/standalone/src/lib/proof/auth.ts +2 -2
  241. package/.next/standalone/src/lib/proof/collab-state.ts +79 -0
  242. package/.next/standalone/src/lib/proof/lease.ts +104 -0
  243. package/.next/standalone/src/lib/proof/ops-applier.ts +74 -25
  244. package/.next/standalone/src/lib/proof/raw-fs.ts +198 -0
  245. package/.next/standalone/src/lib/proof/registry.ts +1 -1
  246. package/.next/standalone/src/lib/proof/sidecar.ts +38 -1
  247. package/.next/standalone/src/lib/proof/types.ts +13 -1
  248. package/.next/standalone/src/tests/proof/agent-fs.test.ts +824 -0
  249. package/.next/standalone/src/tests/proof/agents-install.test.ts +53 -0
  250. package/.next/standalone/src/tests/proof/collab-state.test.ts +499 -0
  251. package/.next/standalone/src/tests/proof/reconcile-sidecar.test.ts +195 -0
  252. package/.next/standalone/src/tests/proof/sidecar-lifecycle.test.ts +177 -0
  253. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  254. package/README.md +79 -16
  255. package/package.json +1 -1
  256. package/.next/standalone/.next/server/app/signin.html +0 -1
  257. package/.next/standalone/.next/server/app/signin.meta +0 -15
  258. package/.next/standalone/.next/server/app/signin.rsc +0 -23
  259. package/.next/standalone/.next/server/app/signin.segments/_full.segment.rsc +0 -23
  260. package/.next/standalone/.next/server/app/signin.segments/_head.segment.rsc +0 -6
  261. package/.next/standalone/.next/server/app/signin.segments/_index.segment.rsc +0 -7
  262. package/.next/standalone/.next/server/app/signin.segments/_tree.segment.rsc +0 -3
  263. package/.next/standalone/.next/server/app/signin.segments/signin/__PAGE__.segment.rsc +0 -9
  264. package/.next/standalone/.next/server/app/signin.segments/signin.segment.rsc +0 -5
  265. package/.next/standalone/.next/server/chunks/[root-of-the-server]__09~~-nd._.js +0 -3
  266. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w87kiz._.js +0 -3
  267. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12ssbhv._.js +0 -3
  268. package/.next/standalone/.next/server/chunks/node_modules__pnpm_0g0fhfk._.js +0 -3
  269. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__088hqzy._.js +0 -3
  270. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0c147fo._.js +0 -3
  271. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0z.1v1z._.js +0 -3
  272. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12zp2dm._.js +0 -3
  273. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0exqvyf._.js +0 -3
  274. package/.next/standalone/.next/server/chunks/ssr/src_app_signin_page_tsx_1143y49._.js +0 -3
  275. package/.next/standalone/.next/static/chunks/0-_39e4awbk8y.js +0 -1
  276. package/.next/standalone/.next/static/chunks/02bxx~2cg~s6~.js +0 -1
  277. package/.next/standalone/.next/static/chunks/0mj_f8ncf2tks.js +0 -1
  278. /package/.next/standalone/.next/static/{OwXjVuoXDC_gda_BKji1J → 19j4-_brJLoulXAMekpM2}/_buildManifest.js +0 -0
  279. /package/.next/standalone/.next/static/{OwXjVuoXDC_gda_BKji1J → 19j4-_brJLoulXAMekpM2}/_clientMiddlewareManifest.js +0 -0
  280. /package/.next/standalone/.next/static/{OwXjVuoXDC_gda_BKji1J → 19j4-_brJLoulXAMekpM2}/_ssgManifest.js +0 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Collab-state computation for §3.5 Working mode vs Collaborating mode.
3
+ *
4
+ * Returns:
5
+ * state — "active" | "tracked" | "untracked" | "not-markdown"
6
+ * revision — X-Collab-Revision (sidecar.revision + leaseGeneration(path))
7
+ * snapshotUrl — Tier-2 snapshot URL (null for non-markdown)
8
+ *
9
+ * Revision formula:
10
+ * revision = (sidecar?.revision ?? 0) + leaseGeneration(relPath)
11
+ *
12
+ * sidecar.revision bumps on every collab write (block-ops, comment, suggestion).
13
+ * leaseGeneration bumps on every lease open/close.
14
+ * Together they form a monotonically-non-decreasing integer that changes
15
+ * whenever collab state could change. A raw PUT that supplies
16
+ * If-Collab-Match: <n> is only safe when n equals the revision computed
17
+ * atomically inside the write mutex — otherwise 409 COLLAB_ACTIVE.
18
+ *
19
+ * active = has artifacts (pendingSuggestions > 0 OR unresolvedComments > 0
20
+ * OR blockProvenance entries > 0) OR has a current human edit lease.
21
+ */
22
+
23
+ import { readSidecar } from "./sidecar";
24
+ import { hasActiveLease, leaseGeneration } from "./lease";
25
+
26
+ function isMarkdownPath(p: string): boolean {
27
+ return p.endsWith(".md") || p.endsWith(".markdown");
28
+ }
29
+
30
+ export type CollabState = "active" | "tracked" | "untracked" | "not-markdown";
31
+
32
+ export interface CollabStateResult {
33
+ state: CollabState;
34
+ /** sidecar.revision + leaseGeneration(relPath) */
35
+ revision: number;
36
+ /** /api/agent/files/<relPath> — null for not-markdown */
37
+ snapshotUrl: string | null;
38
+ }
39
+
40
+ export async function computeCollabState(
41
+ rootDir: string,
42
+ relPath: string,
43
+ ): Promise<CollabStateResult> {
44
+ if (!isMarkdownPath(relPath)) {
45
+ return { state: "not-markdown", revision: 0, snapshotUrl: null };
46
+ }
47
+
48
+ const snapshotUrl = `/api/agent/files/${relPath}`;
49
+
50
+ // Compute revision components
51
+ const sidecar = await readSidecar(rootDir, relPath);
52
+ const gen = leaseGeneration(relPath);
53
+ const sidecarRevision = sidecar?.revision ?? 0;
54
+ const revision = sidecarRevision + gen;
55
+
56
+ // Lease check — do after reading sidecar to keep gen stable within this call
57
+ const leaseActive = hasActiveLease(relPath);
58
+
59
+ if (!sidecar && !leaseActive) {
60
+ return { state: "untracked", revision, snapshotUrl };
61
+ }
62
+
63
+ if (leaseActive) {
64
+ return { state: "active", revision, snapshotUrl };
65
+ }
66
+
67
+ // Has sidecar, no lease — inspect artifacts
68
+ const pendingSuggestions = sidecar!.suggestions.filter(
69
+ (s) => s.status === "pending" && !s.stale,
70
+ ).length;
71
+ const unresolvedComments = sidecar!.comments.filter((c) => !c.resolved).length;
72
+ const proofSpanCount = Object.keys(sidecar!.blockProvenance ?? {}).length;
73
+
74
+ if (pendingSuggestions > 0 || unresolvedComments > 0 || proofSpanCount > 0) {
75
+ return { state: "active", revision, snapshotUrl };
76
+ }
77
+
78
+ return { state: "tracked", revision, snapshotUrl };
79
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Human edit-lease store (in-memory, globalThis for hot-reload survival).
3
+ *
4
+ * A lease is set when a human opens a .md doc in the editor and refreshed
5
+ * via heartbeat while the doc stays open. Its presence makes collab-state
6
+ * "active" even before the human has typed a single suggestion (closes the
7
+ * false-negative GPT-5.5 caught in the design review).
8
+ *
9
+ * TTL: LEASE_TTL_MS (default 90 s). Editor should heartbeat every ~30 s.
10
+ *
11
+ * Collab-generation counter:
12
+ * - Starts at 0 per path.
13
+ * - Increments on EVERY 0→1 transition (first setLease, or setLease after
14
+ * all prior leases expired) AND on every 1→0 transition (clearLease or
15
+ * expiry-on-read).
16
+ * - X-Collab-Revision = sidecar.revision + leaseGeneration(path).
17
+ * Any lease or sidecar state change therefore bumps the combined revision.
18
+ */
19
+
20
+ export const LEASE_TTL_MS = 90_000;
21
+
22
+ interface LeaseEntry {
23
+ userId: string;
24
+ expiresAt: number;
25
+ }
26
+
27
+ interface LeaseStore {
28
+ leases: Map<string, LeaseEntry>;
29
+ generations: Map<string, number>;
30
+ }
31
+
32
+ const g = globalThis as typeof globalThis & { __wikiLeaseStore?: LeaseStore };
33
+ if (!g.__wikiLeaseStore) {
34
+ g.__wikiLeaseStore = { leases: new Map(), generations: new Map() };
35
+ }
36
+
37
+ function store(): LeaseStore {
38
+ return g.__wikiLeaseStore!;
39
+ }
40
+
41
+ /** True if relPath has a non-expired lease RIGHT NOW (lazy-sweeps on check). */
42
+ function isLeaseActiveInternal(relPath: string): boolean {
43
+ const s = store();
44
+ const entry = s.leases.get(relPath);
45
+ if (!entry) return false;
46
+ if (Date.now() >= entry.expiresAt) {
47
+ s.leases.delete(relPath);
48
+ // 1→0 transition: bump generation
49
+ s.generations.set(relPath, (s.generations.get(relPath) ?? 0) + 1);
50
+ return false;
51
+ }
52
+ return true;
53
+ }
54
+
55
+ /**
56
+ * Set (or refresh) a lease for relPath by userId.
57
+ * Bumps the generation counter on a 0→1 transition.
58
+ */
59
+ export function setLease(relPath: string, userId: string, ttlMs = LEASE_TTL_MS): void {
60
+ const s = store();
61
+ const wasActive = isLeaseActiveInternal(relPath);
62
+ s.leases.set(relPath, { userId, expiresAt: Date.now() + ttlMs });
63
+ if (!wasActive) {
64
+ // 0→1 transition: bump generation
65
+ s.generations.set(relPath, (s.generations.get(relPath) ?? 0) + 1);
66
+ }
67
+ }
68
+
69
+ /** True if relPath has a current non-expired lease. */
70
+ export function hasActiveLease(relPath: string): boolean {
71
+ return isLeaseActiveInternal(relPath);
72
+ }
73
+
74
+ /**
75
+ * Clear the lease for relPath (on doc close).
76
+ * Only clears if the existing lease belongs to userId.
77
+ * Bumps generation on removal.
78
+ */
79
+ export function clearLease(relPath: string, userId: string): void {
80
+ const s = store();
81
+ const entry = s.leases.get(relPath);
82
+ if (!entry) return;
83
+ // Still clear expired leases owned by the same user
84
+ if (entry.userId === userId) {
85
+ s.leases.delete(relPath);
86
+ // 1→0 transition: bump generation
87
+ s.generations.set(relPath, (s.generations.get(relPath) ?? 0) + 1);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Returns the monotonic-ish collab generation for relPath.
93
+ * Increments on every 0→1 and 1→0 lease transition.
94
+ */
95
+ export function leaseGeneration(relPath: string): number {
96
+ return store().generations.get(relPath) ?? 0;
97
+ }
98
+
99
+ /** Reset store — for tests only. */
100
+ export function _resetLeaseStore(): void {
101
+ const s = store();
102
+ s.leases.clear();
103
+ s.generations.clear();
104
+ }
@@ -103,6 +103,20 @@ function opMarkdownToBlocks(
103
103
  return { nodes, refs };
104
104
  }
105
105
 
106
+ function markOrphanedRefsStale(sidecar: Sidecar, newRefMap: Record<string, unknown>): void {
107
+ const validRefs = new Set(Object.keys(newRefMap));
108
+ for (const s of sidecar.suggestions) {
109
+ if (s.status === "pending" && !validRefs.has(s.ref)) {
110
+ s.stale = true;
111
+ }
112
+ }
113
+ for (const c of sidecar.comments) {
114
+ if (!c.resolved && !validRefs.has(c.ref)) {
115
+ c.stale = true;
116
+ }
117
+ }
118
+ }
119
+
106
120
  function buildSnapshot(
107
121
  mdPath: string,
108
122
  blocks: Block[],
@@ -121,6 +135,49 @@ function buildSnapshot(
121
135
  };
122
136
  }
123
137
 
138
+ /**
139
+ * Reconcile a sidecar after a file was modified outside of block-ops.
140
+ * Rebuilds refMap, bumps revision, marks orphaned anchors stale, emits event, writes sidecar.
141
+ *
142
+ * Callers MUST hold `withFileMutex(mdPath, ...)` before calling.
143
+ *
144
+ * Used by:
145
+ * - readSnapshot: eventType="file.externallyEdited", by="system"
146
+ * - raw-fs PUT (Phase 2): eventType="file.rawWritten", by="ai:<id>"
147
+ */
148
+ export async function reconcileSidecar(args: {
149
+ rootDir: string;
150
+ mdPath: string;
151
+ content: string;
152
+ sidecar: Sidecar; // mutated in place
153
+ by: string;
154
+ eventType: string;
155
+ fingerprint: string; // pre-computed sha256 of content
156
+ }): Promise<{ snapshot: Snapshot; blocks: Block[] }> {
157
+ const { rootDir, mdPath, content, sidecar, by, eventType, fingerprint } = args;
158
+ const nodes = parseBlocks(content);
159
+ const { blocks, newRefMap } = assignRefs(nodes, sidecar);
160
+ const oldFingerprint = sidecar.fingerprint;
161
+ sidecar.refMap = newRefMap;
162
+ sidecar.revision += 1;
163
+ sidecar.updatedAt = nowIso();
164
+ sidecar.fingerprint = fingerprint;
165
+ markOrphanedRefsStale(sidecar, newRefMap);
166
+ const eventPayload: Omit<ProofEvent, "id"> & Record<string, unknown> = {
167
+ type: eventType,
168
+ at: nowIso(),
169
+ by,
170
+ fingerprint,
171
+ };
172
+ if (eventType === "file.rawWritten") {
173
+ eventPayload.oldSha = oldFingerprint;
174
+ eventPayload.newSha = fingerprint;
175
+ }
176
+ emitEvents(sidecar, [eventPayload as Omit<ProofEvent, "id">]);
177
+ await writeSidecar(rootDir, mdPath, sidecar);
178
+ return { snapshot: buildSnapshot(mdPath, blocks, sidecar), blocks };
179
+ }
180
+
124
181
  /**
125
182
  * Pure read: load file, parse blocks, assign refs, return snapshot.
126
183
  * Detects external edits (fingerprint mismatch) and records them in the sidecar.
@@ -156,20 +213,16 @@ export async function readSnapshot(
156
213
  const freshFingerprint = sha256file(freshContent);
157
214
 
158
215
  if (freshSidecar.fingerprint && freshSidecar.fingerprint !== freshFingerprint) {
159
- const freshNodes = parseBlocks(freshContent);
160
- const { blocks: freshBlocks, newRefMap } = assignRefs(freshNodes, freshSidecar);
161
- freshSidecar.refMap = newRefMap;
162
- freshSidecar.revision += 1;
163
- freshSidecar.updatedAt = nowIso();
164
- freshSidecar.fingerprint = freshFingerprint;
165
- emitEvents(freshSidecar, [{
166
- type: "file.externallyEdited",
167
- at: nowIso(),
216
+ const { snapshot } = await reconcileSidecar({
217
+ rootDir,
218
+ mdPath,
219
+ content: freshContent,
220
+ sidecar: freshSidecar,
168
221
  by: "system",
222
+ eventType: "file.externallyEdited",
169
223
  fingerprint: freshFingerprint,
170
- }]);
171
- await writeSidecar(rootDir, mdPath, freshSidecar);
172
- return buildSnapshot(mdPath, freshBlocks, freshSidecar);
224
+ });
225
+ return snapshot;
173
226
  }
174
227
 
175
228
  // Another writer already updated — just build snapshot from current state
@@ -233,28 +286,24 @@ export async function applyOps(args: {
233
286
 
234
287
  const isAi = by.startsWith("ai:");
235
288
 
236
- // Detect external edits
289
+ // Detect external edits — reconcile eagerly inside the mutex (R2: do not let lazy reconcile miss)
237
290
  if (sidecar.fingerprint && sidecar.fingerprint !== fingerprint) {
238
- const nodes = parseBlocks(content);
239
- const { blocks: freshBlocks, newRefMap } = assignRefs(nodes, sidecar);
240
- sidecar.refMap = newRefMap;
241
- sidecar.revision += 1;
242
- sidecar.updatedAt = nowIso();
243
- sidecar.fingerprint = fingerprint;
244
- emitEvents(sidecar, [{
245
- type: "file.externallyEdited",
246
- at: nowIso(),
291
+ const { snapshot: freshSnapshot } = await reconcileSidecar({
292
+ rootDir,
293
+ mdPath,
294
+ content,
295
+ sidecar,
247
296
  by: "system",
297
+ eventType: "file.externallyEdited",
248
298
  fingerprint,
249
- }]);
250
- await writeSidecar(rootDir, mdPath, sidecar);
299
+ });
251
300
  // Return STALE_REVISION so caller knows to re-fetch
252
301
  return {
253
302
  ok: false,
254
303
  status: 409,
255
304
  code: "STALE_REVISION",
256
305
  message: "File was externally edited. Fetch the new snapshot and retry.",
257
- snapshot: buildSnapshot(mdPath, freshBlocks, sidecar),
306
+ snapshot: freshSnapshot,
258
307
  };
259
308
  }
260
309
 
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Shared utilities for Tier-1 raw-fs routes.
3
+ *
4
+ * - Path safety (symlink escape guard, hard-denied paths)
5
+ * - SHA-256 of Buffer
6
+ * - Best-effort MIME by extension
7
+ * - Atomic write (tmp → rename, preserves mode)
8
+ */
9
+ import { createHash } from "node:crypto";
10
+ import { open, stat, rename, realpath, mkdir } from "node:fs/promises";
11
+ import path from "node:path";
12
+ import { getRootDir } from "@/lib/root-dir";
13
+
14
+ // ── Denied path checks ──────────────────────────────────────────────────────
15
+
16
+ /**
17
+ * True if the root-relative path is hard-denied regardless of scope.
18
+ * - .proof/ — sidecar storage (Tier-2 internal)
19
+ * - .git/ — git objects (sensitive)
20
+ */
21
+ export function isDeniedRelPath(rel: string): boolean {
22
+ const norm = rel.replace(/\\/g, "/");
23
+ return (
24
+ norm === ".proof" ||
25
+ norm.startsWith(".proof/") ||
26
+ norm === ".git" ||
27
+ norm.startsWith(".git/")
28
+ );
29
+ }
30
+
31
+ export function isMarkdown(filePath: string): boolean {
32
+ return filePath.endsWith(".md") || filePath.endsWith(".markdown");
33
+ }
34
+
35
+ // ── Path safety ──────────────────────────────────────────────────────────────
36
+
37
+ /**
38
+ * Resolve the absolute path for `rel`, checking:
39
+ * 1. basic traversal (path.join containment)
40
+ * 2. hard-denied prefixes (.proof/, .git/)
41
+ * 3. symlink escape: if file exists, realpath must still be under root
42
+ *
43
+ * For non-existent targets (creates), resolves parent dir instead.
44
+ *
45
+ * Returns absolute path on success, null on rejection.
46
+ */
47
+ export async function safeAbsPath(rel: string): Promise<string | null> {
48
+ const root = getRootDir();
49
+ if (!root) return null;
50
+
51
+ // Normalise and basic traversal guard
52
+ const normalised = path.normalize(rel);
53
+ if (normalised.startsWith("..") || path.isAbsolute(normalised)) return null;
54
+
55
+ if (isDeniedRelPath(normalised)) return null;
56
+
57
+ const abs = path.join(root, normalised);
58
+ // Re-check containment after normalize
59
+ if (abs !== root && !abs.startsWith(root + path.sep)) return null;
60
+
61
+ // Symlink-escape check: resolve realpath of the nearest existing ancestor
62
+ try {
63
+ const real = await realpath(abs);
64
+ if (real !== root && !real.startsWith(root + path.sep)) return null;
65
+ } catch {
66
+ // Target doesn't exist — check parent
67
+ const parent = path.dirname(abs);
68
+ if (parent !== abs) {
69
+ try {
70
+ const parentReal = await realpath(parent);
71
+ if (parentReal !== root && !parentReal.startsWith(root + path.sep)) {
72
+ return null;
73
+ }
74
+ } catch {
75
+ // Parent doesn't exist either — containment already checked above
76
+ }
77
+ }
78
+ }
79
+
80
+ return abs;
81
+ }
82
+
83
+ // ── Hashing ──────────────────────────────────────────────────────────────────
84
+
85
+ /** Returns "sha256:<hex>" — the canonical sha format used in ETag / audit. */
86
+ export function sha256ofBuf(buf: Buffer): string {
87
+ return "sha256:" + createHash("sha256").update(buf).digest("hex");
88
+ }
89
+
90
+ /**
91
+ * Normalise an If-Match / ETag header value to bare hex.
92
+ * Strips outer double-quotes and "sha256:" prefix.
93
+ */
94
+ export function extractShaHex(header: string): string {
95
+ return header
96
+ .replace(/^"/, "")
97
+ .replace(/"$/, "")
98
+ .replace(/^sha256:/, "");
99
+ }
100
+
101
+ // ── MIME ──────────────────────────────────────────────────────────────────────
102
+
103
+ const MIME_MAP: Record<string, string> = {
104
+ ".md": "text/markdown; charset=utf-8",
105
+ ".markdown": "text/markdown; charset=utf-8",
106
+ ".txt": "text/plain; charset=utf-8",
107
+ ".json": "application/json; charset=utf-8",
108
+ ".jsonc": "application/json; charset=utf-8",
109
+ ".ts": "text/typescript; charset=utf-8",
110
+ ".tsx": "text/typescript; charset=utf-8",
111
+ ".mts": "text/typescript; charset=utf-8",
112
+ ".js": "text/javascript; charset=utf-8",
113
+ ".jsx": "text/javascript; charset=utf-8",
114
+ ".mjs": "text/javascript; charset=utf-8",
115
+ ".html": "text/html; charset=utf-8",
116
+ ".htm": "text/html; charset=utf-8",
117
+ ".css": "text/css; charset=utf-8",
118
+ ".yaml": "text/yaml; charset=utf-8",
119
+ ".yml": "text/yaml; charset=utf-8",
120
+ ".toml": "text/toml; charset=utf-8",
121
+ ".xml": "application/xml; charset=utf-8",
122
+ ".svg": "image/svg+xml",
123
+ ".png": "image/png",
124
+ ".jpg": "image/jpeg",
125
+ ".jpeg": "image/jpeg",
126
+ ".gif": "image/gif",
127
+ ".webp": "image/webp",
128
+ ".pdf": "application/pdf",
129
+ ".zip": "application/zip",
130
+ ".tar": "application/x-tar",
131
+ ".gz": "application/gzip",
132
+ ".py": "text/x-python; charset=utf-8",
133
+ ".rs": "text/x-rustsrc; charset=utf-8",
134
+ ".go": "text/x-go; charset=utf-8",
135
+ ".sh": "text/x-shellscript; charset=utf-8",
136
+ ".csv": "text/csv; charset=utf-8",
137
+ ".env": "text/plain; charset=utf-8",
138
+ };
139
+
140
+ export function mimeByExt(filePath: string): string {
141
+ const ext = path.extname(filePath).toLowerCase();
142
+ return MIME_MAP[ext] ?? "application/octet-stream";
143
+ }
144
+
145
+ // ── Binary detection ──────────────────────────────────────────────────────────
146
+
147
+ /** Heuristic: buffer contains a null byte in first 8 KB → treat as binary. */
148
+ export function looksLikeBinary(buf: Buffer): boolean {
149
+ const sample = buf.slice(0, Math.min(buf.length, 8192));
150
+ return sample.includes(0);
151
+ }
152
+
153
+ // ── Atomic write ──────────────────────────────────────────────────────────────
154
+
155
+ /**
156
+ * Write `data` to `absPath` atomically:
157
+ * 1. Write to a sibling .tmp file
158
+ * 2. datasync the tmp file
159
+ * 3. rename tmp → target
160
+ *
161
+ * Preserves the unix mode of the existing file.
162
+ * Caller is responsible for creating parent dirs.
163
+ */
164
+ export async function atomicWrite(absPath: string, data: Buffer): Promise<void> {
165
+ // Preserve mode of existing file
166
+ let mode = 0o644;
167
+ try {
168
+ const st = await stat(absPath);
169
+ mode = st.mode & 0o777;
170
+ } catch {
171
+ // New file — use 0o644 default
172
+ }
173
+
174
+ const tmp = absPath + ".~" + process.pid + "." + Date.now() + ".tmp";
175
+ const fh = await open(tmp, "w", mode);
176
+ try {
177
+ await fh.write(data);
178
+ await fh.datasync();
179
+ } finally {
180
+ await fh.close();
181
+ }
182
+ await rename(tmp, absPath);
183
+ }
184
+
185
+ /** Ensure parent directory exists; creates intermediate dirs if mkdirs=true. */
186
+ export async function ensureParentDir(absPath: string, mkdirs: boolean): Promise<boolean> {
187
+ const dir = path.dirname(absPath);
188
+ if (mkdirs) {
189
+ await mkdir(dir, { recursive: true });
190
+ return true;
191
+ }
192
+ try {
193
+ await stat(dir);
194
+ return true;
195
+ } catch {
196
+ return false;
197
+ }
198
+ }
@@ -19,7 +19,7 @@ const LAST_SEEN_THROTTLE_MS = 30_000;
19
19
 
20
20
  export interface AgentScope {
21
21
  paths: string[]; // glob patterns
22
- ops: Array<"read" | "mutate">;
22
+ ops: Array<"read" | "mutate" | "delete">;
23
23
  }
24
24
 
25
25
  export interface Agent {
@@ -1,4 +1,4 @@
1
- import { readFile, writeFile, mkdir, rename } from "node:fs/promises";
1
+ import { readFile, writeFile, mkdir, rename, unlink } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import type { Sidecar } from "./types";
4
4
 
@@ -44,6 +44,43 @@ export async function writeSidecar(
44
44
  await rename(tmp, dest);
45
45
  }
46
46
 
47
+ /**
48
+ * Move a sidecar file alongside a renamed .md file.
49
+ * No-op if the sidecar does not exist.
50
+ */
51
+ export async function moveSidecar(
52
+ rootDir: string,
53
+ fromMdPath: string,
54
+ toMdPath: string,
55
+ ): Promise<void> {
56
+ const src = sidecarPath(rootDir, fromMdPath);
57
+ const dest = sidecarPath(rootDir, toMdPath);
58
+ try {
59
+ await mkdir(path.dirname(dest), { recursive: true });
60
+ await rename(src, dest);
61
+ } catch (err) {
62
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") return; // no sidecar — ok
63
+ throw err;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Delete a sidecar file for a removed .md file.
69
+ * No-op if the sidecar does not exist.
70
+ */
71
+ export async function deleteSidecar(
72
+ rootDir: string,
73
+ mdPath: string,
74
+ ): Promise<void> {
75
+ const filePath = sidecarPath(rootDir, mdPath);
76
+ try {
77
+ await unlink(filePath);
78
+ } catch (err) {
79
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") return; // already gone — ok
80
+ throw err;
81
+ }
82
+ }
83
+
47
84
  export function emptySidecar(mdPath: string): Sidecar {
48
85
  const now = new Date().toISOString();
49
86
  return {
@@ -40,6 +40,8 @@ export interface Comment {
40
40
  resolved: boolean;
41
41
  createdAt: string;
42
42
  turns: CommentTurn[];
43
+ /** Set true when a raw .md overwrite orphans the anchor ref (R2 collab-anchor safety). */
44
+ stale?: boolean;
43
45
  }
44
46
 
45
47
  export type SuggestionKind =
@@ -61,11 +63,21 @@ export interface Suggestion {
61
63
  createdAt: string;
62
64
  resolvedAt?: string; // when accepted/rejected
63
65
  resolvedBy?: string;
66
+ /** Set true when a raw .md overwrite orphans the anchor ref (R2 collab-anchor safety). */
67
+ stale?: boolean;
64
68
  }
65
69
 
66
70
  export interface ProofEvent {
67
71
  id: number;
68
- type: string; // see §4.6 list
72
+ /**
73
+ * Known event types:
74
+ * block.replaced | block.inserted | block.deleted
75
+ * comment.added | comment.replied | comment.resolved | comment.reopened
76
+ * suggestion.added | suggestion.accepted | suggestion.rejected
77
+ * file.externallyEdited — writer unknown (chokidar / external tool)
78
+ * file.rawWritten — writer known (by: "ai:<id>"), emitted by Tier-1 raw-fs write
79
+ */
80
+ type: string;
69
81
  at: string;
70
82
  by: string;
71
83
  [k: string]: unknown;