@amoree-code/ocean 0.0.0-stage → 1.2.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.
- package/LICENSE +21 -0
- package/README.md +284 -2
- package/dist/application/auth/auth-orchestrator.js +77 -0
- package/dist/application/clients/clients.js +646 -0
- package/dist/application/clients/editors.js +398 -0
- package/dist/application/clients/hooks.js +335 -0
- package/dist/application/clients/native.js +410 -0
- package/dist/application/clients/opencode-plugin.js +158 -0
- package/dist/application/clients/registry.js +188 -0
- package/dist/application/gateway/webhook-gateway.js +137 -0
- package/dist/application/install/provider-installer.js +184 -0
- package/dist/application/integrations/charter-copies.js +76 -0
- package/dist/application/integrations/claude-vscode.js +106 -0
- package/dist/application/integrations/engine-entry-repair.js +105 -0
- package/dist/application/integrations/skill-filter.js +254 -0
- package/dist/application/layout/layout-apply.js +443 -0
- package/dist/application/layout/layout-plan.js +236 -0
- package/dist/application/layout/workspace-template.js +34 -0
- package/dist/application/onboarding/state.js +23 -0
- package/dist/application/onboarding/steps.js +249 -0
- package/dist/application/profiles/profile-distribution.js +53 -0
- package/dist/application/profiles/profile-suggestion.js +39 -0
- package/dist/application/profiles/profile-upgrade-service.js +208 -0
- package/dist/application/pull/history-index.js +476 -0
- package/dist/application/pull/project-match.js +132 -0
- package/dist/application/pull/pull.js +202 -0
- package/dist/application/pull/server-capture.js +307 -0
- package/dist/application/scheduler/local-scheduler.js +210 -0
- package/dist/composition/runtime.js +64 -0
- package/dist/engine-root.js +9 -0
- package/dist/infrastructure/filesystem/profile-loader.js +46 -0
- package/dist/infrastructure/filesystem/runtime-config-loader.js +29 -0
- package/dist/infrastructure/filesystem/skill-loader.js +84 -0
- package/dist/infrastructure/filesystem/workspace-capability.js +37 -0
- package/dist/infrastructure/observability/runtime-logger.js +16 -0
- package/dist/infrastructure/process/agent-tool-executor.js +99 -0
- package/dist/infrastructure/process/cli-process.js +49 -0
- package/dist/infrastructure/process/interactive-process.js +145 -0
- package/dist/infrastructure/process/process-supervisor.js +194 -0
- package/dist/infrastructure/process/service.js +252 -0
- package/dist/infrastructure/providers/browser-provider.js +2 -0
- package/dist/infrastructure/providers/claude-sdk.js +137 -0
- package/dist/infrastructure/providers/client-home.js +14 -0
- package/dist/infrastructure/providers/fake-browser-provider.js +146 -0
- package/dist/infrastructure/providers/ollama-embedder.js +74 -0
- package/dist/infrastructure/providers/openai-compatible.js +268 -0
- package/dist/infrastructure/providers/playwright-browser-provider.js +380 -0
- package/dist/infrastructure/providers/provider-capabilities.js +50 -0
- package/dist/infrastructure/providers/provider-invocation.js +184 -0
- package/dist/infrastructure/providers/provider-registry.js +77 -0
- package/dist/infrastructure/providers/providers.js +134 -0
- package/dist/infrastructure/wrappers/wrapper-manager.js +374 -0
- package/dist/interfaces/cli/auth-command.js +54 -0
- package/dist/interfaces/cli/browser-bridge-command.js +93 -0
- package/dist/interfaces/cli/browser-command.js +119 -0
- package/dist/interfaces/cli/capture-command.js +125 -0
- package/dist/interfaces/cli/client-test-command.js +133 -0
- package/dist/interfaces/cli/clients-command.js +129 -0
- package/dist/interfaces/cli/command-registry.js +1211 -0
- package/dist/interfaces/cli/context-command.js +172 -0
- package/dist/interfaces/cli/daily-command.js +131 -0
- package/dist/interfaces/cli/governance-command.js +55 -0
- package/dist/interfaces/cli/handoff-command.js +35 -0
- package/dist/interfaces/cli/help-command.js +207 -0
- package/dist/interfaces/cli/idea-command.js +47 -0
- package/dist/interfaces/cli/intercept-command.js +317 -0
- package/dist/interfaces/cli/layout-command.js +27 -0
- package/dist/interfaces/cli/market-command.js +52 -0
- package/dist/interfaces/cli/mcp-gateway-command.js +30 -0
- package/dist/interfaces/cli/memory-command.js +317 -0
- package/dist/interfaces/cli/migrate-command.js +26 -0
- package/dist/interfaces/cli/observe-command.js +78 -0
- package/dist/interfaces/cli/onboard-command.js +123 -0
- package/dist/interfaces/cli/operate-command.js +59 -0
- package/dist/interfaces/cli/profiles-command.js +70 -0
- package/dist/interfaces/cli/pull-command.js +54 -0
- package/dist/interfaces/cli/setup-command.js +143 -0
- package/dist/interfaces/cli/tasks-command.js +249 -0
- package/dist/main.js +49 -0
- package/dist/node-warnings.js +17 -0
- package/dist/version.js +8 -0
- package/market/mcp/chrome-devtools/ocean.capability.json +28 -0
- package/market/mcp/gateway/ocean.capability.json +21 -0
- package/market/mcp/obsidian/ocean.capability.json +21 -0
- package/market/mcp/ocean/ocean.capability.json +21 -0
- package/market/mcp/playwright/ocean.capability.json +23 -0
- package/node_modules/@ocean/brain/dist/brain/brain-eval.js +29 -0
- package/node_modules/@ocean/brain/dist/brain/brain-markdown.js +226 -0
- package/node_modules/@ocean/brain/dist/brain/brain-reindex.js +247 -0
- package/node_modules/@ocean/brain/dist/brain/brain-service.js +320 -0
- package/node_modules/@ocean/brain/dist/capture/inbox-sync.js +22 -0
- package/node_modules/@ocean/brain/dist/evidence/verification-report.js +13 -0
- package/node_modules/@ocean/brain/dist/infrastructure/brain/brain-index.js +285 -0
- package/node_modules/@ocean/brain/dist/memory/brain-dump.js +145 -0
- package/node_modules/@ocean/brain/dist/memory/daily-narrative.js +142 -0
- package/node_modules/@ocean/brain/dist/memory/index-sync.js +124 -0
- package/node_modules/@ocean/brain/dist/memory/lesson-activation.js +93 -0
- package/node_modules/@ocean/brain/dist/memory/lesson-learning.js +188 -0
- package/node_modules/@ocean/brain/dist/memory/lessons.js +321 -0
- package/node_modules/@ocean/brain/dist/memory/memory-audit.js +100 -0
- package/node_modules/@ocean/brain/dist/memory/memory-digest.js +49 -0
- package/node_modules/@ocean/brain/dist/memory/memory-harvest.js +345 -0
- package/node_modules/@ocean/brain/dist/memory/memory-paths.js +174 -0
- package/node_modules/@ocean/brain/dist/memory/memory-review.js +278 -0
- package/node_modules/@ocean/brain/dist/memory/memory-trash.js +152 -0
- package/node_modules/@ocean/brain/dist/memory/memory-write.js +216 -0
- package/node_modules/@ocean/brain/dist/memory/model-narrative.js +129 -0
- package/node_modules/@ocean/brain/dist/memory/profile-facts.js +103 -0
- package/node_modules/@ocean/brain/dist/memory/session-promotion.js +61 -0
- package/node_modules/@ocean/brain/dist/memory/session-summary.js +113 -0
- package/node_modules/@ocean/brain/package.json +11 -0
- package/node_modules/@ocean/context/dist/context/context-compression.js +66 -0
- package/node_modules/@ocean/context/dist/context/context-cost.js +587 -0
- package/node_modules/@ocean/context/dist/context/context-ladder.js +363 -0
- package/node_modules/@ocean/context/dist/context/context-packet.js +213 -0
- package/node_modules/@ocean/context/dist/context/context-references.js +255 -0
- package/node_modules/@ocean/context/dist/context/intent-router.js +259 -0
- package/node_modules/@ocean/context/dist/context/resource-injection.js +36 -0
- package/node_modules/@ocean/context/package.json +11 -0
- package/node_modules/@ocean/core/dist/domain/brain/brain-record.js +22 -0
- package/node_modules/@ocean/core/dist/domain/brain/record-scope.js +22 -0
- package/node_modules/@ocean/core/dist/domain/capabilities/browser-contract.js +64 -0
- package/node_modules/@ocean/core/dist/domain/capabilities/capability-contract.js +16 -0
- package/node_modules/@ocean/core/dist/domain/capabilities/capability-manifest.js +72 -0
- package/node_modules/@ocean/core/dist/domain/context/context-validator.js +5 -0
- package/node_modules/@ocean/core/dist/domain/context/context.js +34 -0
- package/node_modules/@ocean/core/dist/domain/context/project-id.js +30 -0
- package/node_modules/@ocean/core/dist/domain/evidence/evidence.js +16 -0
- package/node_modules/@ocean/core/dist/domain/mcp/mcp-contract.js +36 -0
- package/node_modules/@ocean/core/dist/domain/mcp/memory-contract.js +41 -0
- package/node_modules/@ocean/core/dist/domain/ports/brain-index-port.js +7 -0
- package/node_modules/@ocean/core/dist/domain/ports/browser-port.js +8 -0
- package/node_modules/@ocean/core/dist/domain/ports/embedder-port.js +2 -0
- package/node_modules/@ocean/core/dist/domain/ports/platform-ports.js +2 -0
- package/node_modules/@ocean/core/dist/domain/ports/runtime-ports.js +2 -0
- package/node_modules/@ocean/core/dist/domain/ports/session-store-port.js +2 -0
- package/node_modules/@ocean/core/dist/domain/process/process-events.js +5 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-distribution.js +19 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-policy.js +34 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-selection.js +73 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-upgrade.js +123 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-validator.js +5 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile.js +218 -0
- package/node_modules/@ocean/core/dist/domain/profiles/runtime-config.js +30 -0
- package/node_modules/@ocean/core/dist/domain/providers/provider-capability.js +28 -0
- package/node_modules/@ocean/core/dist/domain/providers/provider.js +2 -0
- package/node_modules/@ocean/core/dist/domain/redaction/redaction.js +86 -0
- package/node_modules/@ocean/core/dist/domain/runs/agent-tools.js +92 -0
- package/node_modules/@ocean/core/dist/domain/runs/execution-plan.js +71 -0
- package/node_modules/@ocean/core/dist/domain/runs/run-contract.js +38 -0
- package/node_modules/@ocean/core/dist/domain/sessions/entry-contract.js +31 -0
- package/node_modules/@ocean/core/dist/domain/sessions/handoff.js +78 -0
- package/node_modules/@ocean/core/dist/domain/sessions/session.js +58 -0
- package/node_modules/@ocean/core/dist/domain/skills/default-skills.js +29 -0
- package/node_modules/@ocean/core/dist/domain/skills/skill-validator.js +5 -0
- package/node_modules/@ocean/core/dist/domain/skills/skill.js +11 -0
- package/node_modules/@ocean/core/dist/domain/tasks/task-id.js +85 -0
- package/node_modules/@ocean/core/dist/shared/fs-utils.js +212 -0
- package/node_modules/@ocean/core/dist/shared/local-date.js +16 -0
- package/node_modules/@ocean/core/dist/shared/paths.js +324 -0
- package/node_modules/@ocean/core/dist/shared/project-resolution.js +1065 -0
- package/node_modules/@ocean/core/dist/shared/task-reference.js +133 -0
- package/node_modules/@ocean/core/package.json +14 -0
- package/node_modules/@ocean/loop/dist/handoff/handoff-service.js +133 -0
- package/node_modules/@ocean/loop/dist/hooks/lifecycle-hooks.js +15 -0
- package/node_modules/@ocean/loop/dist/hooks/session-end-hook.js +183 -0
- package/node_modules/@ocean/loop/dist/hooks/session-start-hook.js +237 -0
- package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-store.js +690 -0
- package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-validator.js +5 -0
- package/node_modules/@ocean/loop/dist/loops/iteration-graph.js +147 -0
- package/node_modules/@ocean/loop/dist/loops/task-loop-record.js +2 -0
- package/node_modules/@ocean/loop/dist/loops/task-loop.js +281 -0
- package/node_modules/@ocean/loop/dist/operations/memory-knowledge-operations.js +263 -0
- package/node_modules/@ocean/loop/dist/operations/operation-contract.js +227 -0
- package/node_modules/@ocean/loop/dist/operations/operation-scope.js +28 -0
- package/node_modules/@ocean/loop/dist/operations/project-operations.js +93 -0
- package/node_modules/@ocean/loop/dist/operations/record-io.js +65 -0
- package/node_modules/@ocean/loop/dist/operations/record-operations.js +45 -0
- package/node_modules/@ocean/loop/dist/operations/task-linker.js +40 -0
- package/node_modules/@ocean/loop/dist/operations/task-operations.js +279 -0
- package/node_modules/@ocean/loop/dist/operations/write-guard.js +157 -0
- package/node_modules/@ocean/loop/dist/runs/prompt-assembly.js +112 -0
- package/node_modules/@ocean/loop/dist/runs/run-agent.js +425 -0
- package/node_modules/@ocean/loop/dist/runs/run-authorization.js +24 -0
- package/node_modules/@ocean/loop/dist/runs/tool-loop.js +173 -0
- package/node_modules/@ocean/loop/dist/sessions/session-closeout.js +150 -0
- package/node_modules/@ocean/loop/dist/sessions/session-pointer.js +133 -0
- package/node_modules/@ocean/loop/dist/sessions/session-projects.js +26 -0
- package/node_modules/@ocean/loop/dist/sessions/session-retention.js +132 -0
- package/node_modules/@ocean/loop/dist/tasks/archive-tasks.js +210 -0
- package/node_modules/@ocean/loop/dist/tasks/checkpoint-task.js +31 -0
- package/node_modules/@ocean/loop/dist/tasks/claim-task.js +126 -0
- package/node_modules/@ocean/loop/dist/tasks/list-tasks.js +113 -0
- package/node_modules/@ocean/loop/dist/tasks/new-task.js +42 -0
- package/node_modules/@ocean/loop/dist/tasks/renumber-tasks.js +134 -0
- package/node_modules/@ocean/loop/dist/tasks/verification-readiness.js +78 -0
- package/node_modules/@ocean/loop/dist/tasks/verify-task.js +234 -0
- package/node_modules/@ocean/loop/package.json +11 -0
- package/node_modules/@ocean/market/dist/adapters/mcp-adapter.js +64 -0
- package/node_modules/@ocean/market/dist/adapters/owned-directory.js +89 -0
- package/node_modules/@ocean/market/dist/adapters/plugin-adapter.js +39 -0
- package/node_modules/@ocean/market/dist/adapters/skill-adapter.js +32 -0
- package/node_modules/@ocean/market/dist/capability/adapter.js +2 -0
- package/node_modules/@ocean/market/dist/capability/catalog.js +63 -0
- package/node_modules/@ocean/market/dist/capability/content-digest.js +40 -0
- package/node_modules/@ocean/market/dist/capability/registry.js +81 -0
- package/node_modules/@ocean/market/dist/install/default-marketplace.js +47 -0
- package/node_modules/@ocean/market/dist/install/lock.js +34 -0
- package/node_modules/@ocean/market/dist/install/marketplace-service.js +165 -0
- package/node_modules/@ocean/market/dist/mcp/browser-bridge.js +263 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-connection.js +67 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-gateway.js +317 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-launch.js +102 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-runner.js +32 -0
- package/node_modules/@ocean/market/dist/security/description-scan.js +45 -0
- package/node_modules/@ocean/market/dist/security/permissions.js +57 -0
- package/node_modules/@ocean/market/dist/skills/core-skill-sync.js +113 -0
- package/node_modules/@ocean/market/dist/skills/signal-detection.js +84 -0
- package/node_modules/@ocean/market/dist/skills/skill-hub.js +220 -0
- package/node_modules/@ocean/market/package.json +11 -0
- package/node_modules/@ocean/services/dist/browser/browser-service.js +124 -0
- package/node_modules/@ocean/services/dist/browser/browser-session.js +287 -0
- package/node_modules/@ocean/services/dist/browser/browser-task-runner.js +70 -0
- package/node_modules/@ocean/services/dist/doctor/workspace-doctor.js +547 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/call-ledger.js +114 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/mcp-client.js +162 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/obsidian-server.js +251 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/ocean-server.js +880 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/server-version.js +20 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/tool-registry.js +29 -0
- package/node_modules/@ocean/services/dist/obsidian/conflict-log.js +68 -0
- package/node_modules/@ocean/services/dist/obsidian/inbox-promotion.js +80 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-discovery.js +113 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-ingestion.js +77 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-sync.js +73 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-writer.js +75 -0
- package/node_modules/@ocean/services/package.json +11 -0
- package/ocean-marketplace.json +415 -0
- package/package.json +67 -4
- package/skills/core/business-logic/SKILL.md +22 -0
- package/skills/core/catch-up/SKILL.md +17 -0
- package/skills/core/core-thinking/SKILL.md +11 -0
- package/skills/core/design-thinking/SKILL.md +19 -0
- package/skills/core/graft/SKILL.md +156 -0
- package/skills/core/loop/SKILL.md +20 -0
- package/skills/core/session-handoff/SKILL.md +15 -0
- package/skills/core/use-browser/SKILL.md +69 -0
- package/skills/core/verification/SKILL.md +11 -0
- package/skills/index.json +56 -0
- package/templates/README.md +4 -0
- package/templates/profiles/default.json +51 -0
- package/templates/profiles/developer.json +51 -0
- package/templates/profiles/reviewer.json +51 -0
- package/templates/profiles/strategist.json +51 -0
- package/templates/workspace/00-inbox/INBOX.md +7 -0
- package/templates/workspace/00-inbox/candidates/.gitkeep +0 -0
- package/templates/workspace/00-inbox/native/.gitkeep +0 -0
- package/templates/workspace/01-daily/.gitkeep +0 -0
- package/templates/workspace/02-personal/CONFLICTS.md +7 -0
- package/templates/workspace/02-personal/MEMORY.md +15 -0
- package/templates/workspace/02-personal/goals/.gitkeep +0 -0
- package/templates/workspace/02-personal/identity/.gitkeep +0 -0
- package/templates/workspace/02-personal/preferences/.gitkeep +0 -0
- package/templates/workspace/03-professional/.gitkeep +0 -0
- package/templates/workspace/04-projects/PROJECTS.md +5 -0
- package/templates/workspace/04-projects/_template/docs/.gitkeep +0 -0
- package/templates/workspace/04-projects/_template/memory/.gitkeep +0 -0
- package/templates/workspace/04-projects/_template/tasks/.gitkeep +0 -0
- package/templates/workspace/05-knowledge/KNOWLEDGE.md +8 -0
- package/templates/workspace/05-knowledge/decisions/.gitkeep +0 -0
- package/templates/workspace/05-knowledge/lessons/.gitkeep +0 -0
- package/templates/workspace/06-templates/TEMPLATES.md +25 -0
- package/templates/workspace/06-templates/decision.md +8 -0
- package/templates/workspace/06-templates/intelligence.md +32 -0
- package/templates/workspace/06-templates/project-claude.md +48 -0
- package/templates/workspace/06-templates/session.md +38 -0
- package/templates/workspace/06-templates/task.md +42 -0
- package/templates/workspace/MANIFEST.json +51 -0
- package/templates/workspace/bridge/config/CONFIG.md +21 -0
- package/templates/workspace/bridge/config/runtime.json +4 -0
- package/templates/workspace/charter/core.md +104 -0
- package/templates/workspace/charter/policies/README.md +31 -0
- package/templates/workspace/charter/policies/claude-code-context.md +90 -0
- package/templates/workspace/charter/policies/concise-output.md +29 -0
- package/templates/workspace/charter/policies/context.md +91 -0
- package/templates/workspace/charter/policies/contracts/task.md +189 -0
- package/templates/workspace/charter/policies/git.md +60 -0
- package/templates/workspace/charter/policies/graph.md +47 -0
- package/templates/workspace/charter/policies/knowledge.md +33 -0
- package/templates/workspace/charter/policies/lifecycle.md +119 -0
- package/templates/workspace/charter/policies/memory.md +67 -0
- package/templates/workspace/charter/policies/minimal-change.md +43 -0
- package/templates/workspace/charter/policies/models.md +57 -0
- package/templates/workspace/charter/policies/privacy-terms.txt +3 -0
- package/templates/workspace/charter/policies/response.md +39 -0
- package/templates/workspace/charter/policies/skill-observer.md +44 -0
- package/templates/workspace/charter/policies/stack.md +23 -0
- package/templates/workspace/charter/policies/task.md +139 -0
- package/templates/workspace/charter/policies/verification.md +29 -0
- package/templates/workspace/sessions/.gitkeep +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { MemoryContractError } from "@ocean/core/domain/mcp/memory-contract.js";
|
|
5
|
+
import { projectDir } from "@ocean/core/shared/paths.js";
|
|
6
|
+
import { sha256 } from "./memory-audit.js";
|
|
7
|
+
import { harvestLedgerFile, stagingDir } from "./memory-paths.js";
|
|
8
|
+
import { proposeMemory } from "./memory-write.js";
|
|
9
|
+
export const HARVEST_SOURCES = [
|
|
10
|
+
"claude-code",
|
|
11
|
+
"codex",
|
|
12
|
+
"gemini",
|
|
13
|
+
"hermes",
|
|
14
|
+
"antigravity",
|
|
15
|
+
"instructions",
|
|
16
|
+
];
|
|
17
|
+
// The same text with other spacing or letter case is the same memory.
|
|
18
|
+
const normalizedKey = (text) => `n:${sha256(text.toLowerCase().replace(/\s+/g, " ").trim())}`;
|
|
19
|
+
// A short label: the first heading, else a frontmatter name, else the first line.
|
|
20
|
+
export function textTitle(text) {
|
|
21
|
+
const heading = /^#{1,6}\s+(.+)$/m.exec(text)?.[1];
|
|
22
|
+
const name = /^name:\s*["']?(.+?)["']?\s*$/m.exec(text)?.[1];
|
|
23
|
+
const first = text
|
|
24
|
+
.split("\n")
|
|
25
|
+
.map((line) => line.trim())
|
|
26
|
+
.find((line) => line && line !== "---");
|
|
27
|
+
return (heading ?? name ?? first ?? "").slice(0, 100);
|
|
28
|
+
}
|
|
29
|
+
const slug = (value) => value
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
32
|
+
.replace(/^-+|-+$/g, "")
|
|
33
|
+
.slice(0, 50) || "note";
|
|
34
|
+
// The frontmatter a pulled candidate carries, and the record it would become.
|
|
35
|
+
function placementMeta(origin, placement) {
|
|
36
|
+
const project = placement.project ?? "unassigned";
|
|
37
|
+
const title = textTitle(origin.text);
|
|
38
|
+
const meta = {
|
|
39
|
+
client: origin.client,
|
|
40
|
+
project,
|
|
41
|
+
...(placement.tags?.length ? { tags: placement.tags } : {}),
|
|
42
|
+
...(placement.cwd ? { cwd: placement.cwd } : {}),
|
|
43
|
+
pull: true,
|
|
44
|
+
pulled_at: new Date().toISOString(),
|
|
45
|
+
...(origin.date ? { date: origin.date } : {}),
|
|
46
|
+
title,
|
|
47
|
+
file: origin.file,
|
|
48
|
+
};
|
|
49
|
+
const target = placement.target
|
|
50
|
+
? `${projectDir(project)}/memory/${slug(title)}-${sha256(origin.text).slice(0, 6)}.md`
|
|
51
|
+
: undefined;
|
|
52
|
+
return { meta, target, project, title };
|
|
53
|
+
}
|
|
54
|
+
const MAX_BYTES = 32_000;
|
|
55
|
+
const TEXT = /\.(md|txt)$/i;
|
|
56
|
+
async function filesIn(directory, depth = 0) {
|
|
57
|
+
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
58
|
+
const found = [];
|
|
59
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
60
|
+
if (entry.name.startsWith("."))
|
|
61
|
+
continue;
|
|
62
|
+
const target = path.join(directory, entry.name);
|
|
63
|
+
if (entry.isFile() && TEXT.test(entry.name))
|
|
64
|
+
found.push(target);
|
|
65
|
+
else if (depth > 0 && entry.isDirectory())
|
|
66
|
+
found.push(...(await filesIn(target, depth - 1)));
|
|
67
|
+
}
|
|
68
|
+
return found;
|
|
69
|
+
}
|
|
70
|
+
// Global instruction files the owner may have written by hand. The block Ocean generates from
|
|
71
|
+
// its own charter (`ai-sync` markers) and `@` includes of it are Ocean's already, not imported.
|
|
72
|
+
const INSTRUCTION_FILES = [
|
|
73
|
+
".claude/CLAUDE.md",
|
|
74
|
+
".codex/AGENTS.md",
|
|
75
|
+
".gemini/GEMINI.md",
|
|
76
|
+
".qwen/QWEN.md",
|
|
77
|
+
".config/opencode/AGENTS.md",
|
|
78
|
+
];
|
|
79
|
+
const GENERATED_BLOCK = /<!-- ai-sync:generated -->[\s\S]*?<!-- ai-sync:end -->/g;
|
|
80
|
+
const GEMINI_MEMORIES = /^## Gemini Added Memories\s*\n([\s\S]*?)(?=^## |$(?![\s\S]))/m;
|
|
81
|
+
function ownedText(text) {
|
|
82
|
+
return text
|
|
83
|
+
.replace(GENERATED_BLOCK, "")
|
|
84
|
+
.replace(GEMINI_MEMORIES, "")
|
|
85
|
+
.split("\n")
|
|
86
|
+
.filter((line) => !line.trim().startsWith("@"))
|
|
87
|
+
.join("\n")
|
|
88
|
+
.trim();
|
|
89
|
+
}
|
|
90
|
+
// A text over `max` bytes, cut at the shallowest heading level that splits it; a part still too
|
|
91
|
+
// large is cut at the next level, then between paragraphs.
|
|
92
|
+
export function splitMarkdown(text, max = MAX_BYTES) {
|
|
93
|
+
const size = (value) => Buffer.byteLength(value, "utf8");
|
|
94
|
+
if (size(text) <= max)
|
|
95
|
+
return [text];
|
|
96
|
+
for (const level of [1, 2, 3, 4]) {
|
|
97
|
+
const marker = new RegExp(`^#{${level}} `, "m");
|
|
98
|
+
const parts = text
|
|
99
|
+
.split(new RegExp(`(?=^#{${level}} )`, "m"))
|
|
100
|
+
.filter((part) => part.trim());
|
|
101
|
+
if (parts.length > 1 && parts.some((part) => marker.test(part)))
|
|
102
|
+
return parts.flatMap((part) => splitMarkdown(part, max));
|
|
103
|
+
}
|
|
104
|
+
// No heading left to cut at: group whole paragraphs. One paragraph still over the limit stays
|
|
105
|
+
// whole and is reported too large; text is never cut mid-paragraph.
|
|
106
|
+
const out = [];
|
|
107
|
+
let current = "";
|
|
108
|
+
for (const piece of text.split(/(?<=\n\n)/)) {
|
|
109
|
+
if (current && size(current + piece) > max) {
|
|
110
|
+
out.push(current);
|
|
111
|
+
current = "";
|
|
112
|
+
}
|
|
113
|
+
current += piece;
|
|
114
|
+
}
|
|
115
|
+
if (current.trim())
|
|
116
|
+
out.push(current);
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
// The texts each source holds, as (label, text) pairs. Missing stores are simply empty.
|
|
120
|
+
async function sourceTexts(source, home) {
|
|
121
|
+
const read = async (files) => Promise.all(files.map(async (file) => ({ file, text: await readFile(file, "utf8") })));
|
|
122
|
+
if (source === "claude-code") {
|
|
123
|
+
const projects = path.join(home, ".claude", "projects");
|
|
124
|
+
const dirs = await readdir(projects).catch(() => []);
|
|
125
|
+
const files = [...(await filesIn(stagingDir("claude-code")))];
|
|
126
|
+
for (const dir of dirs.sort())
|
|
127
|
+
files.push(...(await filesIn(path.join(projects, dir, "memory"))).filter((file) => path.basename(file) !== "MEMORY.md"));
|
|
128
|
+
return read(files);
|
|
129
|
+
}
|
|
130
|
+
if (source === "codex")
|
|
131
|
+
// raw_memories.md concatenates the per-session rollout summaries: history, indexed by
|
|
132
|
+
// `ocean pull`, not memory.
|
|
133
|
+
return read((await filesIn(path.join(home, ".codex", "memories"))).filter((file) => path.basename(file) !== "raw_memories.md"));
|
|
134
|
+
if (source === "instructions") {
|
|
135
|
+
const texts = [];
|
|
136
|
+
for (const relative of INSTRUCTION_FILES) {
|
|
137
|
+
const file = path.join(home, relative);
|
|
138
|
+
const text = ownedText(await readFile(file, "utf8").catch(() => ""));
|
|
139
|
+
// Headings alone (what is left around a removed block) say nothing.
|
|
140
|
+
if (text.split("\n").some((line) => line.trim() && !/^#+\s/.test(line)))
|
|
141
|
+
texts.push({ file, text });
|
|
142
|
+
}
|
|
143
|
+
for (const file of await filesIn(path.join(home, ".cursor", "rules")))
|
|
144
|
+
texts.push({ file, text: await readFile(file, "utf8") });
|
|
145
|
+
return texts;
|
|
146
|
+
}
|
|
147
|
+
if (source === "hermes")
|
|
148
|
+
return read(await filesIn(path.join(home, ".hermes", "memories")));
|
|
149
|
+
if (source === "antigravity")
|
|
150
|
+
return read(await filesIn(path.join(home, ".gemini", "antigravity", "brain"), 1));
|
|
151
|
+
const texts = await read(await filesIn(path.join(home, ".gemini", "memory")));
|
|
152
|
+
// Gemini's save_memory appends under this heading in GEMINI.md.
|
|
153
|
+
const geminiMd = path.join(home, ".gemini", "GEMINI.md");
|
|
154
|
+
const section = GEMINI_MEMORIES.exec(await readFile(geminiMd, "utf8").catch(() => ""))?.[1];
|
|
155
|
+
if (section?.trim())
|
|
156
|
+
texts.push({ file: geminiMd, text: section.trim() });
|
|
157
|
+
return texts;
|
|
158
|
+
}
|
|
159
|
+
const ledgerFile = harvestLedgerFile;
|
|
160
|
+
async function readLedger() {
|
|
161
|
+
return JSON.parse(await readFile(ledgerFile(), "utf8").catch(() => "{}"));
|
|
162
|
+
}
|
|
163
|
+
export async function harvestMemory(options = {}) {
|
|
164
|
+
const home = options.home ?? os.homedir();
|
|
165
|
+
const ledger = await readLedger();
|
|
166
|
+
const items = [];
|
|
167
|
+
const shown = (file) => file.startsWith(home) ? `~${file.slice(home.length)}` : file;
|
|
168
|
+
const parts = async (source) => {
|
|
169
|
+
const out = [];
|
|
170
|
+
for (const { file, text } of await sourceTexts(source, home)) {
|
|
171
|
+
const date = (await stat(file).catch(() => null))?.mtime
|
|
172
|
+
.toISOString()
|
|
173
|
+
.slice(0, 10);
|
|
174
|
+
const pieces = splitMarkdown(text);
|
|
175
|
+
pieces.forEach((piece, index) => {
|
|
176
|
+
out.push({
|
|
177
|
+
path: file,
|
|
178
|
+
file: pieces.length > 1 ? `${shown(file)}#${index + 1}` : shown(file),
|
|
179
|
+
text: piece,
|
|
180
|
+
date,
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
};
|
|
186
|
+
// Keys proposed in this run too, so one text found twice is proposed once.
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
for (const source of options.sources ?? HARVEST_SOURCES) {
|
|
189
|
+
for (const { path: origin, file, text, date } of await parts(source)) {
|
|
190
|
+
if (!text.trim())
|
|
191
|
+
continue;
|
|
192
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
193
|
+
const keys = [sha256(text), normalizedKey(text)];
|
|
194
|
+
const placed = placementMeta({ client: source, file, text, date }, options.classify?.({ client: source, file: origin, text }) ?? {});
|
|
195
|
+
const item = {
|
|
196
|
+
source,
|
|
197
|
+
file,
|
|
198
|
+
bytes,
|
|
199
|
+
status: "new",
|
|
200
|
+
project: placed.project,
|
|
201
|
+
title: placed.title,
|
|
202
|
+
};
|
|
203
|
+
if (keys.some((key) => ledger[key] || seen.has(key)))
|
|
204
|
+
item.status = "seen";
|
|
205
|
+
else if (bytes > MAX_BYTES)
|
|
206
|
+
item.status = "too-large";
|
|
207
|
+
else if (options.apply)
|
|
208
|
+
try {
|
|
209
|
+
const proposed = await proposeMemory({
|
|
210
|
+
content: text,
|
|
211
|
+
origin: "imported",
|
|
212
|
+
source,
|
|
213
|
+
reason: `harvested from ${file}`.slice(0, 200),
|
|
214
|
+
target: placed.target,
|
|
215
|
+
meta: placed.meta,
|
|
216
|
+
});
|
|
217
|
+
for (const key of keys)
|
|
218
|
+
ledger[key] = proposed.candidate;
|
|
219
|
+
item.status = "proposed";
|
|
220
|
+
item.candidate = proposed.candidate;
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
if (!(error instanceof MemoryContractError))
|
|
224
|
+
throw error;
|
|
225
|
+
item.status = error.code === "SECRET" ? "secret" : "too-large";
|
|
226
|
+
}
|
|
227
|
+
for (const key of keys)
|
|
228
|
+
seen.add(key);
|
|
229
|
+
items.push(item);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (options.apply) {
|
|
233
|
+
await mkdir(path.dirname(ledgerFile()), { recursive: true, mode: 0o700 });
|
|
234
|
+
await writeFile(ledgerFile(), `${JSON.stringify(ledger, null, 2)}\n`, {
|
|
235
|
+
mode: 0o600,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return items;
|
|
239
|
+
}
|
|
240
|
+
// claude.ai account export (ASSUMED shape, read defensively): `projects.json` as a list of
|
|
241
|
+
// projects with name, description, prompt_template and docs [{filename, content}]. Each project
|
|
242
|
+
// and each doc becomes a candidate; anything unreadable is reported, never guessed.
|
|
243
|
+
export async function importClaudeExport(directory, options = {}) {
|
|
244
|
+
const problems = [];
|
|
245
|
+
const items = [];
|
|
246
|
+
const file = path.join(directory, "projects.json");
|
|
247
|
+
if (!(await stat(file).catch(() => null))) {
|
|
248
|
+
problems.push("projects.json not found in the export");
|
|
249
|
+
return { items, problems };
|
|
250
|
+
}
|
|
251
|
+
let projects;
|
|
252
|
+
try {
|
|
253
|
+
projects = JSON.parse(await readFile(file, "utf8"));
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
problems.push("projects.json is not valid JSON");
|
|
257
|
+
return { items, problems };
|
|
258
|
+
}
|
|
259
|
+
if (!Array.isArray(projects)) {
|
|
260
|
+
problems.push("projects.json is not a list");
|
|
261
|
+
return { items, problems };
|
|
262
|
+
}
|
|
263
|
+
const texts = [];
|
|
264
|
+
for (const raw of projects) {
|
|
265
|
+
const name = typeof raw?.name === "string" ? raw.name : "untitled";
|
|
266
|
+
const parts = [
|
|
267
|
+
`# ${name}`,
|
|
268
|
+
typeof raw.description === "string" ? raw.description : "",
|
|
269
|
+
typeof raw.prompt_template === "string"
|
|
270
|
+
? `## Instructions\n\n${raw.prompt_template}`
|
|
271
|
+
: "",
|
|
272
|
+
].filter(Boolean);
|
|
273
|
+
texts.push({
|
|
274
|
+
label: `claude.ai project ${name}`,
|
|
275
|
+
text: parts.join("\n\n"),
|
|
276
|
+
name,
|
|
277
|
+
});
|
|
278
|
+
for (const doc of Array.isArray(raw.docs) ? raw.docs : [])
|
|
279
|
+
if (typeof doc?.content === "string" && doc.content.trim()) {
|
|
280
|
+
const pieces = splitMarkdown(doc.content);
|
|
281
|
+
pieces.forEach((piece, index) => {
|
|
282
|
+
texts.push({
|
|
283
|
+
label: `claude.ai project ${name} doc ${String(doc.filename ?? "")}${pieces.length > 1 ? `#${index + 1}` : ""}`,
|
|
284
|
+
text: piece,
|
|
285
|
+
name,
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return { items: await proposeImported(texts, options), problems };
|
|
291
|
+
}
|
|
292
|
+
// Texts read from a provider (an export, or what a cache kept of its pages) → candidates, each
|
|
293
|
+
// proposed once: the ledger remembers every text, also across spacing and case.
|
|
294
|
+
export async function proposeImported(texts, options = {}) {
|
|
295
|
+
const items = [];
|
|
296
|
+
const ledger = await readLedger();
|
|
297
|
+
const seen = new Set();
|
|
298
|
+
for (const { label, text, name, source = "claude.ai" } of texts) {
|
|
299
|
+
const keys = [sha256(text), normalizedKey(text)];
|
|
300
|
+
const placed = placementMeta({ client: source, file: label, text }, options.classify?.({ client: source, file: label, text, name }) ?? {});
|
|
301
|
+
const item = {
|
|
302
|
+
source,
|
|
303
|
+
file: label,
|
|
304
|
+
bytes: Buffer.byteLength(text, "utf8"),
|
|
305
|
+
status: keys.some((key) => ledger[key] || seen.has(key))
|
|
306
|
+
? "seen"
|
|
307
|
+
: Buffer.byteLength(text, "utf8") > MAX_BYTES
|
|
308
|
+
? "too-large"
|
|
309
|
+
: "new",
|
|
310
|
+
project: placed.project,
|
|
311
|
+
title: placed.title,
|
|
312
|
+
};
|
|
313
|
+
for (const key of keys)
|
|
314
|
+
seen.add(key);
|
|
315
|
+
if (item.status === "new" && options.apply)
|
|
316
|
+
try {
|
|
317
|
+
const proposed = await proposeMemory({
|
|
318
|
+
content: text,
|
|
319
|
+
origin: "imported",
|
|
320
|
+
source,
|
|
321
|
+
reason: label.slice(0, 200),
|
|
322
|
+
target: placed.target,
|
|
323
|
+
meta: placed.meta,
|
|
324
|
+
});
|
|
325
|
+
for (const key of keys)
|
|
326
|
+
ledger[key] = proposed.candidate;
|
|
327
|
+
item.status = "proposed";
|
|
328
|
+
item.candidate = proposed.candidate;
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
if (!(error instanceof MemoryContractError))
|
|
332
|
+
throw error;
|
|
333
|
+
item.status = error.code === "SECRET" ? "secret" : "too-large";
|
|
334
|
+
}
|
|
335
|
+
items.push(item);
|
|
336
|
+
}
|
|
337
|
+
if (options.apply) {
|
|
338
|
+
await mkdir(path.dirname(ledgerFile()), { recursive: true, mode: 0o700 });
|
|
339
|
+
await writeFile(ledgerFile(), `${JSON.stringify(ledger, null, 2)}\n`, {
|
|
340
|
+
mode: 0o600,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return items;
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=memory-harvest.js.map
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { lstatSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { MemoryContractError } from "@ocean/core/domain/mcp/memory-contract.js";
|
|
4
|
+
import { INBOX_DIR, oceanPath, PERSONAL_DIR, PROJECTS_DIR, projectDir, projectPathParts, resolveWithin, SYSTEM_DIR, } from "@ocean/core/shared/paths.js";
|
|
5
|
+
// Every place the memory tools may write, and nothing else (T-296). Permanent records are the
|
|
6
|
+
// only paths a caller chooses; candidates, versions, trash, the audit log and the client
|
|
7
|
+
// journal are named by the server. Native staging belongs to a client's own memory feature.
|
|
8
|
+
// Indexes and the conflicts list are maintained by Ocean itself, never written as records.
|
|
9
|
+
const PROTECTED = new Set([
|
|
10
|
+
"memory.md",
|
|
11
|
+
"knowledge.md",
|
|
12
|
+
"projects.md",
|
|
13
|
+
"conflicts.md",
|
|
14
|
+
"inbox.md",
|
|
15
|
+
]);
|
|
16
|
+
const clientIdPattern = /^[a-z0-9][a-z0-9-]{0,40}$/;
|
|
17
|
+
function workspaceRelative(file) {
|
|
18
|
+
return path.relative(oceanPath(), file).split(path.sep).join("/");
|
|
19
|
+
}
|
|
20
|
+
// A symlink anywhere from the root down, or a hard-linked target, could make a write land in a
|
|
21
|
+
// file outside the record the caller named; resolveWithin alone accepts in-root symlinks.
|
|
22
|
+
export function assertNoLinks(root, file) {
|
|
23
|
+
const resolvedRoot = path.resolve(root);
|
|
24
|
+
const parts = path.relative(resolvedRoot, path.resolve(file)).split(path.sep);
|
|
25
|
+
let current = resolvedRoot;
|
|
26
|
+
for (const part of ["", ...parts]) {
|
|
27
|
+
current = part ? path.join(current, part) : current;
|
|
28
|
+
let stats;
|
|
29
|
+
try {
|
|
30
|
+
stats = lstatSync(current);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error.code === "ENOENT")
|
|
34
|
+
return;
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
if (stats.isSymbolicLink())
|
|
38
|
+
throw new MemoryContractError("SYMLINK", `${workspaceRelative(current)} is a symbolic link`);
|
|
39
|
+
if (current === path.resolve(file) && stats.isFile() && stats.nlink > 1)
|
|
40
|
+
throw new MemoryContractError("HARDLINK", `${workspaceRelative(current)} has more than one hard link`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function segments(relative) {
|
|
44
|
+
if (typeof relative !== "string" ||
|
|
45
|
+
!relative ||
|
|
46
|
+
relative.includes("\0") ||
|
|
47
|
+
path.isAbsolute(relative) ||
|
|
48
|
+
/^[A-Za-z]:/.test(relative))
|
|
49
|
+
throw new MemoryContractError("OUT_OF_ROOT", "path must be a relative path inside the workspace");
|
|
50
|
+
const parts = relative.split(/[\\/]/);
|
|
51
|
+
if (parts.some((part) => part === "" || part === "." || part === ".."))
|
|
52
|
+
throw new MemoryContractError("OUT_OF_ROOT", "path must not contain empty, '.' or '..' segments");
|
|
53
|
+
if (parts.some((part) => part.startsWith(".")))
|
|
54
|
+
throw new MemoryContractError("OUT_OF_ROOT", "path must not contain hidden segments");
|
|
55
|
+
return parts;
|
|
56
|
+
}
|
|
57
|
+
// A permanent record: `02-personal/**/*.md` or `04-projects/<id>/memory/**/*.md`.
|
|
58
|
+
export function permanentRecord(relative) {
|
|
59
|
+
const parts = segments(relative);
|
|
60
|
+
const name = parts.at(-1) ?? "";
|
|
61
|
+
if (!name.toLowerCase().endsWith(".md"))
|
|
62
|
+
throw new MemoryContractError("INVALID", "a record must be a .md file");
|
|
63
|
+
if (PROTECTED.has(name.toLowerCase()))
|
|
64
|
+
throw new MemoryContractError("PROTECTED", `${name} is maintained by Ocean, not written as a record`);
|
|
65
|
+
const joined = parts.join("/");
|
|
66
|
+
const under = (dir) => joined.startsWith(`${dir}/`)
|
|
67
|
+
? joined.slice(dir.length + 1).split("/")
|
|
68
|
+
: null;
|
|
69
|
+
let root;
|
|
70
|
+
let rest;
|
|
71
|
+
const personal = under(PERSONAL_DIR);
|
|
72
|
+
const inProjects = under(PROJECTS_DIR);
|
|
73
|
+
const project = inProjects ? projectPathParts(inProjects) : null;
|
|
74
|
+
const [folder, ...inProject] = project?.rest ?? [];
|
|
75
|
+
if (personal) {
|
|
76
|
+
root = oceanPath(PERSONAL_DIR);
|
|
77
|
+
rest = personal;
|
|
78
|
+
}
|
|
79
|
+
else if (project && folder === "memory" && inProject.length) {
|
|
80
|
+
root = oceanPath(projectDir(project.id), "memory");
|
|
81
|
+
rest = inProject;
|
|
82
|
+
}
|
|
83
|
+
else
|
|
84
|
+
throw new MemoryContractError("OUT_OF_ROOT", `records live under ${PERSONAL_DIR}/ or ${PROJECTS_DIR}/[<category>/]<project>/memory/`);
|
|
85
|
+
let file;
|
|
86
|
+
try {
|
|
87
|
+
file = resolveWithin(root, ...rest);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
throw new MemoryContractError("OUT_OF_ROOT", "path escapes its root");
|
|
91
|
+
}
|
|
92
|
+
assertNoLinks(root, file);
|
|
93
|
+
return {
|
|
94
|
+
file,
|
|
95
|
+
relative: workspaceRelative(file),
|
|
96
|
+
root,
|
|
97
|
+
personal: Boolean(personal),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// A project doc, `04-projects/<id>/docs/**/*.md` (T-307): written only as a new file, by an
|
|
101
|
+
// approved or auto-filed candidate — never through memory_write, so a doc the owner wrote is
|
|
102
|
+
// never changed or removed by a client.
|
|
103
|
+
export function projectDocument(relative) {
|
|
104
|
+
const parts = segments(relative);
|
|
105
|
+
const name = parts.at(-1) ?? "";
|
|
106
|
+
if (!name.toLowerCase().endsWith(".md"))
|
|
107
|
+
throw new MemoryContractError("INVALID", "a doc must be a .md file");
|
|
108
|
+
const joined = parts.join("/");
|
|
109
|
+
const project = joined.startsWith(`${PROJECTS_DIR}/`)
|
|
110
|
+
? projectPathParts(joined.slice(PROJECTS_DIR.length + 1).split("/"))
|
|
111
|
+
: null;
|
|
112
|
+
const [folder, ...inside] = project?.rest ?? [];
|
|
113
|
+
if (!project || folder !== "docs" || !inside.length)
|
|
114
|
+
throw new MemoryContractError("OUT_OF_ROOT", `docs live under ${PROJECTS_DIR}/[<category>/]<project>/docs/`);
|
|
115
|
+
const root = oceanPath(projectDir(project.id), "docs");
|
|
116
|
+
let file;
|
|
117
|
+
try {
|
|
118
|
+
file = resolveWithin(root, ...inside);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
throw new MemoryContractError("OUT_OF_ROOT", "path escapes its root");
|
|
122
|
+
}
|
|
123
|
+
assertNoLinks(root, file);
|
|
124
|
+
return { file, relative: workspaceRelative(file), root, personal: false };
|
|
125
|
+
}
|
|
126
|
+
function stateDir(...parts) {
|
|
127
|
+
return oceanPath(SYSTEM_DIR, "state", ...parts);
|
|
128
|
+
}
|
|
129
|
+
export function candidatesRoot() {
|
|
130
|
+
return oceanPath(INBOX_DIR, "candidates");
|
|
131
|
+
}
|
|
132
|
+
// Server-named only: <candidates>/<yyyy-mm-dd>/<id>.md.
|
|
133
|
+
export function candidateFile(day, id) {
|
|
134
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(day) || !/^[0-9a-z-]{8,64}$/.test(id))
|
|
135
|
+
throw new MemoryContractError("INVALID", "bad candidate name");
|
|
136
|
+
const root = candidatesRoot();
|
|
137
|
+
const file = resolveWithin(root, day, `${id}.md`);
|
|
138
|
+
assertNoLinks(root, file);
|
|
139
|
+
return file;
|
|
140
|
+
}
|
|
141
|
+
// Where a client's own memory feature is redirected to (e.g. Claude Code's
|
|
142
|
+
// autoMemoryDirectory). Ocean only reads it, to turn its files into candidates.
|
|
143
|
+
export function stagingDir(client) {
|
|
144
|
+
if (!clientIdPattern.test(client))
|
|
145
|
+
throw new MemoryContractError("INVALID", `bad client id '${client}'`);
|
|
146
|
+
return resolveWithin(oceanPath(INBOX_DIR, "native"), client);
|
|
147
|
+
}
|
|
148
|
+
export function versionsRoot() {
|
|
149
|
+
return stateDir("memory", "versions");
|
|
150
|
+
}
|
|
151
|
+
export function trashRoot() {
|
|
152
|
+
return stateDir("memory", "trash");
|
|
153
|
+
}
|
|
154
|
+
// A stored copy of a record: <versions|trash>/<id>/<workspace-relative path>.
|
|
155
|
+
export function storedCopy(kind, id, relative) {
|
|
156
|
+
const root = kind === "versions" ? versionsRoot() : trashRoot();
|
|
157
|
+
if (!/^[0-9a-z-]{8,64}$/.test(id))
|
|
158
|
+
throw new MemoryContractError("INVALID", "bad stored copy id");
|
|
159
|
+
const file = resolveWithin(root, id, ...segments(relative));
|
|
160
|
+
assertNoLinks(root, file);
|
|
161
|
+
return file;
|
|
162
|
+
}
|
|
163
|
+
// Content hashes of harvested native-memory texts → their candidate (T-296 P4).
|
|
164
|
+
export function harvestLedgerFile() {
|
|
165
|
+
return stateDir("memory", "harvest.json");
|
|
166
|
+
}
|
|
167
|
+
export function auditFile() {
|
|
168
|
+
return stateDir("memory", "audit.jsonl");
|
|
169
|
+
}
|
|
170
|
+
// The client-configuration journal (P2): only Ocean-managed values, never whole client files.
|
|
171
|
+
export function clientJournalFile() {
|
|
172
|
+
return stateDir("clients", "journal.jsonl");
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=memory-paths.js.map
|