@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,150 @@
1
+ import { writeBrainDump } from "@ocean/brain/memory/brain-dump.js";
2
+ import { appendDailyNarrative, appendObservations, } from "@ocean/brain/memory/daily-narrative.js";
3
+ import { learnFromSession, } from "@ocean/brain/memory/lesson-learning.js";
4
+ import { generateModelNarrative } from "@ocean/brain/memory/model-narrative.js";
5
+ import { writeSessionSummary, } from "@ocean/brain/memory/session-summary.js";
6
+ import { safeJsonParse } from "@ocean/core/shared/fs-utils.js";
7
+ import { gitChangedFiles } from "@ocean/core/shared/project-resolution.js";
8
+ import { createHandoffWithStore } from "../handoff/handoff-service.js";
9
+ async function changedFiles(workingDirectory) {
10
+ return (await gitChangedFiles(workingDirectory)).slice(0, 100);
11
+ }
12
+ function lastEvent(events, type) {
13
+ return [...events].reverse().find((event) => event.type === type);
14
+ }
15
+ function evidence(events, result) {
16
+ return events
17
+ .filter((event) => event.type === "evidence" || event.type === "provider_blocked")
18
+ .map((event) => {
19
+ const value = safeJsonParse(event.data);
20
+ if (event.type === "provider_blocked")
21
+ return result === "blocked"
22
+ ? String(value?.reason ?? event.data)
23
+ : null;
24
+ const proven = value?.result === "proven";
25
+ if (result === "proven" && proven)
26
+ return String(value?.criterion ?? "verified result");
27
+ if (result === "notProven" && !proven)
28
+ return String(value?.criterion ?? value?.result ?? "not proven");
29
+ return null;
30
+ })
31
+ .filter((value) => Boolean(value))
32
+ .slice(0, 12);
33
+ }
34
+ export async function finalizeSession(store, sessionId, input = {}) {
35
+ const session = store.get(sessionId);
36
+ if (!session)
37
+ throw new Error(`Session not found: ${sessionId}`);
38
+ // finalizeSession can legitimately be invoked more than once for the same session
39
+ // (e.g. a caller's success path completes closeout, then a later step in that same
40
+ // caller throws and its catch block calls finalizeSession again). The summary/handoff
41
+ // work below is already idempotent (guarded by existing handoffId / session_summary
42
+ // event checks), but the daily-narrative append is a pure side effect with no such
43
+ // check, so a re-run would otherwise write a duplicate "Work log" line. closedAt is
44
+ // only ever set by the updateCloseout call further down, so its presence here means
45
+ // this session has already been closed out once.
46
+ const alreadyClosedOut = Boolean(session.closedAt);
47
+ const events = store.listEvents(sessionId);
48
+ const exitEvent = lastEvent(events, "process_exit");
49
+ const exitCode = input.exitCode ??
50
+ (exitEvent
51
+ ? Number(safeJsonParse(exitEvent.data)?.exitCode ?? 1)
52
+ : session.status === "completed"
53
+ ? 0
54
+ : 1);
55
+ const closeoutStatus = session.status === "completed" && exitCode === 0 ? "completed" : "failed";
56
+ const files = await changedFiles(session.workingDirectory);
57
+ const summary = await writeSessionSummary({
58
+ session,
59
+ events,
60
+ changedFiles: files,
61
+ exitCode,
62
+ nextAction: input.nextAction,
63
+ });
64
+ let handoffId = session.handoffId;
65
+ const enoughEvidence = Boolean(session.taskId ||
66
+ (closeoutStatus === "completed" &&
67
+ events.some((event) => ["user_input", "provider_output", "text", "json"].includes(event.type))));
68
+ if (!handoffId && enoughEvidence) {
69
+ try {
70
+ const handoff = await createHandoffWithStore(store, {
71
+ sessionId,
72
+ taskId: session.taskId ?? undefined,
73
+ nextAction: input.nextAction,
74
+ sourceSummaryPath: summary.summaryPath,
75
+ changedFiles: files,
76
+ verification: evidence(events, "proven"),
77
+ notProven: evidence(events, "notProven"),
78
+ blocked: evidence(events, "blocked"),
79
+ });
80
+ handoffId = handoff.handoffId;
81
+ }
82
+ catch (error) {
83
+ store.appendEvent(sessionId, "closeout_warning", error instanceof Error ? error.message : String(error));
84
+ }
85
+ }
86
+ const closedAt = new Date().toISOString();
87
+ store.updateCloseout(sessionId, { ...summary, closeoutStatus, closedAt });
88
+ if (!alreadyClosedOut) {
89
+ // Computed first so writeBrainDump can surface the same signals as the
90
+ // daily narrative, but kept in its own try/catch: a failure here must
91
+ // not skip the narrative/summary work below, and vice versa.
92
+ let observations = [];
93
+ try {
94
+ const learning = await learnFromSession(store, sessionId);
95
+ observations = learning.created;
96
+ // Only when the session changed the store: a no-op closeout adds no event.
97
+ if (learning.created.length ||
98
+ learning.confirmed.length ||
99
+ learning.contradicted.length ||
100
+ learning.promoted.length ||
101
+ learning.errors.length)
102
+ store.appendEvent(sessionId, "lesson_scored", JSON.stringify({
103
+ verified: learning.score.verified,
104
+ created: learning.created.map((lesson) => lesson.id),
105
+ confirmed: learning.confirmed,
106
+ contradicted: learning.contradicted,
107
+ promoted: learning.promoted,
108
+ errors: learning.errors,
109
+ }));
110
+ await appendObservations(session, observations);
111
+ }
112
+ catch (error) {
113
+ store.appendEvent(sessionId, "closeout_warning", error instanceof Error ? error.message : String(error));
114
+ }
115
+ try {
116
+ const narrative = await generateModelNarrative({
117
+ session,
118
+ events,
119
+ changedFiles: files,
120
+ nextAction: input.nextAction,
121
+ });
122
+ await appendDailyNarrative({
123
+ session,
124
+ events,
125
+ exitCode,
126
+ nextAction: input.nextAction,
127
+ narrative,
128
+ });
129
+ await writeBrainDump({
130
+ session,
131
+ events,
132
+ changedFiles: files,
133
+ exitCode,
134
+ nextAction: input.nextAction,
135
+ narrative,
136
+ observations,
137
+ });
138
+ }
139
+ catch (error) {
140
+ store.appendEvent(sessionId, "closeout_warning", error instanceof Error ? error.message : String(error));
141
+ }
142
+ }
143
+ if (!store
144
+ .listEvents(sessionId)
145
+ .some((event) => event.type === "session_summary")) {
146
+ store.appendEvent(sessionId, "session_summary", JSON.stringify({ ...summary, handoffId, closeoutStatus, closedAt }));
147
+ }
148
+ return { ...summary, handoffId, closeoutStatus };
149
+ }
150
+ //# sourceMappingURL=session-closeout.js.map
@@ -0,0 +1,133 @@
1
+ import path from "node:path";
2
+ import { validateBudget } from "@ocean/context/context/context-ladder.js";
3
+ import { sameProject } from "@ocean/core/shared/paths.js";
4
+ import { resolveProject } from "@ocean/core/shared/project-resolution.js";
5
+ // Sessions are created with randomUUID(), so a pointer that is not a UUID did not come from
6
+ // Ocean and is refused before any lookup.
7
+ const SESSION_ID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8
+ const STALE_RESUME_MS = 24 * 60 * 60 * 1000;
9
+ const MAX_NEXT_ACTION_CHARS = 200;
10
+ export function validateSessionIdentifier(sessionId) {
11
+ if (typeof sessionId !== "string" || sessionId.length === 0)
12
+ return { valid: false, reason: "session pointer is missing" };
13
+ if (sessionId.includes("\0"))
14
+ return { valid: false, reason: "session pointer contains a null byte" };
15
+ if (!SESSION_ID_SHAPE.test(sessionId))
16
+ return {
17
+ valid: false,
18
+ reason: `session pointer '${sessionId}' is not a valid Ocean session identifier`,
19
+ };
20
+ return { valid: true, value: sessionId };
21
+ }
22
+ function refused(reason, violations = []) {
23
+ return {
24
+ ok: false,
25
+ mode: "refused",
26
+ reason,
27
+ pointer: null,
28
+ parentSessionId: null,
29
+ violations,
30
+ };
31
+ }
32
+ function clip(value) {
33
+ return value.length > MAX_NEXT_ACTION_CHARS
34
+ ? `${value.slice(0, MAX_NEXT_ACTION_CHARS)}…`
35
+ : value;
36
+ }
37
+ async function projectIdFor(directory) {
38
+ const resolution = await resolveProject(directory);
39
+ return resolution.status === "bound" ? resolution.projectId : null;
40
+ }
41
+ // Builds the compact pointer for a session that is already known to exist. Carries only
42
+ // references: summaryPath is a path, not the summary; nextAction is clipped.
43
+ export async function buildSessionPointer(session) {
44
+ return {
45
+ sessionId: session.sessionId,
46
+ parentSessionId: session.parentSessionId,
47
+ projectId: await projectIdFor(session.workingDirectory),
48
+ provider: session.provider,
49
+ status: session.status,
50
+ taskId: session.taskId,
51
+ checkpointRef: session.summaryPath,
52
+ nextAction: clip(session.nextAction ?? ""),
53
+ updatedAt: session.updatedAt,
54
+ };
55
+ }
56
+ // Deterministic, read-only resume planning. Budget is validated before the store is read.
57
+ export async function planSessionResume(store, sessionId, budget, options = {}) {
58
+ const budgetCheck = validateBudget(budget);
59
+ if (!budgetCheck.valid)
60
+ return refused(`invalid-budget: ${budgetCheck.reason}`, ["invalid-budget"]);
61
+ const identifier = validateSessionIdentifier(sessionId);
62
+ if (!identifier.valid)
63
+ return refused(identifier.reason);
64
+ const session = store.get(identifier.value);
65
+ if (!session)
66
+ return refused(`session ${identifier.value} does not exist in Ocean`);
67
+ const pointer = await buildSessionPointer(session);
68
+ const cwd = options.cwd;
69
+ if (cwd) {
70
+ const currentProject = await projectIdFor(path.resolve(cwd));
71
+ if (pointer.projectId &&
72
+ currentProject &&
73
+ !sameProject(pointer.projectId, currentProject)) {
74
+ return refused(`cross-project resume refused: session belongs to '${pointer.projectId}', current directory resolves to '${currentProject}'`);
75
+ }
76
+ }
77
+ if (options.requestedProject &&
78
+ pointer.projectId &&
79
+ !sameProject(options.requestedProject, pointer.projectId)) {
80
+ return refused(`cross-project resume refused: session belongs to '${pointer.projectId}', request targeted '${options.requestedProject}'`);
81
+ }
82
+ const now = options.now ?? Date.now();
83
+ const age = now - Date.parse(session.updatedAt);
84
+ if (session.status === "created") {
85
+ return refused(`session ${identifier.value} never started (status: created) — nothing to resume`);
86
+ }
87
+ if (session.status === "failed" || session.status === "cancelled") {
88
+ return refused(`session ${identifier.value} ended in terminal status '${session.status}' — a new session must be started explicitly, Ocean will not reassign it automatically`);
89
+ }
90
+ if (session.status === "running" &&
91
+ Number.isFinite(age) &&
92
+ age > STALE_RESUME_MS) {
93
+ return refused(`session ${identifier.value} is stale: last updated ${Math.floor(age / 3_600_000)}h ago while still marked running — run 'ocean session doctor' before resuming`, ["stale-session"]);
94
+ }
95
+ const mode = session.status === "running" ? "attach-child" : "pointer-only";
96
+ const plan = {
97
+ ok: true,
98
+ mode,
99
+ reason: mode === "attach-child"
100
+ ? `session ${identifier.value} is live; a child session may attach to it`
101
+ : `session ${identifier.value} is closed cleanly; a new child session may point at its checkpoint without replaying it`,
102
+ pointer,
103
+ parentSessionId: identifier.value,
104
+ violations: [],
105
+ };
106
+ const bounded = budget;
107
+ const serialized = JSON.stringify(plan).length;
108
+ if (serialized > bounded.maxChars) {
109
+ return refused(`resume plan is ${serialized} chars, budget.maxChars allows ${bounded.maxChars}`, ["max-chars-exceeded"]);
110
+ }
111
+ return plan;
112
+ }
113
+ // Validates an explicitly supplied parent before a caller passes it to store.create().
114
+ // A missing parent is reported as "none", never inferred from the most recent session.
115
+ export function validateParentSession(store, parentSessionId, childSessionId) {
116
+ if (parentSessionId === null || parentSessionId === undefined) {
117
+ return { ok: true, parentSessionId: null };
118
+ }
119
+ const identifier = validateSessionIdentifier(parentSessionId);
120
+ if (!identifier.valid)
121
+ return { ok: false, reason: identifier.reason };
122
+ if (childSessionId && childSessionId === identifier.value) {
123
+ return { ok: false, reason: "a session cannot be its own parent" };
124
+ }
125
+ const parent = store.get(identifier.value);
126
+ if (!parent)
127
+ return {
128
+ ok: false,
129
+ reason: `parent session ${identifier.value} does not exist in Ocean`,
130
+ };
131
+ return { ok: true, parentSessionId: identifier.value };
132
+ }
133
+ //# sourceMappingURL=session-pointer.js.map
@@ -0,0 +1,26 @@
1
+ import { folderProject } from "@ocean/core/shared/project-resolution.js";
2
+ // The project to record on a new session. Recording a session must not fail on registry state,
3
+ // so an unreadable registry yields null; the next `ocean migrate --apply` fills it.
4
+ export async function sessionProjectId(cwd) {
5
+ try {
6
+ return await folderProject(cwd);
7
+ }
8
+ catch {
9
+ return null;
10
+ }
11
+ }
12
+ // Fill project_id on sessions recorded before the column existed, then on the handoffs that
13
+ // came from them. An unreadable registry throws and aborts: never guess a folder's project.
14
+ export async function backfillSessionProjects(store) {
15
+ let sessions = 0;
16
+ let unresolved = 0;
17
+ for (const directory of store.sessionDirectoriesMissingProject()) {
18
+ const project = await folderProject(directory);
19
+ if (project)
20
+ sessions += store.setProjectForDirectory(directory, project);
21
+ else
22
+ unresolved += 1;
23
+ }
24
+ return { sessions, handoffs: store.backfillHandoffProjects(), unresolved };
25
+ }
26
+ //# sourceMappingURL=session-projects.js.map
@@ -0,0 +1,132 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, rm, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { writeSessionSummary } from "@ocean/brain/memory/session-summary.js";
5
+ import { oceanPath, SYSTEM_DIR } from "@ocean/core/shared/paths.js";
6
+ // Session retention (T-228): summarize finished, unsummarized sessions to markdown, then
7
+ // prune their raw provider_output/terminal_input events (never a session row, a handoff, an
8
+ // idea, or a capture-referenced event), then checkpoint+VACUUM. Default mode is dry-run
9
+ // (planRetention only); simulate measures a VACUUMed copy; apply is fingerprint-gated and is
10
+ // never invoked against the live sessions.sqlite by this task — only against fixture
11
+ // databases in tests.
12
+ export const DEFAULT_KEEP_DAYS = 7;
13
+ function computeFingerprint(before, types, sessionIds, eventIds) {
14
+ const sorted = [...sessionIds].sort();
15
+ // Not Math.max(...eventIds): spreading tens of thousands of event ids as call arguments
16
+ // overflows the engine's argument-count limit (RangeError: Maximum call stack size
17
+ // exceeded) on a real corpus, even though small fixture-scale tests never hit it.
18
+ let maxEventId = 0;
19
+ for (const id of eventIds)
20
+ if (id > maxEventId)
21
+ maxEventId = id;
22
+ return createHash("sha256")
23
+ .update(JSON.stringify({
24
+ before,
25
+ types: [...types].sort(),
26
+ sessionIds: sorted,
27
+ eventCount: eventIds.length,
28
+ maxEventId,
29
+ }))
30
+ .digest("hex");
31
+ }
32
+ export function planRetention(store, options = {}) {
33
+ const keepDays = options.keepDays ?? DEFAULT_KEEP_DAYS;
34
+ const before = options.before ??
35
+ new Date((options.now ?? Date.now()) - keepDays * 24 * 60 * 60 * 1000).toISOString();
36
+ const sessions = store.retentionEligibleSessions(before);
37
+ const sessionIds = sessions.map((session) => session.sessionId);
38
+ const needSummary = sessions
39
+ .filter((session) => !session.summaryPath)
40
+ .map((session) => session.sessionId);
41
+ const eventIds = store.retentionPrunableEventIds(sessionIds);
42
+ const dataBytes = store.eventsDataBytes(eventIds);
43
+ const RAW_EVENT_TYPES = ["provider_output", "terminal_input"];
44
+ return {
45
+ before,
46
+ keepDays,
47
+ sessionIds,
48
+ needSummary,
49
+ eventIds,
50
+ dataBytes,
51
+ fingerprint: computeFingerprint(before, RAW_EVENT_TYPES, sessionIds, eventIds),
52
+ };
53
+ }
54
+ // Measures the effect of applying `plan` against a throwaway VACUUMed COPY of the database
55
+ // file backing `store` — the live file is never touched. The copy lives under a private,
56
+ // mode-0700 directory (never /tmp), and is always removed, even on error.
57
+ export async function simulateRetention(store, _sourceFile, plan, openStore) {
58
+ const simDir = oceanPath(SYSTEM_DIR, "sessions", ".compaction-sim");
59
+ await mkdir(simDir, { recursive: true, mode: 0o700 });
60
+ const copyPath = path.join(simDir, `sim-${process.pid}-${Date.now()}.sqlite`);
61
+ await rm(copyPath, { force: true });
62
+ try {
63
+ await store.backupTo(copyPath);
64
+ const beforeBytes = (await stat(copyPath)).size;
65
+ const copyStore = openStore(copyPath);
66
+ try {
67
+ copyStore.deleteEvents(plan.eventIds);
68
+ copyStore.checkpointAndVacuum();
69
+ }
70
+ finally {
71
+ copyStore.close();
72
+ }
73
+ const afterBytes = (await stat(copyPath)).size;
74
+ return {
75
+ beforeBytes,
76
+ afterBytes,
77
+ freedBytes: beforeBytes - afterBytes,
78
+ plan,
79
+ };
80
+ }
81
+ finally {
82
+ await rm(copyPath, { force: true });
83
+ await rm(`${copyPath}-wal`, { force: true });
84
+ await rm(`${copyPath}-shm`, { force: true });
85
+ }
86
+ }
87
+ // Fingerprint-gated: refuses to run if `fingerprint` does not match a freshly recomputed
88
+ // plan for the same `before`/keepDays — the database must not have moved out from under the
89
+ // plan between planning and applying. Backs up first, summarizes any unsummarized finished
90
+ // session (verifying the written file's hash before trusting it), deletes only the planned
91
+ // event ids, appends one audit event per session, then checkpoints+VACUUMs.
92
+ export async function applyRetention(store, sourceFile, plan, fingerprint) {
93
+ const fresh = planRetention(store, {
94
+ keepDays: plan.keepDays,
95
+ before: plan.before,
96
+ });
97
+ if (fresh.fingerprint !== fingerprint ||
98
+ fresh.fingerprint !== plan.fingerprint) {
99
+ throw new Error("retention plan fingerprint mismatch — the database changed since this plan was made; refusing to apply a stale plan");
100
+ }
101
+ const backupDir = oceanPath(SYSTEM_DIR, "sessions", ".backups");
102
+ await mkdir(backupDir, { recursive: true, mode: 0o700 });
103
+ const backupPath = path.join(backupDir, `sessions-${Date.now()}.sqlite`);
104
+ await store.backupTo(backupPath);
105
+ const beforeBytes = (await stat(sourceFile)).size;
106
+ const summarized = [];
107
+ for (const sessionId of plan.needSummary) {
108
+ const session = store.get(sessionId);
109
+ if (!session)
110
+ continue;
111
+ const events = store.listEvents(sessionId);
112
+ const written = await writeSessionSummary({ session, events });
113
+ store.setSummary(sessionId, written);
114
+ summarized.push(sessionId);
115
+ }
116
+ store.deleteEvents(plan.eventIds);
117
+ // Retention's audit event is best-effort and coarse (the total pruned count for the whole
118
+ // plan, not a precise per-session breakdown), recorded once against every planned session.
119
+ for (const sessionId of plan.sessionIds) {
120
+ store.recordRetentionPrune(sessionId, plan.eventIds.length, plan.before);
121
+ }
122
+ store.checkpointAndVacuum();
123
+ const afterBytes = (await stat(sourceFile)).size;
124
+ return {
125
+ backupPath,
126
+ summarized,
127
+ eventsDeleted: plan.eventIds.length,
128
+ beforeBytes,
129
+ afterBytes,
130
+ };
131
+ }
132
+ //# sourceMappingURL=session-retention.js.map
@@ -0,0 +1,210 @@
1
+ import { access, mkdir, readdir, readFile, rename, rmdir, writeFile, } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { resolveWithin, WORKSPACE_PROJECT_ID, workspaceTasksRoot, } from "@ocean/core/shared/paths.js";
4
+ import { projectArchiveName, WORKSPACE_ARCHIVE_NAMESPACE, } from "@ocean/core/shared/task-reference.js";
5
+ import { verifyTask } from "./verify-task.js";
6
+ const field = (source, name) => source
7
+ .match(new RegExp(`^${name}:\\s*(.+)$`, "m"))?.[1]
8
+ ?.trim()
9
+ .replace(/^['"]|['"]$/g, "") ?? "";
10
+ function setState(source, state) {
11
+ return source.replace(/^state:\s*.+$/m, `state: ${state}`);
12
+ }
13
+ function section(source, heading) {
14
+ const header = new RegExp(`^## ${heading}[ \\t]*\\r?$`, "m").exec(source);
15
+ if (!header)
16
+ return undefined;
17
+ const start = source.indexOf("\n", header.index) + 1;
18
+ const nextHeading = /^## /m.exec(source.slice(start));
19
+ return source.slice(start, nextHeading ? start + nextHeading.index : source.length);
20
+ }
21
+ function completionIssues(source) {
22
+ const issues = [];
23
+ if (/^\s*-\s+"?\[ \] /m.test(source))
24
+ issues.push("unchecked work");
25
+ const blockers = section(source, "Blockers");
26
+ if (blockers === undefined) {
27
+ issues.push("missing Blockers section");
28
+ }
29
+ else {
30
+ const entries = blockers
31
+ .split("\n")
32
+ .map((line) => line.trim())
33
+ .filter(Boolean);
34
+ const open = entries.filter((line) => {
35
+ if (/^(?:[-*]\s*)?(?:none|no blockers?)\.?$/i.test(line))
36
+ return false;
37
+ const checkbox = line.match(/^[-*]\s+\[([ xX])\]\s+.+$/);
38
+ if (checkbox)
39
+ return checkbox[1].toLowerCase() !== "x";
40
+ return true;
41
+ });
42
+ if (open.length)
43
+ issues.push("unresolved blockers");
44
+ }
45
+ const definition = section(source, "Definition of done");
46
+ if (definition === undefined) {
47
+ issues.push("missing Definition of done section");
48
+ }
49
+ else {
50
+ const criteria = definition
51
+ .split("\n")
52
+ .map((line) => line.trim())
53
+ .filter(Boolean);
54
+ if (!criteria.length) {
55
+ issues.push("missing acceptance criteria");
56
+ }
57
+ else {
58
+ const unchecked = criteria.some((line) => {
59
+ const checkbox = line.match(/^[-*]\s+\[([ xX])\]\s+.+$/);
60
+ if (!checkbox)
61
+ return true;
62
+ return checkbox[1].toLowerCase() !== "x";
63
+ });
64
+ const malformed = criteria.some((line) => !/^[-*]\s+\[([ xX])\]\s+.+$/.test(line));
65
+ if (malformed)
66
+ issues.push("acceptance criteria must use checked boxes");
67
+ else if (unchecked)
68
+ issues.push("unchecked acceptance criteria");
69
+ }
70
+ }
71
+ return issues;
72
+ }
73
+ export async function completeTask(id, root = workspaceTasksRoot(), options = {}) {
74
+ const taskFile = resolveWithin(root, id, "task.md");
75
+ const source = await readFile(taskFile, "utf8");
76
+ const issues = completionIssues(source);
77
+ if (issues.length)
78
+ throw new Error(`Cannot complete ${id}: ${issues.join("; ")}`);
79
+ const outcome = await verifyTask(id, root, {
80
+ cwd: options.cwd,
81
+ run: options.run,
82
+ });
83
+ if (!outcome.passed)
84
+ throw new Error(`Cannot complete ${id}: ${outcome.reason}`);
85
+ if (field(source, "state") !== "done")
86
+ await writeFile(taskFile, setState(source, "done"));
87
+ const archived = await archiveDoneTasks(root, true, {
88
+ ...options,
89
+ verified: new Set([id]),
90
+ only: new Set([id]),
91
+ });
92
+ const skipped = archived.skipped.find((entry) => entry.id === id);
93
+ if (skipped) {
94
+ await writeFile(taskFile, source, "utf8");
95
+ throw new Error(`Cannot complete ${id}: ${skipped.reason}`);
96
+ }
97
+ return { id, state: "done", ...archived };
98
+ }
99
+ export async function archiveDoneTasks(root = workspaceTasksRoot(), apply = false, options = {}) {
100
+ const result = {
101
+ candidates: [],
102
+ skipped: [],
103
+ moved: [],
104
+ repaired: [],
105
+ };
106
+ const archiveRoot = resolveWithin(root, "archive");
107
+ let entries;
108
+ try {
109
+ entries = await readdir(root, { withFileTypes: true });
110
+ }
111
+ catch (error) {
112
+ if (error.code === "ENOENT")
113
+ return result;
114
+ throw error;
115
+ }
116
+ for (const entry of entries) {
117
+ if (!entry.isDirectory() || entry.name === "archive")
118
+ continue;
119
+ const sourceDirectory = resolveWithin(root, entry.name);
120
+ const taskFile = resolveWithin(root, entry.name, "task.md");
121
+ try {
122
+ await access(taskFile);
123
+ }
124
+ catch {
125
+ const archivedDirectory = resolveWithin(archiveRoot, WORKSPACE_ARCHIVE_NAMESPACE, entry.name);
126
+ try {
127
+ await access(path.join(archivedDirectory, "task.md"));
128
+ }
129
+ catch {
130
+ continue; // not archived
131
+ }
132
+ if (!apply)
133
+ continue;
134
+ for (const artifact of await readdir(sourceDirectory, {
135
+ withFileTypes: true,
136
+ })) {
137
+ const destination = resolveWithin(archivedDirectory, artifact.name);
138
+ try {
139
+ await access(destination);
140
+ result.skipped.push({
141
+ id: entry.name,
142
+ reason: `archive artifact exists: ${artifact.name}`,
143
+ });
144
+ continue;
145
+ }
146
+ catch (error) {
147
+ if (error.code !== "ENOENT")
148
+ throw error;
149
+ }
150
+ await rename(path.join(sourceDirectory, artifact.name), destination);
151
+ }
152
+ try {
153
+ await rmdir(sourceDirectory);
154
+ }
155
+ catch (error) {
156
+ if (error.code !== "ENOTEMPTY")
157
+ throw error;
158
+ }
159
+ result.repaired.push(entry.name);
160
+ continue;
161
+ }
162
+ const source = await readFile(taskFile, "utf8");
163
+ const id = field(source, "id") || entry.name;
164
+ const state = field(source, "state");
165
+ if (state !== "done" && state !== "cancelled")
166
+ continue;
167
+ if (options.only && !options.only.has(id))
168
+ continue;
169
+ if (state === "done") {
170
+ const issues = completionIssues(source);
171
+ if (issues.length) {
172
+ result.skipped.push({ id, reason: issues.join("; ") });
173
+ continue;
174
+ }
175
+ if (apply && !options.verified?.has(id)) {
176
+ const outcome = await verifyTask(id, root, {
177
+ cwd: options.cwd,
178
+ run: options.run,
179
+ });
180
+ if (!outcome.passed) {
181
+ result.skipped.push({ id, reason: outcome.reason });
182
+ continue;
183
+ }
184
+ }
185
+ }
186
+ if (state === "cancelled" && source.match(/^\s*-\s+"?\[ \] /m)) {
187
+ result.skipped.push({ id, reason: "unchecked work" });
188
+ continue;
189
+ }
190
+ result.candidates.push(id);
191
+ if (!apply)
192
+ continue;
193
+ const project = field(source, "project") || WORKSPACE_PROJECT_ID;
194
+ const destinationDirectory = resolveWithin(archiveRoot, projectArchiveName(project), id);
195
+ try {
196
+ await access(destinationDirectory);
197
+ result.skipped.push({ id, reason: "archive destination exists" });
198
+ continue;
199
+ }
200
+ catch (error) {
201
+ if (error.code !== "ENOENT")
202
+ throw error;
203
+ }
204
+ await mkdir(path.dirname(destinationDirectory), { recursive: true });
205
+ await rename(sourceDirectory, destinationDirectory);
206
+ result.moved.push(id);
207
+ }
208
+ return result;
209
+ }
210
+ //# sourceMappingURL=archive-tasks.js.map
@@ -0,0 +1,31 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { localDate } from "@ocean/core/shared/local-date.js";
3
+ import { resolveWithin, workspaceTasksRoot } from "@ocean/core/shared/paths.js";
4
+ /**
5
+ * Append a durable checkpoint to a task's `## Log` and, when given, replace its
6
+ * `## Next action`. Writing to the task file means the checkpoint survives a
7
+ * cold restart: a later loop iteration or a fresh session reads it back from
8
+ * disk rather than from in-memory state.
9
+ */
10
+ export async function checkpointTask(id, root = workspaceTasksRoot(), input) {
11
+ if (!input.note?.trim())
12
+ throw new Error("Checkpoint requires a note.");
13
+ const taskFile = resolveWithin(root, id, "task.md");
14
+ let source = await readFile(taskFile, "utf8");
15
+ const date = input.date ?? localDate();
16
+ const entry = `- ${date} — ${input.note.trim()}`;
17
+ if (/^## Log\s*$/m.test(source))
18
+ source = source.replace(/^## Log\s*$/m, `## Log\n\n${entry}`);
19
+ else
20
+ source = `${source.trimEnd()}\n\n## Log\n\n${entry}\n`;
21
+ if (input.next?.trim()) {
22
+ const next = input.next.trim();
23
+ if (/^## Next action\s*$/m.test(source))
24
+ source = source.replace(/^## Next action\s*$[\s\S]*?(?=^## |\s*$)/m, `## Next action\n\n${next}\n\n`);
25
+ else
26
+ source = `${source.trimEnd()}\n\n## Next action\n\n${next}\n`;
27
+ }
28
+ await writeFile(taskFile, source, "utf8");
29
+ return { checkpointed: id };
30
+ }
31
+ //# sourceMappingURL=checkpoint-task.js.map