@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.
- 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/dist/memory/signal-detection.js +66 -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/security/sandbox.js +74 -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-curation.js +123 -0
- package/node_modules/@ocean/market/dist/skills/skill-hub.js +220 -0
- package/node_modules/@ocean/market/dist/skills/task-observer.js +165 -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,145 @@
|
|
|
1
|
+
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { redactRuntimeText } from "@ocean/core/domain/redaction/redaction.js";
|
|
4
|
+
import { localDate, localMoment, localTime, } from "@ocean/core/shared/local-date.js";
|
|
5
|
+
import { INBOX_DIR, oceanPath, oceanRoot } from "@ocean/core/shared/paths.js";
|
|
6
|
+
import { findGitRoot } from "@ocean/core/shared/project-resolution.js";
|
|
7
|
+
// Same fallback title stamped on a session with no explicit title (see
|
|
8
|
+
// daily-narrative.ts) — never meaningful content on its own.
|
|
9
|
+
const GENERIC_TITLE = /^(claude|codex|hermes|kimi|gemini|copilot|kilo|antigravity|openhands) session$/i;
|
|
10
|
+
function safeText(value, max = 400) {
|
|
11
|
+
return redactRuntimeText(value).replace(/\s+/g, " ").trim().slice(0, max);
|
|
12
|
+
}
|
|
13
|
+
function isGenericContent(text) {
|
|
14
|
+
const trimmed = (text ?? "").trim();
|
|
15
|
+
return trimmed.length === 0 || GENERIC_TITLE.test(trimmed);
|
|
16
|
+
}
|
|
17
|
+
const MIN_USEFUL_SLUG_LENGTH = 3;
|
|
18
|
+
// ASCII-only by design: ranks a Latin-alphabet slug of the title over one
|
|
19
|
+
// derived from the (already ASCII) project name, and falls back to the
|
|
20
|
+
// session id only when both are too short to be useful — e.g. a title in a
|
|
21
|
+
// non-Latin script, where this strips down to nothing.
|
|
22
|
+
function slugify(text, maxLength = 60) {
|
|
23
|
+
return text
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
26
|
+
.replace(/^-+|-+$/g, "")
|
|
27
|
+
.slice(0, maxLength)
|
|
28
|
+
.replace(/-+$/g, "");
|
|
29
|
+
}
|
|
30
|
+
async function uniqueFilename(directory, base, ext) {
|
|
31
|
+
let existing;
|
|
32
|
+
try {
|
|
33
|
+
existing = new Set(await readdir(directory));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
existing = new Set();
|
|
37
|
+
}
|
|
38
|
+
if (!existing.has(`${base}${ext}`))
|
|
39
|
+
return `${base}${ext}`;
|
|
40
|
+
for (let suffix = 2; suffix < 1000; suffix++) {
|
|
41
|
+
const candidate = `${base}-${suffix}${ext}`;
|
|
42
|
+
if (!existing.has(candidate))
|
|
43
|
+
return candidate;
|
|
44
|
+
}
|
|
45
|
+
return `${base}-${Date.now()}${ext}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* One human-readable Markdown file per session under brain/00-inbox/brain-dump/,
|
|
49
|
+
* separate from the machine-oriented kernel/bridge/sessions/summaries/ dump. Prefers
|
|
50
|
+
* the cheap model narrative (see model-narrative.ts) and falls back to the
|
|
51
|
+
* same deterministic heuristics as daily-narrative.ts when it is unavailable.
|
|
52
|
+
* When lesson-learning.ts already found signals for this session (corrections,
|
|
53
|
+
* repeated procedures, explicit decisions), they're mirrored under
|
|
54
|
+
* "## Signals" — the same events already surfaced in brain/01-daily/, so a
|
|
55
|
+
* later cross-session sweep has one place to look instead of two.
|
|
56
|
+
*
|
|
57
|
+
* Skipped for the same low-value case daily-narrative.ts skips: a generic
|
|
58
|
+
* title with no real (git) project behind it — nothing a human would want to
|
|
59
|
+
* read later.
|
|
60
|
+
*/
|
|
61
|
+
export async function writeBrainDump(input) {
|
|
62
|
+
const { session, events, narrative, changedFiles, observations } = input;
|
|
63
|
+
const workLog = narrative?.workLog ??
|
|
64
|
+
(() => {
|
|
65
|
+
const objectiveEvent = events.find((event) => event.type === "user_input" || event.type === "resume_requested");
|
|
66
|
+
return safeText(objectiveEvent?.data ?? session.title, 200);
|
|
67
|
+
})();
|
|
68
|
+
const hasRealProject = findGitRoot(session.workingDirectory) !== null;
|
|
69
|
+
if (isGenericContent(workLog) && !hasRealProject)
|
|
70
|
+
return null;
|
|
71
|
+
const decision = narrative?.decision ??
|
|
72
|
+
events
|
|
73
|
+
.filter((event) => event.type === "provider_output" ||
|
|
74
|
+
event.type === "text" ||
|
|
75
|
+
event.type === "json")
|
|
76
|
+
.map((event) => safeText(event.data, 500))
|
|
77
|
+
.find((text) => /\bdecision\s*:/i.test(text));
|
|
78
|
+
const problem = narrative?.problem ??
|
|
79
|
+
events
|
|
80
|
+
.filter((event) => event.type === "error" || event.type === "provider_blocked")
|
|
81
|
+
.map((event) => safeText(event.data, 500))[0];
|
|
82
|
+
const nextAction = narrative?.next ?? input.nextAction ?? session.nextAction;
|
|
83
|
+
const hasNextAction = nextAction &&
|
|
84
|
+
nextAction !== "Review the summary and verify the next action.";
|
|
85
|
+
const started = localMoment(session.createdAt);
|
|
86
|
+
const time = localTime(started);
|
|
87
|
+
const date = localDate(started);
|
|
88
|
+
const project = path.basename(session.workingDirectory) || session.workingDirectory;
|
|
89
|
+
const status = session.status === "completed" && (input.exitCode ?? 0) === 0
|
|
90
|
+
? "completed"
|
|
91
|
+
: "failed";
|
|
92
|
+
const title = isGenericContent(workLog) ? project : workLog;
|
|
93
|
+
const lines = [
|
|
94
|
+
`# ${title}`,
|
|
95
|
+
"",
|
|
96
|
+
`- Date: ${date} ${time}`,
|
|
97
|
+
`- Provider: ${session.provider}`,
|
|
98
|
+
`- Project: ${project}`,
|
|
99
|
+
`- Status: ${status}`,
|
|
100
|
+
"",
|
|
101
|
+
"## What happened",
|
|
102
|
+
"",
|
|
103
|
+
workLog || "No objective was captured.",
|
|
104
|
+
...(decision ? ["", "## Decision", "", decision] : []),
|
|
105
|
+
...(problem ? ["", "## Problem", "", problem] : []),
|
|
106
|
+
...(changedFiles.length
|
|
107
|
+
? [
|
|
108
|
+
"",
|
|
109
|
+
"## Files changed",
|
|
110
|
+
"",
|
|
111
|
+
...changedFiles.map((f) => `- ${safeText(f, 240)}`),
|
|
112
|
+
]
|
|
113
|
+
: []),
|
|
114
|
+
...(hasNextAction
|
|
115
|
+
? ["", "## Next", "", safeText(nextAction, 300)]
|
|
116
|
+
: []),
|
|
117
|
+
...(observations?.length
|
|
118
|
+
? [
|
|
119
|
+
"",
|
|
120
|
+
"## Signals",
|
|
121
|
+
"",
|
|
122
|
+
...observations.map((o) => `- [${o.signalType}] ${safeText(o.summary, 300)} (confidence ${o.confidence})`),
|
|
123
|
+
]
|
|
124
|
+
: []),
|
|
125
|
+
"",
|
|
126
|
+
"---",
|
|
127
|
+
"",
|
|
128
|
+
`Session id: ${session.sessionId} · Raw events: \`ocean session events ${session.sessionId}\``,
|
|
129
|
+
];
|
|
130
|
+
const directory = oceanPath(INBOX_DIR, "brain-dump");
|
|
131
|
+
await mkdir(directory, { recursive: true });
|
|
132
|
+
const titleSlug = slugify(title);
|
|
133
|
+
const slug = titleSlug.length >= MIN_USEFUL_SLUG_LENGTH
|
|
134
|
+
? titleSlug
|
|
135
|
+
: slugify(project).length >= MIN_USEFUL_SLUG_LENGTH
|
|
136
|
+
? slugify(project)
|
|
137
|
+
: session.sessionId.slice(0, 8);
|
|
138
|
+
const filename = await uniqueFilename(directory, `${date}-${time.replace(":", "")}-${slug}`, ".md");
|
|
139
|
+
const file = path.join(directory, filename);
|
|
140
|
+
await writeFile(file, `${lines.join("\n")}\n`, "utf8");
|
|
141
|
+
return {
|
|
142
|
+
brainDumpPath: path.relative(oceanRoot(), file).split(path.sep).join("/"),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=brain-dump.js.map
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { mkdir, readFile, realpath } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { redactRuntimeText } from "@ocean/core/domain/redaction/redaction.js";
|
|
4
|
+
import { atomicWrite, withFileLock } from "@ocean/core/shared/fs-utils.js";
|
|
5
|
+
import { localDate } from "@ocean/core/shared/local-date.js";
|
|
6
|
+
import { DAILY_DIR, oceanPath } from "@ocean/core/shared/paths.js";
|
|
7
|
+
import { findGitRoot } from "@ocean/core/shared/project-resolution.js";
|
|
8
|
+
const dailyTemplate = (date) => `# Daily — ${date}\n\n## Focus\n\n## Work log\n\n## Decisions\n\n## Problems\n\n## Next\n\n## Observations\n`;
|
|
9
|
+
// The literal fallback title stamped on a session with no explicit title
|
|
10
|
+
// (see run request construction) — never meaningful content on its own.
|
|
11
|
+
const GENERIC_TITLE = /^claude session$/i;
|
|
12
|
+
function safeText(value, max = 240) {
|
|
13
|
+
return redactRuntimeText(value).replace(/\s+/g, " ").trim().slice(0, max);
|
|
14
|
+
}
|
|
15
|
+
function isGenericContent(text) {
|
|
16
|
+
const trimmed = (text ?? "").trim();
|
|
17
|
+
return trimmed.length === 0 || GENERIC_TITLE.test(trimmed);
|
|
18
|
+
}
|
|
19
|
+
// Markup a client wraps around a prompt (`<ide_opened_file>…</ide_opened_file>`,
|
|
20
|
+
// `<task-notification>…`) is not what the session was about. Client tags carry `_` or `-`,
|
|
21
|
+
// which HTML tag names never do, so a prompt about `<button>Save</button>` keeps its words.
|
|
22
|
+
const CLIENT_TAG = "[a-z][a-z0-9]*(?:[_-][a-z0-9]+)+";
|
|
23
|
+
function withoutClientMarkup(text) {
|
|
24
|
+
return text
|
|
25
|
+
.replace(new RegExp(`<(${CLIENT_TAG})\\b[^>]*>[\\s\\S]*?<\\/\\1>`, "gi"), " ")
|
|
26
|
+
.replace(new RegExp(`<\\/?${CLIENT_TAG}\\b[^>]*>`, "gi"), " ");
|
|
27
|
+
}
|
|
28
|
+
// Local wall-clock date and time, like the SessionEnd hook's own line: a UTC stamp put one
|
|
29
|
+
// session three hours apart in the same log, and after midnight UTC into another day's file.
|
|
30
|
+
function localStamp(now = new Date()) {
|
|
31
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
32
|
+
return {
|
|
33
|
+
date: localDate(now),
|
|
34
|
+
time: `${pad(now.getHours())}:${pad(now.getMinutes())}`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function insertUnderHeading(content, heading, line) {
|
|
38
|
+
// A client that reports one session end twice must not log it twice.
|
|
39
|
+
if (content.split("\n").includes(line))
|
|
40
|
+
return content;
|
|
41
|
+
const marker = `${heading}\n`;
|
|
42
|
+
const index = content.indexOf(marker);
|
|
43
|
+
if (index < 0)
|
|
44
|
+
return `${content.trimEnd()}\n\n${heading}\n${line}\n`;
|
|
45
|
+
const insertAt = index + marker.length;
|
|
46
|
+
return `${content.slice(0, insertAt)}${line}\n${content.slice(insertAt)}`;
|
|
47
|
+
}
|
|
48
|
+
// Read-modify-write of today's daily file. Two sessions closing out at once would otherwise
|
|
49
|
+
// each write back their own copy and drop the other's lines (T-257), so it runs under the
|
|
50
|
+
// file lock and lands atomically.
|
|
51
|
+
async function updateDailyFile(update) {
|
|
52
|
+
const { date } = localStamp();
|
|
53
|
+
const directory = oceanPath(DAILY_DIR);
|
|
54
|
+
const file = path.join(directory, `${date}.md`);
|
|
55
|
+
await mkdir(directory, { recursive: true });
|
|
56
|
+
await withFileLock(file, async () => {
|
|
57
|
+
let content;
|
|
58
|
+
try {
|
|
59
|
+
content = await readFile(file, "utf8");
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// Only a missing file starts from the template: any other read failure must not
|
|
63
|
+
// replace a real day's log with an empty one.
|
|
64
|
+
if (error.code !== "ENOENT")
|
|
65
|
+
throw error;
|
|
66
|
+
content = dailyTemplate(date);
|
|
67
|
+
}
|
|
68
|
+
// A symlinked daily file keeps its link: the write goes to the real file.
|
|
69
|
+
const target = await realpath(file).catch(() => file);
|
|
70
|
+
await atomicWrite(target, update(content));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Human-readable narrative entry appended to today's
|
|
75
|
+
* brain/01-daily/YYYY-MM-DD.md at session closeout. A field from `narrative`
|
|
76
|
+
* (produced by a cheap model call — see model-narrative.ts) is used verbatim
|
|
77
|
+
* when present; any field it omits (including when the model call was
|
|
78
|
+
* skipped or failed entirely) falls back to the deterministic heuristic
|
|
79
|
+
* below, so this never depends on the model call succeeding.
|
|
80
|
+
*/
|
|
81
|
+
export async function appendDailyNarrative(input) {
|
|
82
|
+
const { session, events, narrative } = input;
|
|
83
|
+
const inserts = [];
|
|
84
|
+
const { time } = localStamp();
|
|
85
|
+
const project = path.basename(session.workingDirectory) || session.workingDirectory;
|
|
86
|
+
const status = session.status === "completed" && (input.exitCode ?? 0) === 0
|
|
87
|
+
? "completed"
|
|
88
|
+
: "failed";
|
|
89
|
+
const workLog = narrative?.workLog ??
|
|
90
|
+
(() => {
|
|
91
|
+
const objectiveEvent = events.find((event) => event.type === "user_input" || event.type === "resume_requested");
|
|
92
|
+
// A prompt that was only client markup falls back to the session's title.
|
|
93
|
+
const prompt = safeText(withoutClientMarkup(objectiveEvent?.data ?? ""));
|
|
94
|
+
return safeText(prompt || session.title, 160);
|
|
95
|
+
})();
|
|
96
|
+
// Skip an entry with nothing to say (the SessionEnd hook already notes that a session
|
|
97
|
+
// ended), and the doubly-useless case: only the generic placeholder title AND no real
|
|
98
|
+
// project (root/home session, no git repo). A generic title in a real project, or real
|
|
99
|
+
// content with no project, still gets logged.
|
|
100
|
+
const hasRealProject = findGitRoot(session.workingDirectory) !== null;
|
|
101
|
+
const skipWorkLog = !workLog.trim() || (isGenericContent(workLog) && !hasRealProject);
|
|
102
|
+
if (!skipWorkLog) {
|
|
103
|
+
inserts.push([
|
|
104
|
+
"## Work log",
|
|
105
|
+
`- ${time} ${session.provider} session in ${project}: ${workLog} — ${status}`,
|
|
106
|
+
]);
|
|
107
|
+
}
|
|
108
|
+
const decisionLine = narrative?.decision ??
|
|
109
|
+
events
|
|
110
|
+
.filter((event) => event.type === "provider_output" ||
|
|
111
|
+
event.type === "text" ||
|
|
112
|
+
event.type === "json")
|
|
113
|
+
.map((event) => safeText(event.data, 500))
|
|
114
|
+
.find((text) => /\bdecision\s*:/i.test(text));
|
|
115
|
+
if (decisionLine)
|
|
116
|
+
inserts.push(["## Decisions", `- ${decisionLine}`]);
|
|
117
|
+
const problemLine = narrative?.problem ??
|
|
118
|
+
events
|
|
119
|
+
.filter((event) => event.type === "error" || event.type === "provider_blocked")
|
|
120
|
+
.map((event) => safeText(event.data, 500))[0];
|
|
121
|
+
if (problemLine)
|
|
122
|
+
inserts.push(["## Problems", `- ${problemLine}`]);
|
|
123
|
+
const nextAction = narrative?.next ?? input.nextAction ?? session.nextAction;
|
|
124
|
+
if (nextAction &&
|
|
125
|
+
nextAction !== "Review the summary and verify the next action.") {
|
|
126
|
+
inserts.push(["## Next", `- [${project}] ${safeText(nextAction, 240)}`]);
|
|
127
|
+
}
|
|
128
|
+
await updateDailyFile((content) => inserts.reduce((next, [heading, line]) => insertUnderHeading(next, heading, line), content));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Appends newly detected observations (corrections, repeated procedures,
|
|
132
|
+
* explicit decisions, proven verifications — see lesson-learning.ts) to
|
|
133
|
+
* today's brain/01-daily/YYYY-MM-DD.md, so they surface next to the
|
|
134
|
+
* session narrative instead of sitting only in 05-knowledge/lessons.
|
|
135
|
+
*/
|
|
136
|
+
export async function appendObservations(session, observations) {
|
|
137
|
+
if (!observations.length)
|
|
138
|
+
return;
|
|
139
|
+
const project = path.basename(session.workingDirectory) || session.workingDirectory;
|
|
140
|
+
await updateDailyFile((content) => observations.reduce((next, observation) => insertUnderHeading(next, "## Observations", `- [${observation.signalType}] ${safeText(observation.summary, 300)} _(${project}, confidence ${observation.confidence})_`), content));
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=daily-narrative.js.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { oceanPath, STORE_DIR } from "@ocean/core/shared/paths.js";
|
|
5
|
+
import { conflictsFile, lessonsDir } from "./lessons.js";
|
|
6
|
+
const generatedStart = "## Records (generated)";
|
|
7
|
+
async function markdownFiles(directory) {
|
|
8
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
9
|
+
const files = [];
|
|
10
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
11
|
+
if (entry.name.startsWith(".") || entry.name === "archive")
|
|
12
|
+
continue;
|
|
13
|
+
const full = path.join(directory, entry.name);
|
|
14
|
+
if (entry.isDirectory())
|
|
15
|
+
files.push(...(await markdownFiles(full)));
|
|
16
|
+
else if (entry.name.endsWith(".md"))
|
|
17
|
+
files.push(full);
|
|
18
|
+
}
|
|
19
|
+
return files;
|
|
20
|
+
}
|
|
21
|
+
function title(content, fallback) {
|
|
22
|
+
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/m)?.[1] ?? "";
|
|
23
|
+
const declared = frontmatter
|
|
24
|
+
.match(/^(?:title|name):\s*["']?(.+?)["']?\s*$/m)?.[1]
|
|
25
|
+
?.trim();
|
|
26
|
+
if (declared)
|
|
27
|
+
return declared;
|
|
28
|
+
const heading = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
29
|
+
if (heading &&
|
|
30
|
+
!/^(decision|discovery|failure|solution|research|result|architecture)$/i.test(heading))
|
|
31
|
+
return heading;
|
|
32
|
+
const summary = content
|
|
33
|
+
.replace(/^---[\s\S]*?---\s*/m, "")
|
|
34
|
+
.split("\n")
|
|
35
|
+
.map((line) => line.trim())
|
|
36
|
+
.find((line) => line &&
|
|
37
|
+
!line.startsWith("#") &&
|
|
38
|
+
!line.startsWith("`") &&
|
|
39
|
+
!line.startsWith("|") &&
|
|
40
|
+
!line.startsWith("-"));
|
|
41
|
+
if (summary)
|
|
42
|
+
return summary.replace(/[*_`]/g, "").slice(0, 120);
|
|
43
|
+
return (fallback.split("/").at(-1)?.replace(/\.md$/, "").replace(/[-_]+/g, " ") ??
|
|
44
|
+
fallback);
|
|
45
|
+
}
|
|
46
|
+
function relativeRecord(root, file) {
|
|
47
|
+
return path.relative(root, file).split(path.sep).join("/");
|
|
48
|
+
}
|
|
49
|
+
function links(content) {
|
|
50
|
+
return new Set([...content.matchAll(/\]\(([^)#]+)(?:#[^)]+)?\)/g)].map((match) => match[1]));
|
|
51
|
+
}
|
|
52
|
+
// Lesson records are machine-managed (T-230) and found through the brain index, so the curated
|
|
53
|
+
// KNOWLEDGE.md neither demands nor lists them. CONFLICTS.md beside them waits on a human, so it
|
|
54
|
+
// stays an indexed record.
|
|
55
|
+
function isLessonRecord(store, file) {
|
|
56
|
+
if (store !== "knowledge" || file === conflictsFile())
|
|
57
|
+
return false;
|
|
58
|
+
const relative = path.relative(lessonsDir(), file);
|
|
59
|
+
return (relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative));
|
|
60
|
+
}
|
|
61
|
+
async function syncStore(store, apply) {
|
|
62
|
+
const root = oceanPath(STORE_DIR[store]);
|
|
63
|
+
const indexName = store === "memory" ? "MEMORY.md" : "KNOWLEDGE.md";
|
|
64
|
+
const index = path.join(root, indexName);
|
|
65
|
+
const current = await readFile(index, "utf8");
|
|
66
|
+
const recordFiles = (await markdownFiles(root)).filter((file) => path.basename(file) !== indexName && !isLessonRecord(store, file));
|
|
67
|
+
const known = links(current);
|
|
68
|
+
const missing = recordFiles.filter((file) => !known.has(relativeRecord(root, file)));
|
|
69
|
+
const broken = [];
|
|
70
|
+
for (const link of known) {
|
|
71
|
+
if (!link.startsWith("http") &&
|
|
72
|
+
!(await awaitedExists(path.resolve(root, link))))
|
|
73
|
+
broken.push(link);
|
|
74
|
+
}
|
|
75
|
+
if (!apply)
|
|
76
|
+
return {
|
|
77
|
+
store,
|
|
78
|
+
missing: missing.map((file) => relativeRecord(root, file)),
|
|
79
|
+
broken,
|
|
80
|
+
changed: false,
|
|
81
|
+
};
|
|
82
|
+
const generated = [
|
|
83
|
+
generatedStart,
|
|
84
|
+
"",
|
|
85
|
+
...recordFiles.map((file) => {
|
|
86
|
+
const relative = relativeRecord(root, file);
|
|
87
|
+
return `- [${title(readFileSync(file, "utf8"), relative)}](${relative})`;
|
|
88
|
+
}),
|
|
89
|
+
].join("\n");
|
|
90
|
+
const next = current.includes(generatedStart)
|
|
91
|
+
? `${current.slice(0, current.indexOf(generatedStart)).trimEnd()}\n\n${generated}\n`
|
|
92
|
+
: `${current.trimEnd()}\n\n${generated}\n`;
|
|
93
|
+
const changed = next !== current;
|
|
94
|
+
if (changed)
|
|
95
|
+
await writeFile(index, next);
|
|
96
|
+
return {
|
|
97
|
+
store,
|
|
98
|
+
missing: missing.map((file) => relativeRecord(root, file)),
|
|
99
|
+
broken,
|
|
100
|
+
changed,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function awaitedExists(file) {
|
|
104
|
+
try {
|
|
105
|
+
await readFile(file);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export async function syncMemoryIndexes(apply) {
|
|
113
|
+
return Promise.all([
|
|
114
|
+
syncStore("memory", apply),
|
|
115
|
+
syncStore("knowledge", apply),
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
export async function doctorMemoryIndexes() {
|
|
119
|
+
const results = await syncMemoryIndexes(false);
|
|
120
|
+
const missing = results.flatMap((result) => result.missing.map((file) => `${result.store}/${file}`));
|
|
121
|
+
const broken = results.flatMap((result) => result.broken.map((file) => `${result.store}/${file}`));
|
|
122
|
+
return { ok: missing.length === 0 && broken.length === 0, missing, broken };
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=index-sync.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { truncateUtf8 } from "@ocean/core/shared/fs-utils.js";
|
|
2
|
+
import { brainSearch } from "../brain/brain-service.js";
|
|
3
|
+
import { effectiveConfidence, readLesson } from "./lessons.js";
|
|
4
|
+
// Which lessons a run starts with (T-230 P3). Candidates come from the brain index, not a
|
|
5
|
+
// substring match on the lesson's name; the lesson file then has the last word, so a lesson
|
|
6
|
+
// retired or decayed since the index was built never activates.
|
|
7
|
+
export const MIN_ACTIVE_CONFIDENCE = 0.2;
|
|
8
|
+
const LESSONS_PATH_PREFIX = "knowledge/lessons/";
|
|
9
|
+
const MAX_QUERY_TOKENS = 8;
|
|
10
|
+
const HITS_PER_TOKEN = 5;
|
|
11
|
+
// The index ANDs every token of a query, so a whole prompt would match nothing: each
|
|
12
|
+
// distinctive word is asked separately and a lesson is ranked by how many of them it answers.
|
|
13
|
+
const STOPWORDS = new Set("the and for you are but not can was has how why who all any its our out one see now new did let may way too via per use get about after again also because before being between could does doing from have into just like make more most other should some than that their them then there these they this those through using very want what when where which while with would your".split(" "));
|
|
14
|
+
export function queryTokens(prompt) {
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
for (const match of prompt
|
|
17
|
+
.toLocaleLowerCase()
|
|
18
|
+
.matchAll(/[\p{L}\p{N}][\p{L}\p{N}-]{2,}/gu))
|
|
19
|
+
if (!STOPWORDS.has(match[0]))
|
|
20
|
+
seen.add(match[0]);
|
|
21
|
+
return [...seen]
|
|
22
|
+
.sort((a, b) => b.length - a.length || a.localeCompare(b))
|
|
23
|
+
.slice(0, MAX_QUERY_TOKENS);
|
|
24
|
+
}
|
|
25
|
+
export async function selectActiveLessons(prompt, options) {
|
|
26
|
+
const tokens = queryTokens(prompt);
|
|
27
|
+
if (!tokens.length)
|
|
28
|
+
return [];
|
|
29
|
+
const matched = new Map();
|
|
30
|
+
// A token whose search fails (no index built, one that predates record scopes, a transient
|
|
31
|
+
// lock) is skipped: with no index at all nothing activates, which is the safe default, and
|
|
32
|
+
// `ocean memory reindex` brings lessons back.
|
|
33
|
+
const results = await Promise.all(tokens.map((token) => brainSearch({
|
|
34
|
+
query: token,
|
|
35
|
+
root: options.root,
|
|
36
|
+
limit: HITS_PER_TOKEN,
|
|
37
|
+
types: ["experiential"],
|
|
38
|
+
pathPrefix: LESSONS_PATH_PREFIX,
|
|
39
|
+
scope: { project: null },
|
|
40
|
+
indexPort: options.indexPort,
|
|
41
|
+
}).catch(() => null)));
|
|
42
|
+
for (const result of results)
|
|
43
|
+
for (const hit of result?.results ?? [])
|
|
44
|
+
matched.set(hit.id, (matched.get(hit.id) ?? 0) + 1);
|
|
45
|
+
// A short prompt has few tokens to match; a long one must match more than a stray word.
|
|
46
|
+
const needed = tokens.length >= 3 ? 2 : 1;
|
|
47
|
+
const ranked = [];
|
|
48
|
+
for (const [id, matches] of matched) {
|
|
49
|
+
if (matches < needed)
|
|
50
|
+
continue;
|
|
51
|
+
// A lesson file that cannot be read is skipped: lessons are optional context and one
|
|
52
|
+
// bad file must not fail the run that merely matched it.
|
|
53
|
+
const lesson = await readLesson(id).catch(() => null);
|
|
54
|
+
if (lesson?.status !== "active")
|
|
55
|
+
continue;
|
|
56
|
+
const confidence = effectiveConfidence(lesson, options.now);
|
|
57
|
+
if (confidence < MIN_ACTIVE_CONFIDENCE)
|
|
58
|
+
continue;
|
|
59
|
+
ranked.push({ lesson, matches, confidence });
|
|
60
|
+
}
|
|
61
|
+
ranked.sort((a, b) => b.matches - a.matches ||
|
|
62
|
+
b.confidence - a.confidence ||
|
|
63
|
+
a.lesson.id.localeCompare(b.lesson.id));
|
|
64
|
+
const selected = [];
|
|
65
|
+
let bytes = 0;
|
|
66
|
+
for (const { lesson, confidence } of ranked) {
|
|
67
|
+
const remaining = options.maxBytes - bytes;
|
|
68
|
+
if (remaining <= 0)
|
|
69
|
+
break;
|
|
70
|
+
const ref = {
|
|
71
|
+
id: lesson.id,
|
|
72
|
+
title: lesson.title,
|
|
73
|
+
sourceSessionId: lesson.sourceSessionId,
|
|
74
|
+
confidence,
|
|
75
|
+
};
|
|
76
|
+
const size = Buffer.byteLength(lesson.body);
|
|
77
|
+
if (size <= remaining) {
|
|
78
|
+
selected.push({ ...ref, body: lesson.body, truncated: false });
|
|
79
|
+
bytes += size;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// A usable pointer: the command that prints the full lesson.
|
|
83
|
+
const marker = `\n[truncated to the run budget; full text: \`ocean skill list ${lesson.id}\`]`;
|
|
84
|
+
const markerBytes = Buffer.byteLength(marker);
|
|
85
|
+
if (remaining <= markerBytes)
|
|
86
|
+
break;
|
|
87
|
+
const body = `${truncateUtf8(lesson.body, remaining - markerBytes)}${marker}`;
|
|
88
|
+
selected.push({ ...ref, body, truncated: true });
|
|
89
|
+
bytes += Buffer.byteLength(body);
|
|
90
|
+
}
|
|
91
|
+
return selected;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=lesson-activation.js.map
|