@amoree-code/ocean 0.0.0-stage → 1.1.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 (304) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +284 -2
  3. package/dist/application/auth/auth-orchestrator.js +77 -0
  4. package/dist/application/clients/clients.js +646 -0
  5. package/dist/application/clients/editors.js +398 -0
  6. package/dist/application/clients/hooks.js +335 -0
  7. package/dist/application/clients/native.js +410 -0
  8. package/dist/application/clients/opencode-plugin.js +158 -0
  9. package/dist/application/clients/registry.js +188 -0
  10. package/dist/application/gateway/webhook-gateway.js +137 -0
  11. package/dist/application/install/provider-installer.js +184 -0
  12. package/dist/application/integrations/charter-copies.js +76 -0
  13. package/dist/application/integrations/claude-vscode.js +106 -0
  14. package/dist/application/integrations/engine-entry-repair.js +105 -0
  15. package/dist/application/integrations/skill-filter.js +254 -0
  16. package/dist/application/layout/layout-apply.js +443 -0
  17. package/dist/application/layout/layout-plan.js +236 -0
  18. package/dist/application/layout/workspace-template.js +34 -0
  19. package/dist/application/onboarding/state.js +23 -0
  20. package/dist/application/onboarding/steps.js +249 -0
  21. package/dist/application/profiles/profile-distribution.js +53 -0
  22. package/dist/application/profiles/profile-suggestion.js +39 -0
  23. package/dist/application/profiles/profile-upgrade-service.js +208 -0
  24. package/dist/application/pull/history-index.js +476 -0
  25. package/dist/application/pull/project-match.js +132 -0
  26. package/dist/application/pull/pull.js +202 -0
  27. package/dist/application/pull/server-capture.js +307 -0
  28. package/dist/application/scheduler/local-scheduler.js +210 -0
  29. package/dist/composition/runtime.js +64 -0
  30. package/dist/engine-root.js +9 -0
  31. package/dist/infrastructure/filesystem/profile-loader.js +46 -0
  32. package/dist/infrastructure/filesystem/runtime-config-loader.js +29 -0
  33. package/dist/infrastructure/filesystem/skill-loader.js +84 -0
  34. package/dist/infrastructure/filesystem/workspace-capability.js +37 -0
  35. package/dist/infrastructure/observability/runtime-logger.js +16 -0
  36. package/dist/infrastructure/process/agent-tool-executor.js +99 -0
  37. package/dist/infrastructure/process/cli-process.js +49 -0
  38. package/dist/infrastructure/process/interactive-process.js +145 -0
  39. package/dist/infrastructure/process/process-supervisor.js +194 -0
  40. package/dist/infrastructure/process/service.js +252 -0
  41. package/dist/infrastructure/providers/browser-provider.js +2 -0
  42. package/dist/infrastructure/providers/claude-sdk.js +137 -0
  43. package/dist/infrastructure/providers/client-home.js +14 -0
  44. package/dist/infrastructure/providers/fake-browser-provider.js +146 -0
  45. package/dist/infrastructure/providers/ollama-embedder.js +74 -0
  46. package/dist/infrastructure/providers/openai-compatible.js +268 -0
  47. package/dist/infrastructure/providers/playwright-browser-provider.js +380 -0
  48. package/dist/infrastructure/providers/provider-capabilities.js +50 -0
  49. package/dist/infrastructure/providers/provider-invocation.js +184 -0
  50. package/dist/infrastructure/providers/provider-registry.js +77 -0
  51. package/dist/infrastructure/providers/providers.js +134 -0
  52. package/dist/infrastructure/wrappers/wrapper-manager.js +374 -0
  53. package/dist/interfaces/cli/auth-command.js +54 -0
  54. package/dist/interfaces/cli/browser-bridge-command.js +93 -0
  55. package/dist/interfaces/cli/browser-command.js +119 -0
  56. package/dist/interfaces/cli/capture-command.js +125 -0
  57. package/dist/interfaces/cli/client-test-command.js +133 -0
  58. package/dist/interfaces/cli/clients-command.js +129 -0
  59. package/dist/interfaces/cli/command-registry.js +1211 -0
  60. package/dist/interfaces/cli/context-command.js +172 -0
  61. package/dist/interfaces/cli/daily-command.js +131 -0
  62. package/dist/interfaces/cli/governance-command.js +55 -0
  63. package/dist/interfaces/cli/handoff-command.js +35 -0
  64. package/dist/interfaces/cli/help-command.js +207 -0
  65. package/dist/interfaces/cli/idea-command.js +47 -0
  66. package/dist/interfaces/cli/intercept-command.js +317 -0
  67. package/dist/interfaces/cli/layout-command.js +27 -0
  68. package/dist/interfaces/cli/market-command.js +52 -0
  69. package/dist/interfaces/cli/mcp-gateway-command.js +30 -0
  70. package/dist/interfaces/cli/memory-command.js +317 -0
  71. package/dist/interfaces/cli/migrate-command.js +26 -0
  72. package/dist/interfaces/cli/observe-command.js +78 -0
  73. package/dist/interfaces/cli/onboard-command.js +123 -0
  74. package/dist/interfaces/cli/operate-command.js +59 -0
  75. package/dist/interfaces/cli/profiles-command.js +70 -0
  76. package/dist/interfaces/cli/pull-command.js +54 -0
  77. package/dist/interfaces/cli/setup-command.js +143 -0
  78. package/dist/interfaces/cli/tasks-command.js +249 -0
  79. package/dist/main.js +49 -0
  80. package/dist/node-warnings.js +17 -0
  81. package/dist/version.js +8 -0
  82. package/market/mcp/chrome-devtools/ocean.capability.json +28 -0
  83. package/market/mcp/gateway/ocean.capability.json +21 -0
  84. package/market/mcp/obsidian/ocean.capability.json +21 -0
  85. package/market/mcp/ocean/ocean.capability.json +21 -0
  86. package/market/mcp/playwright/ocean.capability.json +23 -0
  87. package/node_modules/@ocean/brain/dist/brain/brain-eval.js +29 -0
  88. package/node_modules/@ocean/brain/dist/brain/brain-markdown.js +226 -0
  89. package/node_modules/@ocean/brain/dist/brain/brain-reindex.js +247 -0
  90. package/node_modules/@ocean/brain/dist/brain/brain-service.js +320 -0
  91. package/node_modules/@ocean/brain/dist/capture/inbox-sync.js +22 -0
  92. package/node_modules/@ocean/brain/dist/evidence/verification-report.js +13 -0
  93. package/node_modules/@ocean/brain/dist/infrastructure/brain/brain-index.js +285 -0
  94. package/node_modules/@ocean/brain/dist/memory/brain-dump.js +145 -0
  95. package/node_modules/@ocean/brain/dist/memory/daily-narrative.js +142 -0
  96. package/node_modules/@ocean/brain/dist/memory/index-sync.js +124 -0
  97. package/node_modules/@ocean/brain/dist/memory/lesson-activation.js +93 -0
  98. package/node_modules/@ocean/brain/dist/memory/lesson-learning.js +188 -0
  99. package/node_modules/@ocean/brain/dist/memory/lessons.js +321 -0
  100. package/node_modules/@ocean/brain/dist/memory/memory-audit.js +100 -0
  101. package/node_modules/@ocean/brain/dist/memory/memory-digest.js +49 -0
  102. package/node_modules/@ocean/brain/dist/memory/memory-harvest.js +345 -0
  103. package/node_modules/@ocean/brain/dist/memory/memory-paths.js +174 -0
  104. package/node_modules/@ocean/brain/dist/memory/memory-review.js +278 -0
  105. package/node_modules/@ocean/brain/dist/memory/memory-trash.js +152 -0
  106. package/node_modules/@ocean/brain/dist/memory/memory-write.js +216 -0
  107. package/node_modules/@ocean/brain/dist/memory/model-narrative.js +129 -0
  108. package/node_modules/@ocean/brain/dist/memory/profile-facts.js +103 -0
  109. package/node_modules/@ocean/brain/dist/memory/session-promotion.js +61 -0
  110. package/node_modules/@ocean/brain/dist/memory/session-summary.js +113 -0
  111. package/node_modules/@ocean/brain/dist/memory/signal-detection.js +66 -0
  112. package/node_modules/@ocean/brain/package.json +11 -0
  113. package/node_modules/@ocean/context/dist/context/context-compression.js +66 -0
  114. package/node_modules/@ocean/context/dist/context/context-cost.js +587 -0
  115. package/node_modules/@ocean/context/dist/context/context-ladder.js +363 -0
  116. package/node_modules/@ocean/context/dist/context/context-packet.js +213 -0
  117. package/node_modules/@ocean/context/dist/context/context-references.js +255 -0
  118. package/node_modules/@ocean/context/dist/context/intent-router.js +259 -0
  119. package/node_modules/@ocean/context/dist/context/resource-injection.js +36 -0
  120. package/node_modules/@ocean/context/package.json +11 -0
  121. package/node_modules/@ocean/core/dist/domain/brain/brain-record.js +22 -0
  122. package/node_modules/@ocean/core/dist/domain/brain/record-scope.js +22 -0
  123. package/node_modules/@ocean/core/dist/domain/capabilities/browser-contract.js +64 -0
  124. package/node_modules/@ocean/core/dist/domain/capabilities/capability-contract.js +16 -0
  125. package/node_modules/@ocean/core/dist/domain/capabilities/capability-manifest.js +72 -0
  126. package/node_modules/@ocean/core/dist/domain/context/context-validator.js +5 -0
  127. package/node_modules/@ocean/core/dist/domain/context/context.js +34 -0
  128. package/node_modules/@ocean/core/dist/domain/context/project-id.js +30 -0
  129. package/node_modules/@ocean/core/dist/domain/evidence/evidence.js +16 -0
  130. package/node_modules/@ocean/core/dist/domain/mcp/mcp-contract.js +36 -0
  131. package/node_modules/@ocean/core/dist/domain/mcp/memory-contract.js +41 -0
  132. package/node_modules/@ocean/core/dist/domain/ports/brain-index-port.js +7 -0
  133. package/node_modules/@ocean/core/dist/domain/ports/browser-port.js +8 -0
  134. package/node_modules/@ocean/core/dist/domain/ports/embedder-port.js +2 -0
  135. package/node_modules/@ocean/core/dist/domain/ports/platform-ports.js +2 -0
  136. package/node_modules/@ocean/core/dist/domain/ports/runtime-ports.js +2 -0
  137. package/node_modules/@ocean/core/dist/domain/ports/session-store-port.js +2 -0
  138. package/node_modules/@ocean/core/dist/domain/process/process-events.js +5 -0
  139. package/node_modules/@ocean/core/dist/domain/profiles/profile-distribution.js +19 -0
  140. package/node_modules/@ocean/core/dist/domain/profiles/profile-policy.js +34 -0
  141. package/node_modules/@ocean/core/dist/domain/profiles/profile-selection.js +73 -0
  142. package/node_modules/@ocean/core/dist/domain/profiles/profile-upgrade.js +123 -0
  143. package/node_modules/@ocean/core/dist/domain/profiles/profile-validator.js +5 -0
  144. package/node_modules/@ocean/core/dist/domain/profiles/profile.js +218 -0
  145. package/node_modules/@ocean/core/dist/domain/profiles/runtime-config.js +30 -0
  146. package/node_modules/@ocean/core/dist/domain/providers/provider-capability.js +28 -0
  147. package/node_modules/@ocean/core/dist/domain/providers/provider.js +2 -0
  148. package/node_modules/@ocean/core/dist/domain/redaction/redaction.js +86 -0
  149. package/node_modules/@ocean/core/dist/domain/runs/agent-tools.js +92 -0
  150. package/node_modules/@ocean/core/dist/domain/runs/execution-plan.js +71 -0
  151. package/node_modules/@ocean/core/dist/domain/runs/run-contract.js +38 -0
  152. package/node_modules/@ocean/core/dist/domain/sessions/entry-contract.js +31 -0
  153. package/node_modules/@ocean/core/dist/domain/sessions/handoff.js +78 -0
  154. package/node_modules/@ocean/core/dist/domain/sessions/session.js +58 -0
  155. package/node_modules/@ocean/core/dist/domain/skills/default-skills.js +29 -0
  156. package/node_modules/@ocean/core/dist/domain/skills/skill-validator.js +5 -0
  157. package/node_modules/@ocean/core/dist/domain/skills/skill.js +11 -0
  158. package/node_modules/@ocean/core/dist/domain/tasks/task-id.js +85 -0
  159. package/node_modules/@ocean/core/dist/shared/fs-utils.js +212 -0
  160. package/node_modules/@ocean/core/dist/shared/local-date.js +16 -0
  161. package/node_modules/@ocean/core/dist/shared/paths.js +324 -0
  162. package/node_modules/@ocean/core/dist/shared/project-resolution.js +1065 -0
  163. package/node_modules/@ocean/core/dist/shared/task-reference.js +133 -0
  164. package/node_modules/@ocean/core/package.json +14 -0
  165. package/node_modules/@ocean/loop/dist/handoff/handoff-service.js +133 -0
  166. package/node_modules/@ocean/loop/dist/hooks/lifecycle-hooks.js +15 -0
  167. package/node_modules/@ocean/loop/dist/hooks/session-end-hook.js +183 -0
  168. package/node_modules/@ocean/loop/dist/hooks/session-start-hook.js +237 -0
  169. package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-store.js +690 -0
  170. package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-validator.js +5 -0
  171. package/node_modules/@ocean/loop/dist/loops/iteration-graph.js +147 -0
  172. package/node_modules/@ocean/loop/dist/loops/task-loop-record.js +2 -0
  173. package/node_modules/@ocean/loop/dist/loops/task-loop.js +281 -0
  174. package/node_modules/@ocean/loop/dist/operations/memory-knowledge-operations.js +263 -0
  175. package/node_modules/@ocean/loop/dist/operations/operation-contract.js +227 -0
  176. package/node_modules/@ocean/loop/dist/operations/operation-scope.js +28 -0
  177. package/node_modules/@ocean/loop/dist/operations/project-operations.js +93 -0
  178. package/node_modules/@ocean/loop/dist/operations/record-io.js +65 -0
  179. package/node_modules/@ocean/loop/dist/operations/record-operations.js +45 -0
  180. package/node_modules/@ocean/loop/dist/operations/task-linker.js +40 -0
  181. package/node_modules/@ocean/loop/dist/operations/task-operations.js +279 -0
  182. package/node_modules/@ocean/loop/dist/operations/write-guard.js +157 -0
  183. package/node_modules/@ocean/loop/dist/runs/prompt-assembly.js +112 -0
  184. package/node_modules/@ocean/loop/dist/runs/run-agent.js +425 -0
  185. package/node_modules/@ocean/loop/dist/runs/run-authorization.js +24 -0
  186. package/node_modules/@ocean/loop/dist/runs/tool-loop.js +173 -0
  187. package/node_modules/@ocean/loop/dist/sessions/session-closeout.js +150 -0
  188. package/node_modules/@ocean/loop/dist/sessions/session-pointer.js +133 -0
  189. package/node_modules/@ocean/loop/dist/sessions/session-projects.js +26 -0
  190. package/node_modules/@ocean/loop/dist/sessions/session-retention.js +132 -0
  191. package/node_modules/@ocean/loop/dist/tasks/archive-tasks.js +210 -0
  192. package/node_modules/@ocean/loop/dist/tasks/checkpoint-task.js +31 -0
  193. package/node_modules/@ocean/loop/dist/tasks/claim-task.js +126 -0
  194. package/node_modules/@ocean/loop/dist/tasks/list-tasks.js +113 -0
  195. package/node_modules/@ocean/loop/dist/tasks/new-task.js +42 -0
  196. package/node_modules/@ocean/loop/dist/tasks/renumber-tasks.js +134 -0
  197. package/node_modules/@ocean/loop/dist/tasks/verification-readiness.js +78 -0
  198. package/node_modules/@ocean/loop/dist/tasks/verify-task.js +234 -0
  199. package/node_modules/@ocean/loop/package.json +11 -0
  200. package/node_modules/@ocean/market/dist/adapters/mcp-adapter.js +64 -0
  201. package/node_modules/@ocean/market/dist/adapters/owned-directory.js +89 -0
  202. package/node_modules/@ocean/market/dist/adapters/plugin-adapter.js +39 -0
  203. package/node_modules/@ocean/market/dist/adapters/skill-adapter.js +32 -0
  204. package/node_modules/@ocean/market/dist/capability/adapter.js +2 -0
  205. package/node_modules/@ocean/market/dist/capability/catalog.js +63 -0
  206. package/node_modules/@ocean/market/dist/capability/content-digest.js +40 -0
  207. package/node_modules/@ocean/market/dist/capability/registry.js +81 -0
  208. package/node_modules/@ocean/market/dist/install/default-marketplace.js +47 -0
  209. package/node_modules/@ocean/market/dist/install/lock.js +34 -0
  210. package/node_modules/@ocean/market/dist/install/marketplace-service.js +165 -0
  211. package/node_modules/@ocean/market/dist/mcp/browser-bridge.js +263 -0
  212. package/node_modules/@ocean/market/dist/mcp/mcp-connection.js +67 -0
  213. package/node_modules/@ocean/market/dist/mcp/mcp-gateway.js +317 -0
  214. package/node_modules/@ocean/market/dist/mcp/mcp-launch.js +102 -0
  215. package/node_modules/@ocean/market/dist/mcp/mcp-runner.js +32 -0
  216. package/node_modules/@ocean/market/dist/security/description-scan.js +45 -0
  217. package/node_modules/@ocean/market/dist/security/permissions.js +57 -0
  218. package/node_modules/@ocean/market/dist/security/sandbox.js +74 -0
  219. package/node_modules/@ocean/market/dist/skills/core-skill-sync.js +113 -0
  220. package/node_modules/@ocean/market/dist/skills/signal-detection.js +84 -0
  221. package/node_modules/@ocean/market/dist/skills/skill-curation.js +123 -0
  222. package/node_modules/@ocean/market/dist/skills/skill-hub.js +220 -0
  223. package/node_modules/@ocean/market/dist/skills/task-observer.js +165 -0
  224. package/node_modules/@ocean/market/package.json +11 -0
  225. package/node_modules/@ocean/services/dist/browser/browser-service.js +124 -0
  226. package/node_modules/@ocean/services/dist/browser/browser-session.js +287 -0
  227. package/node_modules/@ocean/services/dist/browser/browser-task-runner.js +70 -0
  228. package/node_modules/@ocean/services/dist/doctor/workspace-doctor.js +547 -0
  229. package/node_modules/@ocean/services/dist/infrastructure/mcp/call-ledger.js +114 -0
  230. package/node_modules/@ocean/services/dist/infrastructure/mcp/mcp-client.js +162 -0
  231. package/node_modules/@ocean/services/dist/infrastructure/mcp/obsidian-server.js +251 -0
  232. package/node_modules/@ocean/services/dist/infrastructure/mcp/ocean-server.js +880 -0
  233. package/node_modules/@ocean/services/dist/infrastructure/mcp/server-version.js +20 -0
  234. package/node_modules/@ocean/services/dist/infrastructure/mcp/tool-registry.js +29 -0
  235. package/node_modules/@ocean/services/dist/obsidian/conflict-log.js +68 -0
  236. package/node_modules/@ocean/services/dist/obsidian/inbox-promotion.js +80 -0
  237. package/node_modules/@ocean/services/dist/obsidian/vault-discovery.js +113 -0
  238. package/node_modules/@ocean/services/dist/obsidian/vault-ingestion.js +77 -0
  239. package/node_modules/@ocean/services/dist/obsidian/vault-sync.js +73 -0
  240. package/node_modules/@ocean/services/dist/obsidian/vault-writer.js +75 -0
  241. package/node_modules/@ocean/services/package.json +11 -0
  242. package/ocean-marketplace.json +415 -0
  243. package/package.json +67 -4
  244. package/skills/core/business-logic/SKILL.md +22 -0
  245. package/skills/core/catch-up/SKILL.md +17 -0
  246. package/skills/core/core-thinking/SKILL.md +11 -0
  247. package/skills/core/design-thinking/SKILL.md +19 -0
  248. package/skills/core/graft/SKILL.md +156 -0
  249. package/skills/core/loop/SKILL.md +20 -0
  250. package/skills/core/session-handoff/SKILL.md +15 -0
  251. package/skills/core/use-browser/SKILL.md +69 -0
  252. package/skills/core/verification/SKILL.md +11 -0
  253. package/skills/index.json +56 -0
  254. package/templates/README.md +4 -0
  255. package/templates/profiles/default.json +51 -0
  256. package/templates/profiles/developer.json +51 -0
  257. package/templates/profiles/reviewer.json +51 -0
  258. package/templates/profiles/strategist.json +51 -0
  259. package/templates/workspace/00-inbox/INBOX.md +7 -0
  260. package/templates/workspace/00-inbox/candidates/.gitkeep +0 -0
  261. package/templates/workspace/00-inbox/native/.gitkeep +0 -0
  262. package/templates/workspace/01-daily/.gitkeep +0 -0
  263. package/templates/workspace/02-personal/CONFLICTS.md +7 -0
  264. package/templates/workspace/02-personal/MEMORY.md +15 -0
  265. package/templates/workspace/02-personal/goals/.gitkeep +0 -0
  266. package/templates/workspace/02-personal/identity/.gitkeep +0 -0
  267. package/templates/workspace/02-personal/preferences/.gitkeep +0 -0
  268. package/templates/workspace/03-professional/.gitkeep +0 -0
  269. package/templates/workspace/04-projects/PROJECTS.md +5 -0
  270. package/templates/workspace/04-projects/_template/docs/.gitkeep +0 -0
  271. package/templates/workspace/04-projects/_template/memory/.gitkeep +0 -0
  272. package/templates/workspace/04-projects/_template/tasks/.gitkeep +0 -0
  273. package/templates/workspace/05-knowledge/KNOWLEDGE.md +8 -0
  274. package/templates/workspace/05-knowledge/decisions/.gitkeep +0 -0
  275. package/templates/workspace/05-knowledge/lessons/.gitkeep +0 -0
  276. package/templates/workspace/06-templates/TEMPLATES.md +25 -0
  277. package/templates/workspace/06-templates/decision.md +8 -0
  278. package/templates/workspace/06-templates/intelligence.md +32 -0
  279. package/templates/workspace/06-templates/project-claude.md +48 -0
  280. package/templates/workspace/06-templates/session.md +38 -0
  281. package/templates/workspace/06-templates/task.md +42 -0
  282. package/templates/workspace/MANIFEST.json +51 -0
  283. package/templates/workspace/bridge/config/CONFIG.md +21 -0
  284. package/templates/workspace/bridge/config/runtime.json +4 -0
  285. package/templates/workspace/charter/core.md +104 -0
  286. package/templates/workspace/charter/policies/README.md +31 -0
  287. package/templates/workspace/charter/policies/claude-code-context.md +90 -0
  288. package/templates/workspace/charter/policies/concise-output.md +29 -0
  289. package/templates/workspace/charter/policies/context.md +91 -0
  290. package/templates/workspace/charter/policies/contracts/task.md +189 -0
  291. package/templates/workspace/charter/policies/git.md +60 -0
  292. package/templates/workspace/charter/policies/graph.md +47 -0
  293. package/templates/workspace/charter/policies/knowledge.md +33 -0
  294. package/templates/workspace/charter/policies/lifecycle.md +119 -0
  295. package/templates/workspace/charter/policies/memory.md +67 -0
  296. package/templates/workspace/charter/policies/minimal-change.md +43 -0
  297. package/templates/workspace/charter/policies/models.md +57 -0
  298. package/templates/workspace/charter/policies/privacy-terms.txt +3 -0
  299. package/templates/workspace/charter/policies/response.md +39 -0
  300. package/templates/workspace/charter/policies/skill-observer.md +44 -0
  301. package/templates/workspace/charter/policies/stack.md +23 -0
  302. package/templates/workspace/charter/policies/task.md +139 -0
  303. package/templates/workspace/charter/policies/verification.md +29 -0
  304. package/templates/workspace/sessions/.gitkeep +0 -0
@@ -0,0 +1,476 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { copyFile, mkdir, mkdtemp, open, readdir, readFile, rm, stat, } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { DatabaseSync } from "node:sqlite";
6
+ import { fileURLToPath } from "node:url";
7
+ import zlib from "node:zlib";
8
+ import { redactSecrets } from "@ocean/core/domain/redaction/redaction.js";
9
+ import { atomicWrite } from "@ocean/core/shared/fs-utils.js";
10
+ import { oceanPath, SYSTEM_DIR } from "@ocean/core/shared/paths.js";
11
+ import { loadProjectIndex, placeFolder } from "./project-match.js";
12
+ const HEAD_BYTES = 64 * 1024;
13
+ const TITLE_CHARS = 120;
14
+ export const historyIndexFile = () => oceanPath(SYSTEM_DIR, "state", "history", "sessions.jsonl");
15
+ function title(text) {
16
+ if (typeof text !== "string")
17
+ return "";
18
+ return redactSecrets(text.replace(/\s+/g, " ").trim()).slice(0, TITLE_CHARS);
19
+ }
20
+ // The texts of a chat message's content: a string, or the text parts of a list.
21
+ function contentTexts(content) {
22
+ if (typeof content === "string")
23
+ return [content];
24
+ if (!Array.isArray(content))
25
+ return [];
26
+ return content.flatMap((part) => part && typeof part === "object" && typeof part.text === "string"
27
+ ? [part.text]
28
+ : []);
29
+ }
30
+ // What the owner asked: the first message text left once injected context (leading
31
+ // `<tag>…</tag>` blocks: environment, opened file, reminders, command echoes) is taken off.
32
+ const LEADING_BLOCK = /^\s*<([A-Za-z][\w-]*)[^>]*>[\s\S]*?<\/\1>/;
33
+ function firstPrompt(contents) {
34
+ for (const content of contents)
35
+ for (let text of contentTexts(content)) {
36
+ while (LEADING_BLOCK.test(text))
37
+ text = text.replace(LEADING_BLOCK, "");
38
+ if (text.trim() && !/^\s*(<|# AGENTS\.md|Caveat:)/.test(text))
39
+ return text;
40
+ }
41
+ return undefined;
42
+ }
43
+ // Codex compresses older sessions (`.jsonl.zst`); zstd is in node:zlib from Node 22.15. Without
44
+ // it such a file is still indexed, untitled.
45
+ const zstd = zlib
46
+ .createZstdDecompress;
47
+ async function zstdHead(file, bytes) {
48
+ if (!zstd)
49
+ return [];
50
+ const chunks = [];
51
+ let size = 0;
52
+ const source = createReadStream(file);
53
+ const stream = source.pipe(zstd());
54
+ try {
55
+ for await (const chunk of stream) {
56
+ chunks.push(chunk);
57
+ size += chunk.length;
58
+ if (size >= bytes)
59
+ break;
60
+ }
61
+ }
62
+ finally {
63
+ source.destroy();
64
+ stream.destroy();
65
+ }
66
+ const lines = Buffer.concat(chunks)
67
+ .subarray(0, bytes)
68
+ .toString("utf8")
69
+ .split("\n");
70
+ return size >= bytes ? lines.slice(0, -1) : lines;
71
+ }
72
+ async function head(file, bytes = HEAD_BYTES) {
73
+ if (file.endsWith(".zst"))
74
+ return zstdHead(file, bytes);
75
+ const handle = await open(file, "r");
76
+ try {
77
+ const buffer = Buffer.alloc(bytes);
78
+ const { bytesRead } = await handle.read(buffer, 0, bytes, 0);
79
+ const lines = buffer.subarray(0, bytesRead).toString("utf8").split("\n");
80
+ // The last line may be cut off mid-record.
81
+ return bytesRead === bytes ? lines.slice(0, -1) : lines;
82
+ }
83
+ finally {
84
+ await handle.close();
85
+ }
86
+ }
87
+ // The end of a long transcript, where titles set later in the session are appended.
88
+ async function tail(file) {
89
+ const handle = await open(file, "r");
90
+ try {
91
+ const { size } = await handle.stat();
92
+ if (size <= HEAD_BYTES)
93
+ return [];
94
+ const buffer = Buffer.alloc(HEAD_BYTES);
95
+ const { bytesRead } = await handle.read(buffer, 0, HEAD_BYTES, size - HEAD_BYTES);
96
+ // The first line may start mid-record.
97
+ return buffer.subarray(0, bytesRead).toString("utf8").split("\n").slice(1);
98
+ }
99
+ finally {
100
+ await handle.close();
101
+ }
102
+ }
103
+ function records(lines) {
104
+ const out = [];
105
+ for (const line of lines) {
106
+ if (!line.trim())
107
+ continue;
108
+ try {
109
+ const value = JSON.parse(line);
110
+ if (value && typeof value === "object")
111
+ out.push(value);
112
+ }
113
+ catch {
114
+ // a partial or foreign line
115
+ }
116
+ }
117
+ return out;
118
+ }
119
+ async function filesUnder(directory, match, depth) {
120
+ const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
121
+ const found = [];
122
+ for (const entry of entries) {
123
+ const target = path.join(directory, entry.name);
124
+ if (entry.isFile() && match(entry.name))
125
+ found.push(target);
126
+ else if (depth > 0 && entry.isDirectory() && !entry.name.startsWith("."))
127
+ found.push(...(await filesUnder(target, match, depth - 1)));
128
+ }
129
+ return found;
130
+ }
131
+ const at = (value) => {
132
+ const time = typeof value === "number" ? value : Date.parse(String(value ?? ""));
133
+ return Number.isFinite(time) ? new Date(time).toISOString() : undefined;
134
+ };
135
+ const object = (value) => value && typeof value === "object" ? value : {};
136
+ const readers = [
137
+ {
138
+ client: "claude-code",
139
+ files: (home) => filesUnder(path.join(home, ".claude", "projects"), (name) => name.endsWith(".jsonl"), 1),
140
+ async parse(file) {
141
+ const lines = records(await head(file));
142
+ const end = records(await tail(file));
143
+ // The title the owner set, then Claude Code's own, then the first prompt.
144
+ const named = (type, key) => [...lines, ...end].reverse().find((line) => line.type === type)?.[key];
145
+ const prompt = firstPrompt(lines
146
+ .filter((line) => line.type === "user" && !line.isMeta)
147
+ .map((line) => object(line.message).content));
148
+ return {
149
+ cwd: lines.find((line) => typeof line.cwd === "string")?.cwd,
150
+ title: title(named("custom-title", "customTitle") ??
151
+ named("ai-title", "aiTitle") ??
152
+ named("summary", "summary") ??
153
+ prompt),
154
+ };
155
+ },
156
+ },
157
+ {
158
+ client: "codex",
159
+ files: (home) => filesUnder(path.join(home, ".codex", "sessions"), (name) => name.startsWith("rollout-") && /\.jsonl(\.zst)?$/.test(name), 4),
160
+ async parse(file) {
161
+ // The first prompt follows tens of kilobytes of instructions and injected context.
162
+ const lines = records(await head(file, 4 * HEAD_BYTES));
163
+ const meta = object(lines.find((line) => line.type === "session_meta")?.payload);
164
+ const prompt = firstPrompt(lines
165
+ .map((line) => object(line.payload))
166
+ .filter((item) => item.type === "message" && item.role === "user")
167
+ .map((item) => item.content));
168
+ return {
169
+ id: typeof meta.id === "string" ? meta.id : undefined,
170
+ cwd: typeof meta.cwd === "string" ? meta.cwd : undefined,
171
+ title: title(prompt),
172
+ };
173
+ },
174
+ },
175
+ {
176
+ // Codex's own one-page summary of each past session.
177
+ client: "codex-summary",
178
+ files: (home) => filesUnder(path.join(home, ".codex", "memories", "rollout_summaries"), (name) => name.endsWith(".md"), 0),
179
+ async parse(file) {
180
+ const slug = path
181
+ .basename(file, ".md")
182
+ .replace(/^\d{4}-\d\d-\d\dT[\d-]+-[^-]+-/, "");
183
+ return { title: title(slug.replace(/_/g, " ")) };
184
+ },
185
+ },
186
+ {
187
+ client: "gemini",
188
+ files: (home) => filesUnder(path.join(home, ".gemini", "tmp"), (name) => name.startsWith("session") && name.endsWith(".jsonl"), 2),
189
+ async parse(file) {
190
+ // One `$set` line can hold every message so far: read the whole file, within a bound.
191
+ const lines = records(await head(file, 32 * HEAD_BYTES));
192
+ const first = lines[0] ?? {};
193
+ const root = await readFile(path.join(path.dirname(path.dirname(file)), ".project_root"), "utf8").catch(() => "");
194
+ // Messages are lines of their own, or batched under `$set.messages`.
195
+ const messages = lines.flatMap((line) => {
196
+ const batch = object(line.$set).messages;
197
+ return Array.isArray(batch) ? batch.map(object) : [line];
198
+ });
199
+ const prompt = firstPrompt(messages
200
+ .filter((message) => message.type === "user")
201
+ .map((message) => message.content));
202
+ return {
203
+ id: typeof first.sessionId === "string" ? first.sessionId : undefined,
204
+ cwd: root.trim() || undefined,
205
+ updated: at(first.lastUpdated),
206
+ title: title(prompt),
207
+ };
208
+ },
209
+ },
210
+ {
211
+ client: "cursor",
212
+ files: (home) => filesUnder(path.join(home, ".cursor", "chats"), (name) => name === "meta.json", 2),
213
+ async parse(file) {
214
+ const meta = object(JSON.parse(await readFile(file, "utf8")));
215
+ return {
216
+ id: path.basename(path.dirname(file)),
217
+ cwd: typeof meta.cwd === "string" ? meta.cwd : undefined,
218
+ updated: at(meta.updatedAtMs),
219
+ title: title(meta.title),
220
+ };
221
+ },
222
+ },
223
+ {
224
+ // Claude Desktop's Code sessions; their transcripts are Claude Code's, under ~/.claude.
225
+ client: "claude-desktop",
226
+ files: (home) => filesUnder(path.join(home, "Library", "Application Support", "Claude", "claude-code-sessions"), (name) => name.startsWith("local_") && name.endsWith(".json"), 2),
227
+ async parse(file) {
228
+ const session = object(JSON.parse(await readFile(file, "utf8")));
229
+ return {
230
+ id: typeof session.sessionId === "string" ? session.sessionId : undefined,
231
+ cwd: typeof session.cwd === "string" ? session.cwd : undefined,
232
+ updated: at(session.lastActivityAt),
233
+ title: title(session.title),
234
+ cliSession: typeof session.cliSessionId === "string"
235
+ ? session.cliSessionId
236
+ : undefined,
237
+ };
238
+ },
239
+ },
240
+ ];
241
+ // Antigravity lists its conversations in one SQLite file (the transcripts are protobuf). It is
242
+ // read from a private copy: the app keeps it open, and reading in place may need to write its
243
+ // lock files.
244
+ async function antigravityEntries(home) {
245
+ const folder = path.join(home, ".gemini", "antigravity");
246
+ const file = path.join(folder, "conversation_summaries.db");
247
+ if (!(await stat(file).catch(() => null)))
248
+ return [];
249
+ const scratch = await mkdtemp(path.join(os.tmpdir(), "ocean-agy-"));
250
+ let db;
251
+ try {
252
+ for (const suffix of ["", "-wal", "-shm"])
253
+ await copyFile(`${file}${suffix}`, path.join(scratch, `summaries.db${suffix}`)).catch(() => undefined);
254
+ db = new DatabaseSync(path.join(scratch, "summaries.db"), {
255
+ readOnly: true,
256
+ });
257
+ const rows = db
258
+ .prepare("select conversation_id, title, preview, last_modified_time, workspace_uris from conversation_summaries")
259
+ .all();
260
+ const transcripts = new Set(await readdir(path.join(folder, "conversations")).catch(() => []));
261
+ return rows.map((row) => {
262
+ const id = String(row.conversation_id);
263
+ let cwd;
264
+ try {
265
+ const uri = JSON.parse(String(row.workspace_uris || "[]"))[0];
266
+ cwd = uri?.startsWith("file:") ? fileURLToPath(uri) : undefined;
267
+ }
268
+ catch {
269
+ // no workspace recorded
270
+ }
271
+ const transcript = [`${id}.pb`, `${id}.db`].find((name) => transcripts.has(name));
272
+ return {
273
+ id,
274
+ file: transcript
275
+ ? path.join(folder, "conversations", transcript)
276
+ : `${file}#${id}`,
277
+ cwd,
278
+ updated: at(String(row.last_modified_time ?? "").replace(" ", "T")),
279
+ title: title(String(row.title || row.preview || "")),
280
+ version: String(row.last_modified_time),
281
+ };
282
+ });
283
+ }
284
+ catch {
285
+ // another schema: nothing indexed this time
286
+ return [];
287
+ }
288
+ finally {
289
+ db?.close();
290
+ await rm(scratch, { recursive: true, force: true });
291
+ }
292
+ }
293
+ // OpenCode keeps its sessions in one SQLite file; each row is a conversation.
294
+ async function openCodeEntries(home) {
295
+ const file = path.join(home, ".local", "share", "opencode", "opencode.db");
296
+ if (!(await stat(file).catch(() => null)))
297
+ return [];
298
+ let db;
299
+ try {
300
+ db = new DatabaseSync(file, { readOnly: true });
301
+ const rows = db
302
+ .prepare("select id, title, directory, time_updated from session")
303
+ .all();
304
+ return rows.map((row) => ({
305
+ id: String(row.id),
306
+ file: `${file}#${String(row.id)}`,
307
+ cwd: typeof row.directory === "string" ? row.directory : undefined,
308
+ updated: at(row.time_updated),
309
+ title: title(row.title),
310
+ version: String(row.time_updated),
311
+ }));
312
+ }
313
+ catch {
314
+ // another schema or a locked file: nothing indexed this time
315
+ return [];
316
+ }
317
+ finally {
318
+ db?.close();
319
+ }
320
+ }
321
+ // Conversations that live only on a provider's servers (claude.ai, ChatGPT) have no local file.
322
+ // A capture writes one snapshot per client, `<system>/state/history/server/<client>.jsonl`, one
323
+ // line per conversation: {id, title, url, updated, project?, tags?}. The index keeps the link.
324
+ export const serverHistoryDir = () => oceanPath(SYSTEM_DIR, "state", "history", "server");
325
+ async function serverEntries() {
326
+ const directory = serverHistoryDir();
327
+ const names = await readdir(directory).catch(() => []);
328
+ const rows = [];
329
+ for (const name of names.filter((name) => name.endsWith(".jsonl"))) {
330
+ const text = await readFile(path.join(directory, name), "utf8").catch(() => "");
331
+ for (const row of records(text.split("\n"))) {
332
+ if (typeof row.id !== "string" || typeof row.url !== "string")
333
+ continue;
334
+ const updated = at(row.updated);
335
+ rows.push({
336
+ client: name.slice(0, -".jsonl".length),
337
+ id: row.id,
338
+ file: row.url,
339
+ updated,
340
+ title: title(row.title),
341
+ project: typeof row.project === "string" ? row.project : undefined,
342
+ tags: Array.isArray(row.tags)
343
+ ? row.tags.filter((tag) => typeof tag === "string")
344
+ : undefined,
345
+ version: updated ?? "",
346
+ });
347
+ }
348
+ }
349
+ return rows;
350
+ }
351
+ async function readIndex() {
352
+ const text = await readFile(historyIndexFile(), "utf8").catch(() => "");
353
+ return records(text.split("\n"));
354
+ }
355
+ // Rebuilds the index; a transcript whose size and time are unchanged is not read again, so a
356
+ // daily run reads only what is new. Projects are matched afresh every time: a folder bound since
357
+ // the last run moves its conversations.
358
+ export async function buildHistoryIndex(options = {}) {
359
+ const home = options.home ?? os.homedir();
360
+ const shown = (file) => file.startsWith(home) ? `~${file.slice(home.length)}` : file;
361
+ const previous = new Map((await readIndex()).map((entry) => [entry.file, entry]));
362
+ const projects = await loadProjectIndex(home);
363
+ const pulledAt = new Date().toISOString();
364
+ const entries = [];
365
+ const add = (client, file, parsed, fallback) => {
366
+ const place = placeFolder(projects, parsed.cwd);
367
+ const known = previous.get(file);
368
+ const updated = parsed.updated ?? fallback.updated;
369
+ const tags = parsed.tags ?? place.tags;
370
+ entries.push({
371
+ client,
372
+ project: parsed.project ?? place.project ?? "unassigned",
373
+ ...(tags?.length ? { tags } : {}),
374
+ pull: true,
375
+ date: updated.slice(0, 10),
376
+ title: parsed.title ?? "",
377
+ file,
378
+ ...(place.cwd ? { cwd: place.cwd } : {}),
379
+ id: parsed.id ??
380
+ path.basename(file).replace(/\.jsonl(\.zst)?$|\.[^.]+$/, ""),
381
+ updated,
382
+ // First seen, kept across runs.
383
+ pulled_at: known?.pulled_at ?? pulledAt,
384
+ bytes: fallback.bytes,
385
+ ...(parsed.cliSession ? { cliSession: parsed.cliSession } : {}),
386
+ version: fallback.version,
387
+ });
388
+ };
389
+ for (const reader of readers)
390
+ for (const file of await reader.files(home)) {
391
+ const info = await stat(file).catch(() => null);
392
+ if (!info)
393
+ continue;
394
+ const version = `${info.size}:${info.mtimeMs}`;
395
+ const known = previous.get(shown(file));
396
+ const parsed = known?.client === reader.client && known.version === version
397
+ ? {
398
+ id: known.id,
399
+ cwd: known.cwd,
400
+ updated: known.updated,
401
+ title: known.title,
402
+ cliSession: known.cliSession,
403
+ }
404
+ : await reader.parse(file).catch(() => ({}));
405
+ add(reader.client, shown(file), parsed, {
406
+ updated: info.mtime.toISOString(),
407
+ bytes: info.size,
408
+ version,
409
+ });
410
+ }
411
+ for (const row of await openCodeEntries(home))
412
+ add("opencode", shown(row.file), row, {
413
+ updated: new Date(0).toISOString(),
414
+ bytes: 0,
415
+ version: row.version,
416
+ });
417
+ for (const row of await antigravityEntries(home))
418
+ add("antigravity", shown(row.file), row, {
419
+ updated: new Date(0).toISOString(),
420
+ bytes: 0,
421
+ version: row.version,
422
+ });
423
+ for (const row of await serverEntries())
424
+ add(row.client, row.file, row, {
425
+ updated: new Date(0).toISOString(),
426
+ bytes: 0,
427
+ version: row.version,
428
+ });
429
+ // Claude Desktop's Code sessions are Claude Code sessions: one line, marked.
430
+ const claudeCode = new Map(entries
431
+ .filter((entry) => entry.client === "claude-code")
432
+ .map((entry) => [entry.id, entry]));
433
+ const merged = entries.filter((entry) => {
434
+ const same = entry.client === "claude-desktop" && entry.cliSession
435
+ ? claudeCode.get(entry.cliSession)
436
+ : undefined;
437
+ if (!same)
438
+ return true;
439
+ same.alsoIn = ["claude-desktop"];
440
+ if (!same.title)
441
+ same.title = entry.title;
442
+ return false;
443
+ });
444
+ merged.sort((a, b) => b.updated.localeCompare(a.updated));
445
+ if (options.write) {
446
+ await mkdir(path.dirname(historyIndexFile()), {
447
+ recursive: true,
448
+ mode: 0o700,
449
+ });
450
+ await atomicWrite(historyIndexFile(), merged.map((entry) => JSON.stringify(entry)).join("\n") +
451
+ (merged.length ? "\n" : ""), 0o600);
452
+ }
453
+ return merged;
454
+ }
455
+ // Every word must appear in the title, project, tags, folder, client or file; newest first.
456
+ export async function searchHistory(query, options = {}) {
457
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
458
+ return (await readIndex())
459
+ .filter((entry) => !options.client || entry.client === options.client)
460
+ .filter((entry) => !options.project || entry.project === options.project)
461
+ .filter((entry) => {
462
+ const text = [
463
+ entry.title,
464
+ entry.project,
465
+ ...(entry.tags ?? []),
466
+ entry.cwd ?? "",
467
+ entry.client,
468
+ entry.file,
469
+ ]
470
+ .join(" ")
471
+ .toLowerCase();
472
+ return words.every((word) => text.includes(word));
473
+ })
474
+ .slice(0, options.limit ?? 20);
475
+ }
476
+ //# sourceMappingURL=history-index.js.map
@@ -0,0 +1,132 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { expandHome, listProjectManifests, projectFormerPaths, projectTags, } from "@ocean/core/shared/project-resolution.js";
4
+ export const UNASSIGNED = "unassigned";
5
+ export const SCRATCH = "scratch";
6
+ const lower = (value) => value.toLowerCase();
7
+ export async function loadProjectIndex(home = os.homedir()) {
8
+ const projects = [];
9
+ for (const manifest of await listProjectManifests()) {
10
+ // A malformed optional list must not drop the project from sorting.
11
+ const safe = (read) => {
12
+ try {
13
+ return read();
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ };
19
+ const former = safe(() => projectFormerPaths(manifest));
20
+ const folders = [...manifest.paths, ...former].map((entry) => path.resolve(expandHome(entry)));
21
+ projects.push({
22
+ id: manifest.id,
23
+ // A project is also known by its old folders' names (atlas for ocean) — not by its
24
+ // current folders' names, which can be as generic as `dashboard`.
25
+ names: [
26
+ ...new Set([
27
+ manifest.id,
28
+ manifest.name,
29
+ ...former.map((entry) => path.basename(expandHome(entry))),
30
+ ]
31
+ .filter(Boolean)
32
+ .map(lower)),
33
+ ],
34
+ tags: safe(() => projectTags(manifest)),
35
+ folders,
36
+ });
37
+ }
38
+ return { projects, home };
39
+ }
40
+ // `folder` is `root` or below it, in the platform's own separator (Windows resolves to `\`).
41
+ const under = (folder, root) => folder === root ||
42
+ folder.startsWith(root.endsWith(path.sep) ? root : `${root}${path.sep}`);
43
+ // macOS folder names ignore case: ~/Documents/ameer/Ocean-UIUX-MCP is the bound ocean-UIUX-MCP.
44
+ const within = (folder, root) => under(lower(folder), lower(root));
45
+ function isScratch(folder, home) {
46
+ const roots = [
47
+ os.tmpdir(),
48
+ ...["/tmp", "/private/tmp", "/var/folders", "/private/var/folders"].map((root) => path.resolve(root)),
49
+ path.join(home, "Library", "Application Support", "Claude", "scratch-workspaces"),
50
+ ];
51
+ return (folder === path.parse(folder).root ||
52
+ folder === home ||
53
+ roots.some((root) => under(folder, root)) ||
54
+ /(^|[-_/])scratch([-_/]|$)/i.test(path.basename(folder)));
55
+ }
56
+ // A conversation's working folder → its project.
57
+ export function placeFolder(index, cwd) {
58
+ if (!cwd)
59
+ return { project: UNASSIGNED };
60
+ const folder = path.resolve(expandHome(cwd.trim()));
61
+ const shown = folder.startsWith(index.home)
62
+ ? `~${folder.slice(index.home.length)}`
63
+ : folder;
64
+ let best;
65
+ for (const project of index.projects)
66
+ for (const root of project.folders)
67
+ if (within(folder, root) && root.length > (best?.length ?? -1))
68
+ best = { id: project.id, tags: project.tags, length: root.length };
69
+ if (best)
70
+ return { project: best.id, tags: best.tags, cwd: shown, target: true };
71
+ if (isScratch(folder, index.home))
72
+ return { project: SCRATCH, cwd: shown };
73
+ // The deepest folder on the path named like a project (moved folders keep their names).
74
+ for (const part of folder.split(path.sep).filter(Boolean).reverse()) {
75
+ const project = index.projects.find((item) => item.names.includes(lower(part.trim())));
76
+ if (project)
77
+ return {
78
+ project: project.id,
79
+ tags: project.tags,
80
+ cwd: shown,
81
+ target: true,
82
+ };
83
+ }
84
+ return { project: UNASSIGNED, cwd: shown };
85
+ }
86
+ // The project whose name appears in a phrase as whole words; the longest name wins, so
87
+ // "Ocean IELTS plan" is ocean-ielts, not ocean.
88
+ const words = (value) => `-${value
89
+ .toLowerCase()
90
+ .replace(/[^a-z0-9]+/g, "-")
91
+ .replace(/^-+|-+$/g, "")}-`;
92
+ function projectNamedIn(index, phrase) {
93
+ const text = words(phrase);
94
+ return index.projects
95
+ .flatMap((project) => project.names.map((name) => ({ project, name: words(name) })))
96
+ .filter(({ name }) => name !== "--" && text.includes(name))
97
+ .sort((a, b) => b.name.length - a.name.length)[0]?.project;
98
+ }
99
+ // Claude Code names a project's folder by its path with every non-alphanumeric as `-`.
100
+ const claudeDir = (folder) => folder.replace(/[^A-Za-z0-9]/g, "-");
101
+ // A memory text → its project: Claude Code's per-project memory by its folder, a claude.ai
102
+ // project by its name, anything else by a project named in its first heading.
103
+ export function placeMemory(index, origin) {
104
+ if (origin.client === "claude-code") {
105
+ const dir = /\.claude[\\/]projects[\\/]([^\\/]+)[\\/]memory[\\/]/.exec(origin.file)?.[1];
106
+ if (dir) {
107
+ let best;
108
+ for (const project of index.projects)
109
+ for (const root of project.folders) {
110
+ const encoded = claudeDir(root);
111
+ if ((lower(dir) === lower(encoded) ||
112
+ lower(dir).startsWith(`${lower(encoded)}-`)) &&
113
+ encoded.length > (best?.length ?? -1))
114
+ best = {
115
+ id: project.id,
116
+ tags: project.tags,
117
+ length: encoded.length,
118
+ };
119
+ }
120
+ if (best)
121
+ return { project: best.id, tags: best.tags, target: true };
122
+ return { project: UNASSIGNED };
123
+ }
124
+ }
125
+ const named = origin.name ?? /^#{1,6}\s+(.+)$/m.exec(origin.text)?.[1];
126
+ const project = named ? projectNamedIn(index, named) : undefined;
127
+ if (project)
128
+ return { project: project.id, tags: project.tags, target: true };
129
+ // Global memory and instructions are about the owner, not one project.
130
+ return { project: "personal" };
131
+ }
132
+ //# sourceMappingURL=project-match.js.map