@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,587 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { clientSkillRoots, } from "@ocean/market/skills/core-skill-sync.js";
|
|
5
|
+
// Read-only measurement of each client's always-on context: the text an interactive client
|
|
6
|
+
// loads on every request before the user types anything (global rules, an auto-loaded memory
|
|
7
|
+
// index, the frontmatter of every model-visible skill, and for Claude its commands, agents
|
|
8
|
+
// and enabled plugins). Reports only display paths (~/...),
|
|
9
|
+
// byte counts and fixed notes — never file contents and never raw error messages, which can
|
|
10
|
+
// carry absolute paths.
|
|
11
|
+
export const DEFAULT_CONTEXT_BUDGET = 12_288;
|
|
12
|
+
// Keyed by every SkillClient, so TypeScript forces an entry when a client is added.
|
|
13
|
+
const clientSources = {
|
|
14
|
+
claude: {
|
|
15
|
+
rules: [".claude/CLAUDE.md"],
|
|
16
|
+
imports: true,
|
|
17
|
+
nested: false,
|
|
18
|
+
skillsRoot: clientSkillRoots.claude,
|
|
19
|
+
notes: ["project-level CLAUDE.md/AGENTS.md walk-up is not measured"],
|
|
20
|
+
},
|
|
21
|
+
codex: {
|
|
22
|
+
rules: [".codex/AGENTS.override.md", ".codex/AGENTS.md"],
|
|
23
|
+
imports: false,
|
|
24
|
+
nested: true,
|
|
25
|
+
skillsRoot: clientSkillRoots.codex,
|
|
26
|
+
notes: ["assumed: AGENTS.override.md precedence and ~/.agents usage"],
|
|
27
|
+
},
|
|
28
|
+
gemini: {
|
|
29
|
+
rules: [".gemini/GEMINI.md"],
|
|
30
|
+
imports: true,
|
|
31
|
+
nested: false,
|
|
32
|
+
skillsRoot: clientSkillRoots.gemini,
|
|
33
|
+
notes: [],
|
|
34
|
+
},
|
|
35
|
+
hermes: {
|
|
36
|
+
rules: [".hermes/SOUL.md"],
|
|
37
|
+
imports: false,
|
|
38
|
+
nested: true,
|
|
39
|
+
skillsRoot: clientSkillRoots.hermes,
|
|
40
|
+
notes: [
|
|
41
|
+
"assumed: SOUL.md is the always-on rules file",
|
|
42
|
+
"memories are not counted (auto-load unverified)",
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
cursor: {
|
|
46
|
+
rules: [],
|
|
47
|
+
imports: false,
|
|
48
|
+
nested: false,
|
|
49
|
+
skillsRoot: clientSkillRoots.cursor,
|
|
50
|
+
notes: ["user rules live in the app settings store, not a file"],
|
|
51
|
+
},
|
|
52
|
+
antigravity: {
|
|
53
|
+
rules: [],
|
|
54
|
+
imports: false,
|
|
55
|
+
nested: false,
|
|
56
|
+
skillsRoot: clientSkillRoots.antigravity,
|
|
57
|
+
notes: ["global rules location not known to the engine"],
|
|
58
|
+
},
|
|
59
|
+
agents: {
|
|
60
|
+
rules: [],
|
|
61
|
+
imports: false,
|
|
62
|
+
nested: false,
|
|
63
|
+
skillsRoot: (home) => path.join(home, ".agents", "skills"),
|
|
64
|
+
shared: true,
|
|
65
|
+
notes: [
|
|
66
|
+
"shared skills root; clients that symlink into it count the same files",
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
export function displayPath(abs, home) {
|
|
71
|
+
const relative = path.relative(home, abs);
|
|
72
|
+
if (!relative)
|
|
73
|
+
return "~";
|
|
74
|
+
if (!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
75
|
+
return `~/${relative.split(path.sep).join("/")}`;
|
|
76
|
+
return `<outside-home>/${path.basename(abs)}`;
|
|
77
|
+
}
|
|
78
|
+
async function sizeOf(file) {
|
|
79
|
+
try {
|
|
80
|
+
const info = await stat(file);
|
|
81
|
+
return info.isFile() ? info.size : null;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function isDirectory(target) {
|
|
88
|
+
try {
|
|
89
|
+
return (await stat(target)).isDirectory();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const IMPORT_TOKEN = /(?:^|\s)@(\S+)/g;
|
|
96
|
+
// `@path` tokens outside code fences and inline code spans that look like a path (so
|
|
97
|
+
// `@handle` mentions are ignored).
|
|
98
|
+
function importTargets(text) {
|
|
99
|
+
const targets = [];
|
|
100
|
+
let fenced = false;
|
|
101
|
+
for (const raw of text.split("\n")) {
|
|
102
|
+
if (raw.trimStart().startsWith("```")) {
|
|
103
|
+
fenced = !fenced;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (fenced)
|
|
107
|
+
continue;
|
|
108
|
+
const line = raw.replace(/`[^`]*`/g, " ");
|
|
109
|
+
for (const match of line.matchAll(IMPORT_TOKEN)) {
|
|
110
|
+
const token = match[1];
|
|
111
|
+
if (/^(~\/|\.\/|\.\.\/|\/)/.test(token) ||
|
|
112
|
+
token.includes("/") ||
|
|
113
|
+
token.endsWith(".md"))
|
|
114
|
+
targets.push(token);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return targets;
|
|
118
|
+
}
|
|
119
|
+
function resolveImport(token, includer, home) {
|
|
120
|
+
if (token.startsWith("~/"))
|
|
121
|
+
return path.join(home, token.slice(2));
|
|
122
|
+
if (path.isAbsolute(token))
|
|
123
|
+
return token;
|
|
124
|
+
return path.resolve(path.dirname(includer), token);
|
|
125
|
+
}
|
|
126
|
+
async function measureRules(sources, home) {
|
|
127
|
+
if (!sources.rules.length)
|
|
128
|
+
return [];
|
|
129
|
+
let chosen = null;
|
|
130
|
+
let bytes = null;
|
|
131
|
+
for (const candidate of sources.rules) {
|
|
132
|
+
const file = path.join(home, candidate);
|
|
133
|
+
bytes = await sizeOf(file);
|
|
134
|
+
if (bytes !== null) {
|
|
135
|
+
chosen = file;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (chosen === null || bytes === null) {
|
|
140
|
+
const canonical = path.join(home, sources.rules[sources.rules.length - 1]);
|
|
141
|
+
return [{ path: displayPath(canonical, home), bytes: 0, missing: true }];
|
|
142
|
+
}
|
|
143
|
+
const rules = [{ path: displayPath(chosen, home), bytes }];
|
|
144
|
+
if (!sources.imports)
|
|
145
|
+
return rules;
|
|
146
|
+
let text;
|
|
147
|
+
try {
|
|
148
|
+
text = await readFile(chosen, "utf8");
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
rules[0].unreadable = true;
|
|
152
|
+
return rules;
|
|
153
|
+
}
|
|
154
|
+
const seen = new Set([chosen]);
|
|
155
|
+
const via = displayPath(chosen, home);
|
|
156
|
+
for (const token of importTargets(text)) {
|
|
157
|
+
const target = resolveImport(token, chosen, home);
|
|
158
|
+
if (seen.has(target))
|
|
159
|
+
continue;
|
|
160
|
+
seen.add(target);
|
|
161
|
+
const size = await sizeOf(target);
|
|
162
|
+
rules.push(size === null
|
|
163
|
+
? { path: displayPath(target, home), bytes: 0, via, missing: true }
|
|
164
|
+
: { path: displayPath(target, home), bytes: size, via });
|
|
165
|
+
}
|
|
166
|
+
return rules;
|
|
167
|
+
}
|
|
168
|
+
// Walks up the path string (never realpath'd, matching how the slug is built) to the nearest
|
|
169
|
+
// `.git`. A `.git` directory marks the root; a `.git` file is a linked worktree (or a
|
|
170
|
+
// submodule), followed through `gitdir:` and `commondir` to the main checkout when it has
|
|
171
|
+
// one. Only git metadata is read, and nothing from it is reported.
|
|
172
|
+
async function memoryProjectRoot(projectDir) {
|
|
173
|
+
let current = projectDir;
|
|
174
|
+
for (;;) {
|
|
175
|
+
const dotGit = path.join(current, ".git");
|
|
176
|
+
const info = await stat(dotGit).catch(() => null);
|
|
177
|
+
if (info?.isDirectory())
|
|
178
|
+
return current;
|
|
179
|
+
if (info?.isFile())
|
|
180
|
+
return (await worktreeMainRoot(dotGit)) ?? current;
|
|
181
|
+
const parent = path.dirname(current);
|
|
182
|
+
if (parent === current)
|
|
183
|
+
return projectDir;
|
|
184
|
+
current = parent;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function worktreeMainRoot(dotGitFile) {
|
|
188
|
+
try {
|
|
189
|
+
const match = /^gitdir:[ \t]*(.+)$/m.exec(await readFile(dotGitFile, "utf8"));
|
|
190
|
+
if (!match)
|
|
191
|
+
return null;
|
|
192
|
+
const gitDir = path.resolve(path.dirname(dotGitFile), match[1].trim());
|
|
193
|
+
const commonDir = path.resolve(gitDir, (await readFile(path.join(gitDir, "commondir"), "utf8")).trim());
|
|
194
|
+
return path.basename(commonDir) === ".git" ? path.dirname(commonDir) : null;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function measureMemory(client, home, projectDir, notes) {
|
|
201
|
+
if (client !== "claude")
|
|
202
|
+
return [];
|
|
203
|
+
if (!projectDir) {
|
|
204
|
+
notes.push("claude per-project memory skipped; pass --project <dir>");
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
// Claude keys auto-memory by the project's git repository root (every subdirectory and
|
|
208
|
+
// worktree of one repo shares it), falling back to the directory itself outside a repo.
|
|
209
|
+
// The slug is that path string with every non-alphanumeric character replaced by '-'. It
|
|
210
|
+
// encodes an absolute path, so it is never displayed.
|
|
211
|
+
const slug = (await memoryProjectRoot(projectDir)).replace(/[^a-zA-Z0-9]/g, "-");
|
|
212
|
+
const file = path.join(home, ".claude", "projects", slug, "memory", "MEMORY.md");
|
|
213
|
+
const display = "~/.claude/projects/<project-slug>/memory/MEMORY.md";
|
|
214
|
+
notes.push("Claude loads up to the first 200 lines; whole-file bytes shown");
|
|
215
|
+
const bytes = await sizeOf(file);
|
|
216
|
+
return bytes === null
|
|
217
|
+
? [{ path: display, bytes: 0, missing: true }]
|
|
218
|
+
: [{ path: display, bytes }];
|
|
219
|
+
}
|
|
220
|
+
// Frontmatter is the leading `---` line through the closing `---` line inclusive.
|
|
221
|
+
function frontmatterBytes(buffer) {
|
|
222
|
+
const text = buffer.toString("utf8");
|
|
223
|
+
const open = /^---[ \t]*\r?\n/.exec(text);
|
|
224
|
+
if (!open)
|
|
225
|
+
return 0;
|
|
226
|
+
const close = /\n---[ \t]*(\r?\n|$)/.exec(text.slice(open[0].length - 1));
|
|
227
|
+
if (!close)
|
|
228
|
+
return 0;
|
|
229
|
+
const end = open[0].length - 1 + close.index + close[0].length;
|
|
230
|
+
return Buffer.byteLength(text.slice(0, end));
|
|
231
|
+
}
|
|
232
|
+
// The `name:` and `disable-model-invocation:` of a frontmatter block — all the listing rules
|
|
233
|
+
// need. A missing name falls back to the directory or file name.
|
|
234
|
+
function frontmatterFields(buffer, bytes) {
|
|
235
|
+
const text = buffer.subarray(0, bytes).toString("utf8");
|
|
236
|
+
const name = /^name:[ \t]*["']?([^"'\r\n]+?)["']?[ \t]*$/m.exec(text)?.[1];
|
|
237
|
+
return {
|
|
238
|
+
name: name ?? null,
|
|
239
|
+
modelHidden: /^disable-model-invocation:[ \t]*true[ \t]*$/m.test(text),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
// Claude's `skillOverrides` values that keep a skill out of the model's listing.
|
|
243
|
+
const HIDDEN_OVERRIDES = new Set(["off", "user-invocable-only"]);
|
|
244
|
+
async function readClaudeSettings(home, notes) {
|
|
245
|
+
const settings = { hidden: new Set(), enabledPlugins: [] };
|
|
246
|
+
let parsed;
|
|
247
|
+
try {
|
|
248
|
+
parsed = JSON.parse(await readFile(path.join(home, ".claude", "settings.json"), "utf8"));
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (error.code !== "ENOENT")
|
|
252
|
+
notes.push("settings.json unreadable; skill overrides not applied");
|
|
253
|
+
return settings;
|
|
254
|
+
}
|
|
255
|
+
if (!parsed || typeof parsed !== "object")
|
|
256
|
+
return settings;
|
|
257
|
+
const { skillOverrides, enabledPlugins } = parsed;
|
|
258
|
+
if (skillOverrides && typeof skillOverrides === "object")
|
|
259
|
+
for (const [name, value] of Object.entries(skillOverrides))
|
|
260
|
+
if (typeof value === "string" && HIDDEN_OVERRIDES.has(value))
|
|
261
|
+
settings.hidden.add(name);
|
|
262
|
+
if (enabledPlugins && typeof enabledPlugins === "object")
|
|
263
|
+
settings.enabledPlugins = Object.entries(enabledPlugins)
|
|
264
|
+
.filter(([, enabled]) => enabled === true)
|
|
265
|
+
.map(([id]) => id);
|
|
266
|
+
return settings;
|
|
267
|
+
}
|
|
268
|
+
// The skills the owner hid from Claude's model; `ocean client sync` hides the same ones from
|
|
269
|
+
// Codex and Gemini.
|
|
270
|
+
export async function claudeHiddenSkills(home = os.homedir()) {
|
|
271
|
+
return (await readClaudeSettings(home, [])).hidden;
|
|
272
|
+
}
|
|
273
|
+
// The SKILL.md paths that `~/.codex/config.toml` disables with
|
|
274
|
+
// `[[skills.config]] path = "..." enabled = false`.
|
|
275
|
+
export async function codexDisabledSkillPaths(home = os.homedir()) {
|
|
276
|
+
const disabled = new Set();
|
|
277
|
+
let text;
|
|
278
|
+
try {
|
|
279
|
+
text = await readFile(path.join(home, ".codex", "config.toml"), "utf8");
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return disabled;
|
|
283
|
+
}
|
|
284
|
+
let entry = null;
|
|
285
|
+
const flush = () => {
|
|
286
|
+
if (entry?.path && entry.enabled === false)
|
|
287
|
+
disabled.add(path.resolve(entry.path));
|
|
288
|
+
};
|
|
289
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
290
|
+
const line = raw.trim();
|
|
291
|
+
if (line.startsWith("[")) {
|
|
292
|
+
flush();
|
|
293
|
+
entry = line === "[[skills.config]]" ? {} : null;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (!entry)
|
|
297
|
+
continue;
|
|
298
|
+
const value = /^path\s*=\s*("(?:[^"\\]|\\.)*"|'[^']*')\s*(#.*)?$/.exec(line)?.[1];
|
|
299
|
+
if (value)
|
|
300
|
+
try {
|
|
301
|
+
entry.path = value.startsWith("'")
|
|
302
|
+
? value.slice(1, -1)
|
|
303
|
+
: JSON.parse(value);
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
// An unreadable path disables nothing.
|
|
307
|
+
}
|
|
308
|
+
const enabled = /^enabled\s*=\s*(true|false)\s*(#.*)?$/.exec(line)?.[1];
|
|
309
|
+
if (enabled)
|
|
310
|
+
entry.enabled = enabled === "true";
|
|
311
|
+
}
|
|
312
|
+
flush();
|
|
313
|
+
return disabled;
|
|
314
|
+
}
|
|
315
|
+
const unquoteName = (value) => value.trim().replace(/^(["'])(.*)\1$/, "$2");
|
|
316
|
+
export function readHermesDisabled(text) {
|
|
317
|
+
const lines = text.split("\n");
|
|
318
|
+
const skills = lines.findIndex((line) => /^skills:\s*(#.*)?$/.test(line));
|
|
319
|
+
if (skills < 0)
|
|
320
|
+
return lines.some((line) => /^skills\s*:/.test(line))
|
|
321
|
+
? null
|
|
322
|
+
: { names: [], lines, skills: -1, disabled: null, indent: " " };
|
|
323
|
+
let end = skills + 1;
|
|
324
|
+
while (end < lines.length && !/^[^\s#]/.test(lines[end]))
|
|
325
|
+
end += 1;
|
|
326
|
+
const child = lines
|
|
327
|
+
.slice(skills + 1, end)
|
|
328
|
+
.find((line) => /^\s+[^\s#]/.test(line));
|
|
329
|
+
const indent = child?.match(/^\s+/)?.[0] ?? " ";
|
|
330
|
+
const key = lines.findIndex((line, index) => index > skills &&
|
|
331
|
+
index < end &&
|
|
332
|
+
line.startsWith(`${indent}disabled:`) &&
|
|
333
|
+
!line.startsWith(`${indent} `));
|
|
334
|
+
if (key < 0)
|
|
335
|
+
return { names: [], lines, skills, disabled: null, indent };
|
|
336
|
+
const value = lines[key]
|
|
337
|
+
.slice(`${indent}disabled:`.length)
|
|
338
|
+
.replace(/\s+#.*$/, "")
|
|
339
|
+
.trim();
|
|
340
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
341
|
+
const inner = value.slice(1, -1).trim();
|
|
342
|
+
const names = inner ? inner.split(",").map(unquoteName) : [];
|
|
343
|
+
return names.every((name) => /^[\w.:-]+$/.test(name))
|
|
344
|
+
? { names, lines, skills, disabled: [key, key + 1], indent }
|
|
345
|
+
: null;
|
|
346
|
+
}
|
|
347
|
+
if (value)
|
|
348
|
+
return null;
|
|
349
|
+
const names = [];
|
|
350
|
+
let next = key + 1;
|
|
351
|
+
for (; next < end; next += 1) {
|
|
352
|
+
const line = lines[next];
|
|
353
|
+
if (!line.trim() || /^\s*#/.test(line))
|
|
354
|
+
continue;
|
|
355
|
+
const item = /^(\s*)-\s+(.+?)\s*$/.exec(line);
|
|
356
|
+
if (!item || item[1].length < indent.length)
|
|
357
|
+
break;
|
|
358
|
+
const name = unquoteName(item[2]);
|
|
359
|
+
if (!/^[\w.:-]+$/.test(name))
|
|
360
|
+
return null;
|
|
361
|
+
names.push(name);
|
|
362
|
+
}
|
|
363
|
+
// Trailing blank lines stay outside the range.
|
|
364
|
+
while (next > key + 1 && !lines[next - 1].trim())
|
|
365
|
+
next -= 1;
|
|
366
|
+
return { names, lines, skills, disabled: [key, next], indent };
|
|
367
|
+
}
|
|
368
|
+
export async function hermesDisabledSkills(home = os.homedir()) {
|
|
369
|
+
try {
|
|
370
|
+
const text = await readFile(path.join(home, ".hermes", "config.yaml"), "utf8");
|
|
371
|
+
return new Set(readHermesDisabled(text)?.names ?? []);
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
return new Set();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// The skill names `~/.gemini/settings.json` lists under `skills.disabled`.
|
|
378
|
+
export async function geminiDisabledSkills(home = os.homedir()) {
|
|
379
|
+
try {
|
|
380
|
+
const parsed = JSON.parse(await readFile(path.join(home, ".gemini", "settings.json"), "utf8"));
|
|
381
|
+
const list = parsed?.skills?.disabled;
|
|
382
|
+
return new Set(Array.isArray(list)
|
|
383
|
+
? list.filter((name) => typeof name === "string")
|
|
384
|
+
: []);
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
return new Set();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// One always-on listing: the frontmatter of every model-visible `*.md` (or `<dir>/SKILL.md`
|
|
391
|
+
// when `skillDirs`) directly under `dir`. Hidden entries cost nothing.
|
|
392
|
+
async function listingBytes(dir, options) {
|
|
393
|
+
let entries;
|
|
394
|
+
try {
|
|
395
|
+
entries = (await readdir(dir)).sort();
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
return 0;
|
|
399
|
+
}
|
|
400
|
+
let total = 0;
|
|
401
|
+
for (const entry of entries) {
|
|
402
|
+
if (entry.startsWith("."))
|
|
403
|
+
continue;
|
|
404
|
+
const file = options.skillDirs
|
|
405
|
+
? path.join(dir, entry, "SKILL.md")
|
|
406
|
+
: entry.endsWith(".md")
|
|
407
|
+
? path.join(dir, entry)
|
|
408
|
+
: null;
|
|
409
|
+
if (!file)
|
|
410
|
+
continue;
|
|
411
|
+
let buffer;
|
|
412
|
+
try {
|
|
413
|
+
buffer = await readFile(file);
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const bytes = frontmatterBytes(buffer);
|
|
419
|
+
const fields = frontmatterFields(buffer, bytes);
|
|
420
|
+
const name = fields.name ?? entry.replace(/\.md$/, "");
|
|
421
|
+
if (fields.modelHidden || options.hidden(name))
|
|
422
|
+
continue;
|
|
423
|
+
total += bytes;
|
|
424
|
+
}
|
|
425
|
+
return total;
|
|
426
|
+
}
|
|
427
|
+
// Claude also lists custom commands, subagents and each enabled plugin's skills, commands
|
|
428
|
+
// and agents on every request; each is reported as one line.
|
|
429
|
+
async function measureClaudeExtras(home, settings, notes) {
|
|
430
|
+
const claude = path.join(home, ".claude");
|
|
431
|
+
const extras = [];
|
|
432
|
+
const add = (dir, bytes) => {
|
|
433
|
+
if (bytes)
|
|
434
|
+
extras.push({ path: displayPath(dir, home), bytes });
|
|
435
|
+
};
|
|
436
|
+
const userHidden = (name) => settings.hidden.has(name);
|
|
437
|
+
for (const kind of ["commands", "agents"])
|
|
438
|
+
add(path.join(claude, kind), await listingBytes(path.join(claude, kind), {
|
|
439
|
+
skillDirs: false,
|
|
440
|
+
hidden: userHidden,
|
|
441
|
+
}));
|
|
442
|
+
if (!settings.enabledPlugins.length)
|
|
443
|
+
return extras;
|
|
444
|
+
let installed = {};
|
|
445
|
+
try {
|
|
446
|
+
installed =
|
|
447
|
+
JSON.parse(await readFile(path.join(claude, "plugins", "installed_plugins.json"), "utf8")).plugins ?? {};
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
notes.push("installed_plugins.json unreadable; plugins not measured");
|
|
451
|
+
return extras;
|
|
452
|
+
}
|
|
453
|
+
for (const id of settings.enabledPlugins) {
|
|
454
|
+
const root = installed[id]?.[0]?.installPath;
|
|
455
|
+
if (typeof root !== "string")
|
|
456
|
+
continue;
|
|
457
|
+
const plugin = id.split("@")[0];
|
|
458
|
+
const hidden = (name) => settings.hidden.has(`${plugin}:${name}`) || settings.hidden.has(name);
|
|
459
|
+
let bytes = await listingBytes(path.join(root, "skills"), {
|
|
460
|
+
skillDirs: true,
|
|
461
|
+
hidden,
|
|
462
|
+
});
|
|
463
|
+
for (const kind of ["commands", "agents"])
|
|
464
|
+
bytes += await listingBytes(path.join(root, kind), {
|
|
465
|
+
skillDirs: false,
|
|
466
|
+
hidden,
|
|
467
|
+
});
|
|
468
|
+
add(root, bytes);
|
|
469
|
+
}
|
|
470
|
+
return extras;
|
|
471
|
+
}
|
|
472
|
+
async function measureSkills(sources, home, notes, hidden = () => false) {
|
|
473
|
+
const root = sources.skillsRoot(home);
|
|
474
|
+
const result = {
|
|
475
|
+
root: displayPath(root, home),
|
|
476
|
+
present: false,
|
|
477
|
+
count: 0,
|
|
478
|
+
frontmatterBytes: 0,
|
|
479
|
+
bodyBytes: 0,
|
|
480
|
+
hidden: 0,
|
|
481
|
+
hiddenFrontmatterBytes: 0,
|
|
482
|
+
};
|
|
483
|
+
let entries;
|
|
484
|
+
try {
|
|
485
|
+
if (!(await isDirectory(root)))
|
|
486
|
+
return result;
|
|
487
|
+
entries = (await readdir(root)).sort();
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
result.present = true;
|
|
493
|
+
let broken = 0;
|
|
494
|
+
const count = async (file) => {
|
|
495
|
+
try {
|
|
496
|
+
const buffer = await readFile(file);
|
|
497
|
+
const frontmatter = frontmatterBytes(buffer);
|
|
498
|
+
const fields = frontmatterFields(buffer, frontmatter);
|
|
499
|
+
const name = fields.name ?? path.basename(path.dirname(file));
|
|
500
|
+
if (fields.modelHidden || hidden(name, file)) {
|
|
501
|
+
result.hidden += 1;
|
|
502
|
+
result.hiddenFrontmatterBytes += frontmatter;
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
result.count += 1;
|
|
506
|
+
result.frontmatterBytes += frontmatter;
|
|
507
|
+
result.bodyBytes += buffer.byteLength - frontmatter;
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
broken += 1;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
for (const name of entries) {
|
|
514
|
+
if (name.startsWith(".") && !sources.nested)
|
|
515
|
+
continue;
|
|
516
|
+
const dir = path.join(root, name);
|
|
517
|
+
const directory = await isDirectory(dir);
|
|
518
|
+
if (directory === null) {
|
|
519
|
+
broken += 1;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (!directory)
|
|
523
|
+
continue;
|
|
524
|
+
const skillFile = path.join(dir, "SKILL.md");
|
|
525
|
+
if ((await sizeOf(skillFile)) !== null) {
|
|
526
|
+
await count(skillFile);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (!sources.nested)
|
|
530
|
+
continue;
|
|
531
|
+
let children;
|
|
532
|
+
try {
|
|
533
|
+
children = (await readdir(dir)).sort();
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
broken += 1;
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
for (const child of children) {
|
|
540
|
+
const nestedFile = path.join(dir, child, "SKILL.md");
|
|
541
|
+
if ((await sizeOf(nestedFile)) !== null)
|
|
542
|
+
await count(nestedFile);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (broken)
|
|
546
|
+
notes.push(`${broken} unreadable or broken skill entries skipped`);
|
|
547
|
+
return result;
|
|
548
|
+
}
|
|
549
|
+
export async function measureContextCost(options = {}) {
|
|
550
|
+
const home = options.home ?? os.homedir();
|
|
551
|
+
const projectDir = options.projectDir ?? null;
|
|
552
|
+
const budget = options.budget ?? DEFAULT_CONTEXT_BUDGET;
|
|
553
|
+
const clients = [];
|
|
554
|
+
for (const client of Object.keys(clientSources)) {
|
|
555
|
+
const sources = clientSources[client];
|
|
556
|
+
const notes = [...sources.notes];
|
|
557
|
+
const rules = await measureRules(sources, home);
|
|
558
|
+
const memory = await measureMemory(client, home, projectDir, notes);
|
|
559
|
+
const claude = client === "claude" ? await readClaudeSettings(home, notes) : null;
|
|
560
|
+
const codexOff = client === "codex" ? await codexDisabledSkillPaths(home) : null;
|
|
561
|
+
const geminiOff = client === "gemini" ? await geminiDisabledSkills(home) : null;
|
|
562
|
+
const hermesOff = client === "hermes" ? await hermesDisabledSkills(home) : null;
|
|
563
|
+
const skills = await measureSkills(sources, home, notes, (name, file) => Boolean(claude?.hidden.has(name) ||
|
|
564
|
+
codexOff?.has(path.resolve(file)) ||
|
|
565
|
+
geminiOff?.has(name) ||
|
|
566
|
+
hermesOff?.has(name)));
|
|
567
|
+
const extras = claude ? await measureClaudeExtras(home, claude, notes) : [];
|
|
568
|
+
const alwaysOnBytes = [...rules, ...memory, ...extras].reduce((total, file) => total + file.bytes, 0) + skills.frontmatterBytes;
|
|
569
|
+
clients.push({
|
|
570
|
+
client,
|
|
571
|
+
shared: sources.shared ?? false,
|
|
572
|
+
rules,
|
|
573
|
+
memory,
|
|
574
|
+
skills,
|
|
575
|
+
extras,
|
|
576
|
+
alwaysOnBytes,
|
|
577
|
+
overBudget: alwaysOnBytes > budget,
|
|
578
|
+
notes,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
budget,
|
|
583
|
+
project: projectDir ? displayPath(projectDir, home) : null,
|
|
584
|
+
clients,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
//# sourceMappingURL=context-cost.js.map
|