studiograph 1.1.3 → 1.2.0-beta.10

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 (356) hide show
  1. package/README.md +2 -8
  2. package/dist/agent/orchestrator.d.ts +53 -0
  3. package/dist/agent/orchestrator.js +197 -12
  4. package/dist/agent/orchestrator.js.map +1 -1
  5. package/dist/agent/prompts/system.md +43 -11
  6. package/dist/agent/skill-loader.d.ts +48 -0
  7. package/dist/agent/skill-loader.js +166 -0
  8. package/dist/agent/skill-loader.js.map +1 -0
  9. package/dist/agent/skills/{bundled/enrich-entities.md → enrich-entities.md} +12 -0
  10. package/dist/agent/skills/entity-schema.md +502 -0
  11. package/dist/agent/skills/obsidian-source-setup.md +246 -0
  12. package/dist/agent/skills/sync-configuration.md +144 -0
  13. package/dist/agent/skills/sync-setup.md +84 -0
  14. package/dist/agent/tools/connector-tools.d.ts +37 -0
  15. package/dist/agent/tools/connector-tools.js +132 -0
  16. package/dist/agent/tools/connector-tools.js.map +1 -0
  17. package/dist/agent/tools/fs-tools.d.ts +39 -0
  18. package/dist/agent/tools/fs-tools.js +106 -0
  19. package/dist/agent/tools/fs-tools.js.map +1 -0
  20. package/dist/agent/tools/graph-tools.d.ts +33 -1
  21. package/dist/agent/tools/graph-tools.js +192 -44
  22. package/dist/agent/tools/graph-tools.js.map +1 -1
  23. package/dist/agent/tools/load-skill.d.ts +1 -1
  24. package/dist/agent/tools/load-skill.js +1 -1
  25. package/dist/agent/tools/load-skill.js.map +1 -1
  26. package/dist/agent/tools/ops-tools.d.ts +24 -0
  27. package/dist/agent/tools/ops-tools.js +542 -0
  28. package/dist/agent/tools/ops-tools.js.map +1 -0
  29. package/dist/agent/tools/sync-tools.d.ts +33 -0
  30. package/dist/agent/tools/sync-tools.js +947 -0
  31. package/dist/agent/tools/sync-tools.js.map +1 -0
  32. package/dist/auth/github.d.ts +11 -8
  33. package/dist/auth/github.js +56 -75
  34. package/dist/auth/github.js.map +1 -1
  35. package/dist/cli/colors.d.ts +54 -0
  36. package/dist/cli/colors.js +133 -0
  37. package/dist/cli/colors.js.map +1 -0
  38. package/dist/cli/commands/app.js +121 -18
  39. package/dist/cli/commands/app.js.map +1 -1
  40. package/dist/cli/commands/auth.d.ts +1 -1
  41. package/dist/cli/commands/auth.js +26 -10
  42. package/dist/cli/commands/auth.js.map +1 -1
  43. package/dist/cli/commands/clone.d.ts +9 -0
  44. package/dist/cli/commands/clone.js +167 -0
  45. package/dist/cli/commands/clone.js.map +1 -0
  46. package/dist/cli/commands/commit.d.ts +8 -0
  47. package/dist/cli/commands/commit.js +44 -0
  48. package/dist/cli/commands/commit.js.map +1 -0
  49. package/dist/cli/commands/connector.js +10 -2
  50. package/dist/cli/commands/connector.js.map +1 -1
  51. package/dist/cli/commands/deploy.d.ts +4 -6
  52. package/dist/cli/commands/deploy.js +292 -115
  53. package/dist/cli/commands/deploy.js.map +1 -1
  54. package/dist/cli/commands/enrich.d.ts +11 -0
  55. package/dist/cli/commands/enrich.js +135 -0
  56. package/dist/cli/commands/enrich.js.map +1 -0
  57. package/dist/cli/commands/graphrag.d.ts +12 -0
  58. package/dist/cli/commands/graphrag.js +122 -0
  59. package/dist/cli/commands/graphrag.js.map +1 -0
  60. package/dist/cli/commands/init.js +29 -196
  61. package/dist/cli/commands/init.js.map +1 -1
  62. package/dist/cli/commands/join.js +23 -3
  63. package/dist/cli/commands/join.js.map +1 -1
  64. package/dist/cli/commands/orphans.d.ts +8 -0
  65. package/dist/cli/commands/orphans.js +125 -0
  66. package/dist/cli/commands/orphans.js.map +1 -0
  67. package/dist/cli/commands/provision.js +11 -7
  68. package/dist/cli/commands/provision.js.map +1 -1
  69. package/dist/cli/commands/r2.js +8 -2
  70. package/dist/cli/commands/r2.js.map +1 -1
  71. package/dist/cli/commands/redeploy.d.ts +9 -0
  72. package/dist/cli/commands/redeploy.js +203 -0
  73. package/dist/cli/commands/redeploy.js.map +1 -0
  74. package/dist/cli/commands/reset.d.ts +12 -0
  75. package/dist/cli/commands/reset.js +137 -0
  76. package/dist/cli/commands/reset.js.map +1 -0
  77. package/dist/cli/commands/resolve.d.ts +8 -0
  78. package/dist/cli/commands/resolve.js +85 -0
  79. package/dist/cli/commands/resolve.js.map +1 -0
  80. package/dist/cli/commands/review.d.ts +19 -0
  81. package/dist/cli/commands/review.js +128 -0
  82. package/dist/cli/commands/review.js.map +1 -0
  83. package/dist/cli/commands/serve.js +55 -8
  84. package/dist/cli/commands/serve.js.map +1 -1
  85. package/dist/cli/commands/source.d.ts +16 -0
  86. package/dist/cli/commands/source.js +159 -0
  87. package/dist/cli/commands/source.js.map +1 -0
  88. package/dist/cli/commands/start.js +307 -55
  89. package/dist/cli/commands/start.js.map +1 -1
  90. package/dist/cli/commands/sync-entities.d.ts +13 -0
  91. package/dist/cli/commands/sync-entities.js +242 -0
  92. package/dist/cli/commands/sync-entities.js.map +1 -0
  93. package/dist/cli/commands/sync.js +81 -5
  94. package/dist/cli/commands/sync.js.map +1 -1
  95. package/dist/cli/index.js +45 -9
  96. package/dist/cli/index.js.map +1 -1
  97. package/dist/cli/scaffolding.d.ts +2 -1
  98. package/dist/cli/scaffolding.js +38 -23
  99. package/dist/cli/scaffolding.js.map +1 -1
  100. package/dist/cli/setup-wizard.d.ts +30 -0
  101. package/dist/cli/setup-wizard.js +244 -0
  102. package/dist/cli/setup-wizard.js.map +1 -0
  103. package/dist/cli/sync-review-interactive.d.ts +31 -0
  104. package/dist/cli/sync-review-interactive.js +393 -0
  105. package/dist/cli/sync-review-interactive.js.map +1 -0
  106. package/dist/cli/theme.d.ts +31 -0
  107. package/dist/cli/theme.js +116 -0
  108. package/dist/cli/theme.js.map +1 -0
  109. package/dist/core/graph.d.ts +40 -12
  110. package/dist/core/graph.js +344 -104
  111. package/dist/core/graph.js.map +1 -1
  112. package/dist/core/migration-runner.d.ts +4 -1
  113. package/dist/core/migration-runner.js +14 -3
  114. package/dist/core/migration-runner.js.map +1 -1
  115. package/dist/core/types.d.ts +2 -4
  116. package/dist/core/types.js +1 -2
  117. package/dist/core/types.js.map +1 -1
  118. package/dist/core/user-config.d.ts +12 -0
  119. package/dist/core/user-config.js.map +1 -1
  120. package/dist/core/workspace-manager.d.ts +6 -1
  121. package/dist/core/workspace-manager.js +29 -4
  122. package/dist/core/workspace-manager.js.map +1 -1
  123. package/dist/core/workspace.d.ts +13 -5
  124. package/dist/core/workspace.js +115 -33
  125. package/dist/core/workspace.js.map +1 -1
  126. package/dist/lib/lib/utils.d.ts +2 -0
  127. package/dist/lib/lib/utils.js +6 -0
  128. package/dist/lib/lib/utils.js.map +1 -0
  129. package/dist/mcp/connector-manager.d.ts +7 -3
  130. package/dist/mcp/connector-manager.js +61 -16
  131. package/dist/mcp/connector-manager.js.map +1 -1
  132. package/dist/mcp/connectors/asana.d.ts +2 -0
  133. package/dist/mcp/connectors/asana.js +20 -0
  134. package/dist/mcp/connectors/asana.js.map +1 -0
  135. package/dist/mcp/connectors/definitions.d.ts +3 -1
  136. package/dist/mcp/connectors/definitions.js +19 -127
  137. package/dist/mcp/connectors/definitions.js.map +1 -1
  138. package/dist/mcp/connectors/figma.d.ts +5 -0
  139. package/dist/mcp/connectors/figma.js +21 -0
  140. package/dist/mcp/connectors/figma.js.map +1 -0
  141. package/dist/mcp/connectors/gdrive.d.ts +2 -0
  142. package/dist/mcp/connectors/gdrive.js +20 -0
  143. package/dist/mcp/connectors/gdrive.js.map +1 -0
  144. package/dist/mcp/connectors/granola.d.ts +2 -0
  145. package/dist/mcp/connectors/granola.js +12 -0
  146. package/dist/mcp/connectors/granola.js.map +1 -0
  147. package/dist/mcp/connectors/linear.d.ts +2 -0
  148. package/dist/mcp/connectors/linear.js +19 -0
  149. package/dist/mcp/connectors/linear.js.map +1 -0
  150. package/dist/mcp/connectors/obsidian.d.ts +2 -0
  151. package/dist/mcp/connectors/obsidian.js +19 -0
  152. package/dist/mcp/connectors/obsidian.js.map +1 -0
  153. package/dist/mcp/connectors/pipedrive.d.ts +2 -0
  154. package/dist/mcp/connectors/pipedrive.js +20 -0
  155. package/dist/mcp/connectors/pipedrive.js.map +1 -0
  156. package/dist/mcp/connectors/slack.d.ts +2 -0
  157. package/dist/mcp/connectors/slack.js +21 -0
  158. package/dist/mcp/connectors/slack.js.map +1 -0
  159. package/dist/mcp/oauth-provider.d.ts +41 -0
  160. package/dist/mcp/oauth-provider.js +160 -0
  161. package/dist/mcp/oauth-provider.js.map +1 -0
  162. package/dist/server/index.js +97 -11
  163. package/dist/server/index.js.map +1 -1
  164. package/dist/server/plugin-loader.d.ts +8 -0
  165. package/dist/server/plugin-loader.js +54 -2
  166. package/dist/server/plugin-loader.js.map +1 -1
  167. package/dist/server/routes/chat.d.ts +3 -2
  168. package/dist/server/routes/chat.js +67 -16
  169. package/dist/server/routes/chat.js.map +1 -1
  170. package/dist/server/routes/git-api.d.ts +9 -0
  171. package/dist/server/routes/git-api.js +82 -0
  172. package/dist/server/routes/git-api.js.map +1 -0
  173. package/dist/server/routes/graph-api.d.ts +2 -2
  174. package/dist/server/routes/graph-api.js +164 -3
  175. package/dist/server/routes/graph-api.js.map +1 -1
  176. package/dist/server/routes/workspace-api.d.ts +9 -0
  177. package/dist/server/routes/workspace-api.js +170 -0
  178. package/dist/server/routes/workspace-api.js.map +1 -0
  179. package/dist/services/assets/base.d.ts +2 -2
  180. package/dist/services/assets/base.js +4 -4
  181. package/dist/services/assets/base.js.map +1 -1
  182. package/dist/services/assets/index.d.ts +6 -6
  183. package/dist/services/assets/index.js +12 -12
  184. package/dist/services/assets/index.js.map +1 -1
  185. package/dist/services/git.d.ts +31 -0
  186. package/dist/services/git.js +92 -3
  187. package/dist/services/git.js.map +1 -1
  188. package/dist/services/github-provisioner.d.ts +8 -3
  189. package/dist/services/github-provisioner.js +35 -8
  190. package/dist/services/github-provisioner.js.map +1 -1
  191. package/dist/services/lint-service.js +8 -1
  192. package/dist/services/lint-service.js.map +1 -1
  193. package/dist/services/markdown.js +3 -3
  194. package/dist/services/markdown.js.map +1 -1
  195. package/dist/services/memory-service.d.ts +1 -1
  196. package/dist/services/memory-service.js +2 -3
  197. package/dist/services/memory-service.js.map +1 -1
  198. package/dist/services/orphan-service.d.ts +31 -0
  199. package/dist/services/orphan-service.js +100 -0
  200. package/dist/services/orphan-service.js.map +1 -0
  201. package/dist/services/sync/commit.d.ts +60 -0
  202. package/dist/services/sync/commit.js +370 -0
  203. package/dist/services/sync/commit.js.map +1 -0
  204. package/dist/services/sync/context-index.d.ts +69 -0
  205. package/dist/services/sync/context-index.js +280 -0
  206. package/dist/services/sync/context-index.js.map +1 -0
  207. package/dist/services/sync/derive.d.ts +34 -0
  208. package/dist/services/sync/derive.js +164 -0
  209. package/dist/services/sync/derive.js.map +1 -0
  210. package/dist/services/sync/enrichment-state.d.ts +31 -0
  211. package/dist/services/sync/enrichment-state.js +63 -0
  212. package/dist/services/sync/enrichment-state.js.map +1 -0
  213. package/dist/services/sync/enrichment.d.ts +25 -0
  214. package/dist/services/sync/enrichment.js +121 -0
  215. package/dist/services/sync/enrichment.js.map +1 -0
  216. package/dist/services/sync/frontmatter-extractor.d.ts +40 -0
  217. package/dist/services/sync/frontmatter-extractor.js +273 -0
  218. package/dist/services/sync/frontmatter-extractor.js.map +1 -0
  219. package/dist/services/sync/graph-match-state.d.ts +33 -0
  220. package/dist/services/sync/graph-match-state.js +61 -0
  221. package/dist/services/sync/graph-match-state.js.map +1 -0
  222. package/dist/services/sync/graph-match.d.ts +53 -0
  223. package/dist/services/sync/graph-match.js +316 -0
  224. package/dist/services/sync/graph-match.js.map +1 -0
  225. package/dist/services/sync/graphrag-client.d.ts +43 -0
  226. package/dist/services/sync/graphrag-client.js +94 -0
  227. package/dist/services/sync/graphrag-client.js.map +1 -0
  228. package/dist/services/sync/graphrag-config.d.ts +16 -0
  229. package/dist/services/sync/graphrag-config.js +39 -0
  230. package/dist/services/sync/graphrag-config.js.map +1 -0
  231. package/dist/services/sync/graphrag-context.d.ts +14 -0
  232. package/dist/services/sync/graphrag-context.js +109 -0
  233. package/dist/services/sync/graphrag-context.js.map +1 -0
  234. package/dist/services/sync/graphrag-indexer.d.ts +30 -0
  235. package/dist/services/sync/graphrag-indexer.js +358 -0
  236. package/dist/services/sync/graphrag-indexer.js.map +1 -0
  237. package/dist/services/sync/llm.d.ts +32 -0
  238. package/dist/services/sync/llm.js +115 -0
  239. package/dist/services/sync/llm.js.map +1 -0
  240. package/dist/services/sync/mcp-client.d.ts +59 -0
  241. package/dist/services/sync/mcp-client.js +285 -0
  242. package/dist/services/sync/mcp-client.js.map +1 -0
  243. package/dist/services/sync/model-factory.d.ts +10 -0
  244. package/dist/services/sync/model-factory.js +24 -0
  245. package/dist/services/sync/model-factory.js.map +1 -0
  246. package/dist/services/sync/name-quality.d.ts +31 -0
  247. package/dist/services/sync/name-quality.js +60 -0
  248. package/dist/services/sync/name-quality.js.map +1 -0
  249. package/dist/services/sync/output-schemas.d.ts +92 -0
  250. package/dist/services/sync/output-schemas.js +43 -0
  251. package/dist/services/sync/output-schemas.js.map +1 -0
  252. package/dist/services/sync/prompts.d.ts +19 -0
  253. package/dist/services/sync/prompts.js +128 -0
  254. package/dist/services/sync/prompts.js.map +1 -0
  255. package/dist/services/sync/reconciler.d.ts +48 -0
  256. package/dist/services/sync/reconciler.js +295 -0
  257. package/dist/services/sync/reconciler.js.map +1 -0
  258. package/dist/services/sync/source-config.d.ts +45 -0
  259. package/dist/services/sync/source-config.js +208 -0
  260. package/dist/services/sync/source-config.js.map +1 -0
  261. package/dist/services/sync/source-definitions/asana.d.ts +15 -0
  262. package/dist/services/sync/source-definitions/asana.js +48 -0
  263. package/dist/services/sync/source-definitions/asana.js.map +1 -0
  264. package/dist/services/sync/source-definitions/definitions.d.ts +21 -0
  265. package/dist/services/sync/source-definitions/definitions.js +26 -0
  266. package/dist/services/sync/source-definitions/definitions.js.map +1 -0
  267. package/dist/services/sync/source-definitions/gdrive.d.ts +16 -0
  268. package/dist/services/sync/source-definitions/gdrive.js +68 -0
  269. package/dist/services/sync/source-definitions/gdrive.js.map +1 -0
  270. package/dist/services/sync/source-definitions/granola.d.ts +2 -0
  271. package/dist/services/sync/source-definitions/granola.js +28 -0
  272. package/dist/services/sync/source-definitions/granola.js.map +1 -0
  273. package/dist/services/sync/source-definitions/linear.d.ts +2 -0
  274. package/dist/services/sync/source-definitions/linear.js +60 -0
  275. package/dist/services/sync/source-definitions/linear.js.map +1 -0
  276. package/dist/services/sync/source-definitions/obsidian.d.ts +2 -0
  277. package/dist/services/sync/source-definitions/obsidian.js +55 -0
  278. package/dist/services/sync/source-definitions/obsidian.js.map +1 -0
  279. package/dist/services/sync/source-definitions/pipedrive.d.ts +2 -0
  280. package/dist/services/sync/source-definitions/pipedrive.js +52 -0
  281. package/dist/services/sync/source-definitions/pipedrive.js.map +1 -0
  282. package/dist/services/sync/staging.d.ts +53 -0
  283. package/dist/services/sync/staging.js +131 -0
  284. package/dist/services/sync/staging.js.map +1 -0
  285. package/dist/services/sync/structured-extractor.d.ts +49 -0
  286. package/dist/services/sync/structured-extractor.js +344 -0
  287. package/dist/services/sync/structured-extractor.js.map +1 -0
  288. package/dist/services/sync/sync-runner.d.ts +32 -0
  289. package/dist/services/sync/sync-runner.js +195 -0
  290. package/dist/services/sync/sync-runner.js.map +1 -0
  291. package/dist/services/sync/sync-state.d.ts +43 -0
  292. package/dist/services/sync/sync-state.js +154 -0
  293. package/dist/services/sync/sync-state.js.map +1 -0
  294. package/dist/services/sync/types.d.ts +203 -0
  295. package/dist/services/sync/types.js +8 -0
  296. package/dist/services/sync/types.js.map +1 -0
  297. package/dist/services/sync/unstructured-extractor.d.ts +29 -0
  298. package/dist/services/sync/unstructured-extractor.js +197 -0
  299. package/dist/services/sync/unstructured-extractor.js.map +1 -0
  300. package/dist/services/vector-service.d.ts +11 -0
  301. package/dist/services/vector-service.js +42 -0
  302. package/dist/services/vector-service.js.map +1 -1
  303. package/dist/utils/git.d.ts +31 -4
  304. package/dist/utils/git.js +65 -7
  305. package/dist/utils/git.js.map +1 -1
  306. package/dist/utils/merge-resolver.d.ts +60 -0
  307. package/dist/utils/merge-resolver.js +304 -0
  308. package/dist/utils/merge-resolver.js.map +1 -0
  309. package/dist/utils/preflight.d.ts +2 -1
  310. package/dist/utils/preflight.js +8 -1
  311. package/dist/utils/preflight.js.map +1 -1
  312. package/dist/utils/workspace-config.d.ts +8 -0
  313. package/dist/utils/workspace-config.js +22 -0
  314. package/dist/utils/workspace-config.js.map +1 -0
  315. package/dist/web/_app/env.js +1 -0
  316. package/dist/web/_app/immutable/assets/0.BwJV4fvl.css +1 -0
  317. package/dist/web/_app/immutable/assets/2.DRHi7ABa.css +1 -0
  318. package/dist/web/_app/immutable/assets/3.CnAQdPKU.css +1 -0
  319. package/dist/web/_app/immutable/assets/4.BG92MufE.css +1 -0
  320. package/dist/web/_app/immutable/assets/5.CfrQIPYs.css +1 -0
  321. package/dist/web/_app/immutable/assets/ChatPanel.BNzaCFTL.css +1 -0
  322. package/dist/web/_app/immutable/assets/editor.D6Rjo7RE.css +1 -0
  323. package/dist/web/_app/immutable/chunks/4QY4j-jX.js +1 -0
  324. package/dist/web/_app/immutable/chunks/B4G4uWkV.js +1 -0
  325. package/dist/web/_app/immutable/chunks/BB_5th5W.js +3383 -0
  326. package/dist/web/_app/immutable/chunks/CUzqHQY_.js +1 -0
  327. package/dist/web/_app/immutable/chunks/CYrVHOHA.js +1 -0
  328. package/dist/web/_app/immutable/chunks/CksE5s8n.js +1 -0
  329. package/dist/web/_app/immutable/chunks/CqkleIqs.js +1 -0
  330. package/dist/web/_app/immutable/chunks/D0JBkEPE.js +86 -0
  331. package/dist/web/_app/immutable/chunks/D2K5GzLj.js +1 -0
  332. package/dist/web/_app/immutable/chunks/DEkoussQ.js +2 -0
  333. package/dist/web/_app/immutable/chunks/DW-KmHRi.js +1 -0
  334. package/dist/web/_app/immutable/chunks/DcCr4z9k.js +1 -0
  335. package/dist/web/_app/immutable/chunks/Dg2wWwJu.js +2 -0
  336. package/dist/web/_app/immutable/chunks/Dh_H7Owr.js +18 -0
  337. package/dist/web/_app/immutable/chunks/DnlgZ_Tk.js +5 -0
  338. package/dist/web/_app/immutable/chunks/DtVH--hH.js +6 -0
  339. package/dist/web/_app/immutable/chunks/PPVm8Dsz.js +1 -0
  340. package/dist/web/_app/immutable/chunks/SBEriEQR.js +1 -0
  341. package/dist/web/_app/immutable/chunks/WSUKABI_.js +1 -0
  342. package/dist/web/_app/immutable/chunks/YTERt3Ul.js +6 -0
  343. package/dist/web/_app/immutable/chunks/m_R7HatS.js +1 -0
  344. package/dist/web/_app/immutable/chunks/vlmEnjfO.js +1 -0
  345. package/dist/web/_app/immutable/entry/app.B9pwpU_x.js +2 -0
  346. package/dist/web/_app/immutable/entry/start.CDuNCEco.js +1 -0
  347. package/dist/web/_app/immutable/nodes/0.YEQhw1us.js +1 -0
  348. package/dist/web/_app/immutable/nodes/1.CvmHhpRy.js +1 -0
  349. package/dist/web/_app/immutable/nodes/2.c5k1r7dq.js +1 -0
  350. package/dist/web/_app/immutable/nodes/3.B24tcdLK.js +1 -0
  351. package/dist/web/_app/immutable/nodes/4.kX2m5Pp9.js +16 -0
  352. package/dist/web/_app/immutable/nodes/5.DELt6wTD.js +2 -0
  353. package/dist/web/_app/version.json +1 -0
  354. package/dist/web/index.html +41 -0
  355. package/package.json +21 -7
  356. /package/dist/agent/skills/{bundled/gather-context.md → gather-context.md} +0 -0
@@ -4,8 +4,8 @@
4
4
  * Provides unified entity management across any repo type.
5
5
  * Integrates Git, Markdown, and Asset services.
6
6
  */
7
- import { join, basename } from 'path';
8
- import { existsSync, readdirSync, statSync } from 'fs';
7
+ import { join, dirname } from 'path';
8
+ import { existsSync, readdirSync, readFileSync, statSync, writeFileSync, mkdirSync } from 'fs';
9
9
  import { GitService } from '../services/git.js';
10
10
  import { MarkdownService } from '../services/markdown.js';
11
11
  import { AssetService } from '../services/assets/index.js';
@@ -54,33 +54,43 @@ export class BaseGraphManager {
54
54
  }
55
55
  /**
56
56
  * Get entity file path
57
+ *
58
+ * Checks three layouts:
59
+ * 1. Flat folder: {repo}/{entityId}/main.md
60
+ * 2. Type subfolder: {repo}/{entityType}/{entityId}/main.md
61
+ * 3. Collection (flat): {repo}/{entityId}.md
57
62
  */
58
63
  getEntityPath(entityType, entityId) {
59
- // Entity files are stored in {entityType}/{entityId}.md
60
- return join(this.repoPath, entityType, `${entityId}.md`);
61
- }
62
- /**
63
- * Get entity folder path
64
- */
65
- getEntityFolder(entityType) {
66
- return join(this.repoPath, entityType);
67
- }
68
- /**
69
- * Public alias for getEntityPath (used internally by dataset methods)
70
- */
71
- getEntityPathPublic(entityType, entityId) {
72
- return this.getEntityPath(entityType, entityId);
64
+ // 1. Flat folder layout (most common)
65
+ const folderPath = join(this.repoPath, entityId, 'main.md');
66
+ if (existsSync(folderPath))
67
+ return folderPath;
68
+ // 2. Type-based subfolder layout (e.g. projects/deck/sample-deck/main.md)
69
+ if (entityType) {
70
+ const typeFolderPath = join(this.repoPath, entityType, entityId, 'main.md');
71
+ if (existsSync(typeFolderPath))
72
+ return typeFolderPath;
73
+ }
74
+ // 3. Collection mode (flat file)
75
+ const flatPath = join(this.repoPath, `${entityId}.md`);
76
+ if (existsSync(flatPath))
77
+ return flatPath;
78
+ return folderPath; // default to folder layout for new entities
73
79
  }
74
80
  /**
75
81
  * Validate entity data against schema
76
82
  */
77
- validateEntity(entityType, data) {
83
+ validateEntity(entityType, data, lenient) {
78
84
  // Use schemaRegistry when available (supports extended built-in schemas and
79
85
  // custom entity types). Fall back to ENTITY_SCHEMAS, then EntityBaseSchema
80
86
  // for unregistered workspace-specific types.
81
- const schema = this.schemaRegistry?.getSchema(entityType)
82
- ?? ENTITY_SCHEMAS[entityType]
83
- ?? EntityBaseSchema;
87
+ // In lenient mode, only validate base fields so entities missing type-specific
88
+ // required fields (e.g. name) are still discoverable.
89
+ const schema = lenient
90
+ ? EntityBaseSchema
91
+ : (this.schemaRegistry?.getSchema(entityType)
92
+ ?? ENTITY_SCHEMAS[entityType]
93
+ ?? EntityBaseSchema);
84
94
  try {
85
95
  // Add entity_type and preprocess YAML parsing artefacts:
86
96
  // - null → undefined (Zod .optional() doesn't accept null)
@@ -125,7 +135,7 @@ export class BaseGraphManager {
125
135
  * Create a new entity
126
136
  */
127
137
  async create(options) {
128
- const { entityType, entityId, data, content = '', commitMessage } = options;
138
+ const { entityType, entityId, data, content = '', commitMessage, skipCommit } = options;
129
139
  // Validate data
130
140
  const validatedData = this.validateEntity(entityType, data);
131
141
  // Check if entity already exists
@@ -139,7 +149,7 @@ export class BaseGraphManager {
139
149
  content,
140
150
  wikilinks: this.markdownService.extractWikilinks(content, validatedData),
141
151
  });
142
- if (this.gitService) {
152
+ if (this.gitService && !skipCommit) {
143
153
  const transaction = this.gitService.createTransaction();
144
154
  transaction.add({
145
155
  operation: 'create',
@@ -165,8 +175,46 @@ export class BaseGraphManager {
165
175
  throw new Error(`Entity not found: ${entityType}/${entityId}`);
166
176
  }
167
177
  const document = this.markdownService.parseFile(entityPath);
168
- // Validate frontmatter
169
- const validatedData = this.validateEntity(entityType, document.frontmatter);
178
+ // Validate frontmatter — fall back to base schema, then raw data
179
+ // so app-defined entity types missing base fields are still readable
180
+ let validatedData;
181
+ try {
182
+ validatedData = this.validateEntity(entityType, document.frontmatter);
183
+ }
184
+ catch {
185
+ try {
186
+ validatedData = this.validateEntity(entityType, document.frontmatter, true);
187
+ }
188
+ catch {
189
+ // Last resort: use raw frontmatter (e.g. app-defined types without created_by)
190
+ validatedData = document.frontmatter;
191
+ }
192
+ }
193
+ return {
194
+ id: entityId,
195
+ path: entityPath,
196
+ entityType,
197
+ data: validatedData,
198
+ document,
199
+ };
200
+ }
201
+ /**
202
+ * Get an entity by ID only (type resolved from frontmatter)
203
+ */
204
+ getById(entityId) {
205
+ const entityPath = this.getEntityPath('', entityId);
206
+ if (!existsSync(entityPath)) {
207
+ throw new Error(`Entity not found: ${entityId}`);
208
+ }
209
+ const document = this.markdownService.parseFile(entityPath);
210
+ const entityType = document.frontmatter.entity_type ?? this.repoName;
211
+ let validatedData;
212
+ try {
213
+ validatedData = this.validateEntity(entityType, document.frontmatter);
214
+ }
215
+ catch {
216
+ validatedData = this.validateEntity(entityType, document.frontmatter, true);
217
+ }
170
218
  return {
171
219
  id: entityId,
172
220
  path: entityPath,
@@ -175,11 +223,98 @@ export class BaseGraphManager {
175
223
  document,
176
224
  };
177
225
  }
226
+ /**
227
+ * List files and folders alongside main.md in an entity folder.
228
+ * Returns a recursive tree. Collection-mode entities return [].
229
+ */
230
+ listEntityContents(entityType, entityId) {
231
+ const entityPath = this.getEntityPath(entityType, entityId);
232
+ if (!existsSync(entityPath))
233
+ return [];
234
+ // Collection-mode entities (flat .md files) have no sub-content
235
+ if (!entityPath.endsWith('main.md'))
236
+ return [];
237
+ const entityDir = dirname(entityPath);
238
+ const readDir = (dirPath) => {
239
+ const entries = [];
240
+ for (const name of readdirSync(dirPath)) {
241
+ if (name.startsWith('.') || name === 'main.md')
242
+ continue;
243
+ const fullPath = join(dirPath, name);
244
+ try {
245
+ const stat = statSync(fullPath);
246
+ if (stat.isDirectory()) {
247
+ const children = readDir(fullPath);
248
+ // Count all files recursively in this directory
249
+ const countFiles = (items) => items.reduce((n, item) => n + (item.type === 'file' ? 1 : countFiles(item.children ?? [])), 0);
250
+ entries.push({ name, type: 'directory', count: countFiles(children), children });
251
+ }
252
+ else {
253
+ entries.push({ name, type: 'file' });
254
+ }
255
+ }
256
+ catch { /* skip unreadable */ }
257
+ }
258
+ return entries.sort((a, b) => {
259
+ // Directories first, then alphabetical
260
+ if (a.type !== b.type)
261
+ return a.type === 'directory' ? -1 : 1;
262
+ return a.name.localeCompare(b.name);
263
+ });
264
+ };
265
+ return readDir(entityDir);
266
+ }
267
+ /**
268
+ * Read a sub-content file from within an entity folder.
269
+ * Returns the raw file content as a string, or null if not found.
270
+ */
271
+ readEntityContent(entityType, entityId, filePath) {
272
+ const entityPath = this.getEntityPath(entityType, entityId);
273
+ if (!existsSync(entityPath) || !entityPath.endsWith('main.md'))
274
+ return null;
275
+ const entityDir = dirname(entityPath);
276
+ const resolved = join(entityDir, filePath);
277
+ // Prevent path traversal
278
+ if (!resolved.startsWith(entityDir))
279
+ return null;
280
+ if (!existsSync(resolved))
281
+ return null;
282
+ try {
283
+ const stat = statSync(resolved);
284
+ if (!stat.isFile())
285
+ return null;
286
+ return readFileSync(resolved, 'utf-8');
287
+ }
288
+ catch {
289
+ return null;
290
+ }
291
+ }
292
+ /**
293
+ * Write a sub-content file inside an entity folder.
294
+ * Creates parent directories as needed. Prevents path traversal.
295
+ */
296
+ writeEntityContent(entityType, entityId, filePath, content) {
297
+ const entityPath = this.getEntityPath(entityType, entityId);
298
+ if (!existsSync(entityPath) || !entityPath.endsWith('main.md')) {
299
+ throw new Error(`Entity not found or is collection-mode: ${entityType}/${entityId}`);
300
+ }
301
+ const entityDir = dirname(entityPath);
302
+ const resolved = join(entityDir, filePath);
303
+ // Prevent path traversal
304
+ if (!resolved.startsWith(entityDir)) {
305
+ throw new Error('Invalid file path: traversal not allowed');
306
+ }
307
+ const dir = dirname(resolved);
308
+ if (!existsSync(dir)) {
309
+ mkdirSync(dir, { recursive: true });
310
+ }
311
+ writeFileSync(resolved, content, 'utf-8');
312
+ }
178
313
  /**
179
314
  * Update an existing entity
180
315
  */
181
316
  async update(options) {
182
- const { entityType, entityId, data, content, commitMessage } = options;
317
+ const { entityType, entityId, data, content, commitMessage, skipCommit } = options;
183
318
  // Get existing entity
184
319
  const existing = this.get(entityType, entityId);
185
320
  // Merge data (if provided) and add update metadata
@@ -192,8 +327,33 @@ export class BaseGraphManager {
192
327
  updated_by: this.gitUser.id,
193
328
  }
194
329
  : existing.data;
195
- // Validate merged data
196
- const validatedData = this.validateEntity(entityType, mergedData);
330
+ // Validate merged data — use lenient validation when no new data is provided,
331
+ // since the existing entity may have been stored without all required fields.
332
+ // Raw fallback coerces Date objects to YYYY-MM-DD so gray-matter doesn't
333
+ // serialize them as full ISO timestamps.
334
+ let validatedData;
335
+ if (data) {
336
+ validatedData = this.validateEntity(entityType, mergedData);
337
+ }
338
+ else {
339
+ try {
340
+ validatedData = this.validateEntity(entityType, mergedData);
341
+ }
342
+ catch {
343
+ try {
344
+ validatedData = this.validateEntity(entityType, mergedData, true);
345
+ }
346
+ catch {
347
+ const coerced = Object.fromEntries(Object.entries(mergedData).map(([k, v]) => {
348
+ if (v !== null && typeof v === 'object' && Object.prototype.toString.call(v) === '[object Date]') {
349
+ return [k, v.toISOString().split('T')[0]];
350
+ }
351
+ return [k, v];
352
+ }));
353
+ validatedData = coerced;
354
+ }
355
+ }
356
+ }
197
357
  // Merge content (if provided)
198
358
  const mergedContent = content !== undefined ? content : existing.document.content;
199
359
  // Write updated markdown file
@@ -203,7 +363,7 @@ export class BaseGraphManager {
203
363
  content: mergedContent,
204
364
  wikilinks: this.markdownService.extractWikilinks(mergedContent, validatedData),
205
365
  });
206
- if (this.gitService) {
366
+ if (this.gitService && !skipCommit) {
207
367
  const transaction = this.gitService.createTransaction();
208
368
  transaction.add({
209
369
  operation: 'update',
@@ -226,9 +386,14 @@ export class BaseGraphManager {
226
386
  async delete(entityType, entityId, commitMessage) {
227
387
  // Verify entity exists
228
388
  const existing = this.get(entityType, entityId);
229
- // Delete the file
230
- const { unlinkSync } = await import('fs');
231
- unlinkSync(existing.path);
389
+ // Delete the entity directory (derive from resolved path, not hardcoded layout)
390
+ const { rmSync } = await import('fs');
391
+ const entityDir = existing.path.endsWith('.md') && !existing.path.endsWith(`${entityId}.md`)
392
+ ? dirname(existing.path) // folder-mode: main.md → parent dir
393
+ : existing.path.endsWith(`${entityId}.md`)
394
+ ? existing.path // collection-mode: delete the single file
395
+ : join(this.repoPath, entityId);
396
+ rmSync(entityDir, { recursive: true, force: true });
232
397
  if (this.gitService) {
233
398
  const transaction = this.gitService.createTransaction();
234
399
  transaction.add({
@@ -247,73 +412,145 @@ export class BaseGraphManager {
247
412
  * List entities of a given type
248
413
  */
249
414
  list(entityType, limit) {
415
+ if (!existsSync(this.repoPath))
416
+ return [];
250
417
  const results = [];
251
- // If entityType specified, search only that folder
252
- if (entityType) {
253
- const folder = this.getEntityFolder(entityType);
254
- if (!existsSync(folder)) {
255
- return [];
418
+ // Lenient validation for list(): try strict schema first, fall back to
419
+ // EntityBaseSchema, then raw data so app-defined types are still discovered.
420
+ const validateLenient = (type, data) => {
421
+ try {
422
+ return this.validateEntity(type, data);
256
423
  }
257
- const files = readdirSync(folder)
258
- .filter(f => f.endsWith('.md'))
259
- .map(f => {
260
- const entityId = basename(f, '.md');
424
+ catch {
261
425
  try {
262
- return this.get(entityType, entityId);
426
+ return this.validateEntity(type, { ...data, entity_type: type }, true);
263
427
  }
264
428
  catch {
265
- return null;
429
+ return data;
266
430
  }
267
- })
268
- .filter((e) => e !== null);
269
- results.push(...files);
270
- }
271
- else {
272
- // Search all entity type folders — registered types first, then any
273
- // additional subdirectories present in the repo (for custom entity types).
274
- // When a schemaRegistry is present, its custom extension types are also included.
275
- const registeredTypes = new Set([
276
- ...Object.keys(ENTITY_SCHEMAS),
277
- ...(this.schemaRegistry?.getAllTypes() ?? []),
278
- ]);
279
- // Collect: all registered types + any actual subdirectories in the repo
280
- const actualSubdirs = existsSync(this.repoPath)
281
- ? readdirSync(this.repoPath).filter(d => {
282
- if (d.startsWith('.'))
283
- return false;
284
- try {
285
- return statSync(join(this.repoPath, d)).isDirectory();
286
- }
287
- catch {
288
- return false;
431
+ }
432
+ };
433
+ const seenIds = new Set();
434
+ // Helper to process a directory entry as a potential entity
435
+ const tryAddEntity = (dirPath, entry) => {
436
+ const mainPath = join(dirPath, entry, 'main.md');
437
+ if (!existsSync(mainPath))
438
+ return;
439
+ if (seenIds.has(entry))
440
+ return;
441
+ try {
442
+ const doc = this.markdownService.parseFile(mainPath);
443
+ const fmType = doc.frontmatter.entity_type;
444
+ const resolvedType = entityType ?? fmType ?? this.repoName;
445
+ if (entityType && fmType && fmType !== entityType)
446
+ return;
447
+ const validatedData = validateLenient(resolvedType, doc.frontmatter);
448
+ // Count all sub-content files recursively (excluding main.md and dotfiles)
449
+ const entityDir = join(dirPath, entry);
450
+ const countFiles = (dir) => {
451
+ let n = 0;
452
+ for (const f of readdirSync(dir)) {
453
+ if (f.startsWith('.'))
454
+ continue;
455
+ if (dir === entityDir && f === 'main.md')
456
+ continue;
457
+ const full = join(dir, f);
458
+ try {
459
+ if (statSync(full).isDirectory())
460
+ n += countFiles(full);
461
+ else
462
+ n++;
463
+ }
464
+ catch { /* skip */ }
289
465
  }
290
- })
291
- : [];
292
- const allTypes = [...new Set([...registeredTypes, ...actualSubdirs])];
293
- for (const type of allTypes) {
294
- const folder = this.getEntityFolder(type);
295
- if (!existsSync(folder)) {
466
+ return n;
467
+ };
468
+ const fileCount = countFiles(entityDir);
469
+ seenIds.add(entry);
470
+ results.push({
471
+ id: entry,
472
+ path: mainPath,
473
+ entityType: resolvedType,
474
+ data: validatedData,
475
+ document: doc,
476
+ hasSubContent: fileCount > 0,
477
+ subContentCount: fileCount,
478
+ });
479
+ }
480
+ catch { /* skip invalid */ }
481
+ };
482
+ for (const entry of readdirSync(this.repoPath)) {
483
+ if (entry.startsWith('.'))
484
+ continue;
485
+ const fullPath = join(this.repoPath, entry);
486
+ try {
487
+ if (!statSync(fullPath).isDirectory())
296
488
  continue;
297
- }
298
- const files = readdirSync(folder)
299
- .filter(f => f.endsWith('.md'))
300
- .map(f => {
301
- const entityId = basename(f, '.md');
302
- try {
303
- return this.get(type, entityId);
304
- }
305
- catch {
306
- return null;
489
+ }
490
+ catch {
491
+ continue;
492
+ }
493
+ const mainPath = join(fullPath, 'main.md');
494
+ if (existsSync(mainPath)) {
495
+ // Direct entity folder (e.g. projects/acme-corp/main.md)
496
+ tryAddEntity(this.repoPath, entry);
497
+ }
498
+ else {
499
+ // Subfolder containing entities (e.g. projects/deck/ containing sample-deck/main.md)
500
+ try {
501
+ for (const sub of readdirSync(fullPath)) {
502
+ if (sub.startsWith('.'))
503
+ continue;
504
+ try {
505
+ if (!statSync(join(fullPath, sub)).isDirectory())
506
+ continue;
507
+ }
508
+ catch {
509
+ continue;
510
+ }
511
+ tryAddEntity(fullPath, sub);
307
512
  }
308
- })
309
- .filter((e) => e !== null);
310
- results.push(...files);
513
+ }
514
+ catch { /* skip unreadable */ }
311
515
  }
312
516
  }
313
- // Apply limit
314
- if (limit && limit > 0) {
315
- return results.slice(0, limit);
517
+ // Also discover collection-mode entities (flat .md files at repo root)
518
+ for (const entry of readdirSync(this.repoPath)) {
519
+ if (entry.startsWith('.'))
520
+ continue;
521
+ if (!entry.endsWith('.md'))
522
+ continue;
523
+ const fullPath = join(this.repoPath, entry);
524
+ try {
525
+ if (!statSync(fullPath).isFile())
526
+ continue;
527
+ }
528
+ catch {
529
+ continue;
530
+ }
531
+ const id = entry.replace(/\.md$/, '');
532
+ if (seenIds.has(id))
533
+ continue; // folder-mode entity takes precedence
534
+ try {
535
+ const doc = this.markdownService.parseFile(fullPath);
536
+ const fmType = doc.frontmatter.entity_type;
537
+ const resolvedType = entityType ?? fmType ?? this.repoName;
538
+ if (entityType && fmType && fmType !== entityType)
539
+ continue;
540
+ const validatedData = validateLenient(resolvedType, doc.frontmatter);
541
+ seenIds.add(id);
542
+ results.push({
543
+ id,
544
+ path: fullPath,
545
+ entityType: resolvedType,
546
+ data: validatedData,
547
+ document: doc,
548
+ });
549
+ }
550
+ catch { /* skip invalid */ }
316
551
  }
552
+ if (limit && limit > 0)
553
+ return results.slice(0, limit);
317
554
  return results;
318
555
  }
319
556
  /**
@@ -435,10 +672,11 @@ export class BaseGraphManager {
435
672
  // Determine entity type from path
436
673
  const relPath = found.replace(this.repoPath + '/', '');
437
674
  const parts = relPath.split('/');
438
- if (parts.length >= 2) {
439
- const linkedType = parts[0];
440
- const linkedId = basename(parts[1], '.md');
675
+ if (parts.length >= 2 && parts[parts.length - 1] === 'main.md') {
676
+ const linkedId = parts[0];
441
677
  try {
678
+ const doc = this.markdownService.parseFile(found);
679
+ const linkedType = doc.frontmatter.entity_type ?? this.repoName;
442
680
  const linkedEntity = this.get(linkedType, linkedId);
443
681
  related.push(linkedEntity);
444
682
  }
@@ -460,10 +698,11 @@ export class BaseGraphManager {
460
698
  // Determine entity type from path
461
699
  const relPath = file.replace(this.repoPath + '/', '');
462
700
  const parts = relPath.split('/');
463
- if (parts.length >= 2) {
464
- const linkedType = parts[0];
465
- const linkedId = basename(parts[1], '.md');
701
+ if (parts.length >= 2 && parts[parts.length - 1] === 'main.md') {
702
+ const linkedId = parts[0];
466
703
  try {
704
+ const doc = this.markdownService.parseFile(file);
705
+ const linkedType = doc.frontmatter.entity_type ?? this.repoName;
467
706
  const linkedEntity = this.get(linkedType, linkedId);
468
707
  results.push(linkedEntity);
469
708
  }
@@ -494,11 +733,12 @@ export class BaseGraphManager {
494
733
  this.markdownService.updateWikilinks(file, oldId, newId);
495
734
  const relPath = file.replace(this.repoPath + '/', '');
496
735
  const parts = relPath.split('/');
497
- if (parts.length >= 2) {
736
+ if (parts.length >= 2 && parts[parts.length - 1] === 'main.md') {
737
+ const doc = this.markdownService.parseFile(file);
498
738
  transaction.add({
499
739
  operation: 'update',
500
- entityType: parts[0],
501
- entityId: basename(parts[1], '.md'),
740
+ entityType: doc.frontmatter.entity_type ?? parts[0],
741
+ entityId: parts[0],
502
742
  filePath: file,
503
743
  });
504
744
  }
@@ -516,38 +756,38 @@ export class BaseGraphManager {
516
756
  /**
517
757
  * Upload asset for an entity
518
758
  */
519
- async uploadAsset(repoName, entityType, entityId, filename, buffer) {
759
+ async uploadAsset(entityType, entityId, filename, buffer, keyPrefix) {
520
760
  if (!this.assetService) {
521
761
  throw new Error('Asset service is not configured for this graph');
522
762
  }
523
- return await this.assetService.upload(repoName, entityType, entityId, filename, buffer);
763
+ return await this.assetService.upload(entityType, entityId, filename, buffer, keyPrefix);
524
764
  }
525
765
  /**
526
766
  * List assets for an entity
527
767
  */
528
- async listAssets(repoName, entityType, entityId) {
768
+ async listAssets(entityType, entityId, keyPrefix) {
529
769
  if (!this.assetService) {
530
770
  throw new Error('Asset service is not configured for this graph');
531
771
  }
532
- return await this.assetService.list(repoName, entityType, entityId);
772
+ return await this.assetService.list(entityType, entityId, keyPrefix);
533
773
  }
534
774
  /**
535
775
  * Delete an asset
536
776
  */
537
- async deleteAsset(repoName, entityType, entityId, filename) {
777
+ async deleteAsset(entityType, entityId, filename, keyPrefix) {
538
778
  if (!this.assetService) {
539
779
  throw new Error('Asset service is not configured for this graph');
540
780
  }
541
- return await this.assetService.delete(repoName, entityType, entityId, filename);
781
+ return await this.assetService.delete(entityType, entityId, filename, keyPrefix);
542
782
  }
543
783
  /**
544
784
  * Delete all assets for an entity (when entity is deleted)
545
785
  */
546
- async deleteAllAssets(repoName, entityType, entityId) {
786
+ async deleteAllAssets(entityType, entityId, keyPrefix) {
547
787
  if (!this.assetService) {
548
788
  throw new Error('Asset service is not configured for this graph');
549
789
  }
550
- return await this.assetService.deleteAll(repoName, entityType, entityId);
790
+ return await this.assetService.deleteAll(entityType, entityId, keyPrefix);
551
791
  }
552
792
  /**
553
793
  * Get dataset rows, optionally filtered
@@ -587,7 +827,7 @@ export class BaseGraphManager {
587
827
  // Stringify back to CSV
588
828
  const newContent = this.csvService.stringifyRows(allRows, schema);
589
829
  // Update the entity file (update content, bump updated_at)
590
- const entityPath = this.getEntityPathPublic('dataset', entityId);
830
+ const entityPath = this.getEntityPath('dataset', entityId);
591
831
  const updatedData = {
592
832
  ...entity.data,
593
833
  updated_at: new Date().toISOString(),