@monoes/monomindcli 2.14.0 → 2.15.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/.claude/helpers/control-start.cjs +10 -13
- package/.claude/skills/monodesign/scripts/context.mjs +5 -5
- package/.claude/skills/monodesign/scripts/critique-storage.mjs +1 -1
- package/.claude/skills/monodesign/scripts/detect-csp.mjs +1 -6
- package/.claude/skills/monodesign/scripts/detector/browser/injected/index.mjs +16 -18
- package/.claude/skills/monodesign/scripts/detector/cli/main.mjs +3 -3
- package/.claude/skills/monodesign/scripts/detector/detect-antipatterns-browser.js +69 -49
- package/.claude/skills/monodesign/scripts/detector/engines/regex/detect-text.mjs +6 -6
- package/.claude/skills/monodesign/scripts/detector/engines/static-html/css-cascade.mjs +6 -5
- package/.claude/skills/monodesign/scripts/detector/engines/static-html/detect-html.mjs +6 -6
- package/.claude/skills/monodesign/scripts/detector/fix/index.mjs +3 -3
- package/.claude/skills/monodesign/scripts/detector/node/file-system.mjs +1 -1
- package/.claude/skills/monodesign/scripts/detector/registry/antipatterns.mjs +1 -1
- package/.claude/skills/monodesign/scripts/detector/rules/checks.mjs +26 -27
- package/.claude/skills/monodesign/scripts/detector/shared/color.mjs +2 -2
- package/.claude/skills/monodesign/scripts/detector/shared/constants.mjs +1 -1
- package/.claude/skills/monodesign/scripts/detector/shared/inline-ignores.mjs +2 -2
- package/.claude/skills/monodesign/scripts/hook-admin.mjs +6 -6
- package/.claude/skills/monodesign/scripts/hook-before-edit.mjs +4 -4
- package/.claude/skills/monodesign/scripts/hook-lib.mjs +20 -11
- package/.claude/skills/monodesign/scripts/hook.mjs +1 -1
- package/.claude/skills/monodesign/scripts/lib/design-parser.mjs +9 -42
- package/.claude/skills/monodesign/scripts/lib/is-generated.mjs +1 -1
- package/.claude/skills/monodesign/scripts/lib/monodesign-config.mjs +5 -5
- package/.claude/skills/monodesign/scripts/lib/monodesign-paths.mjs +1 -1
- package/.claude/skills/monodesign/scripts/live/event-validation.mjs +13 -13
- package/.claude/skills/monodesign/scripts/live/manual-apply.mjs +4 -4
- package/.claude/skills/monodesign/scripts/live/manual-edits-buffer.mjs +1 -1
- package/.claude/skills/monodesign/scripts/live/session-store.mjs +5 -5
- package/.claude/skills/monodesign/scripts/live/svelte-component.mjs +13 -13
- package/.claude/skills/monodesign/scripts/live/sveltekit-adapter.mjs +4 -4
- package/.claude/skills/monodesign/scripts/live/ui-core.mjs +1 -1
- package/.claude/skills/monodesign/scripts/live-accept.mjs +24 -24
- package/.claude/skills/monodesign/scripts/live-browser-dom.js +8 -7
- package/.claude/skills/monodesign/scripts/live-browser-session.js +8 -7
- package/.claude/skills/monodesign/scripts/live-browser.js +406 -433
- package/.claude/skills/monodesign/scripts/live-commit-manual-edits.mjs +6 -6
- package/.claude/skills/monodesign/scripts/live-copy-edit-agent.mjs +2 -2
- package/.claude/skills/monodesign/scripts/live-discard-manual-edits.mjs +1 -1
- package/.claude/skills/monodesign/scripts/live-inject.mjs +7 -7
- package/.claude/skills/monodesign/scripts/live-insert.mjs +11 -11
- package/.claude/skills/monodesign/scripts/live-manual-edit-evidence.mjs +4 -4
- package/.claude/skills/monodesign/scripts/live-poll.mjs +5 -5
- package/.claude/skills/monodesign/scripts/live-server.mjs +10 -10
- package/.claude/skills/monodesign/scripts/live-wrap.mjs +31 -41
- package/.claude/skills/monodesign/scripts/live.mjs +5 -5
- package/.claude/skills/monodesign/scripts/palette.mjs +3 -1
- package/.claude/skills/monodesign/scripts/pin.mjs +1 -1
- package/bin/cli.js +21 -1
- package/bin/mcp-server.js +21 -1
- package/dist/src/capabilities/cap-documents.d.ts.map +1 -1
- package/dist/src/capabilities/cap-documents.js +23 -0
- package/dist/src/capabilities/cap-documents.js.map +1 -1
- package/dist/src/commands/doc-filters.d.ts +19 -0
- package/dist/src/commands/doc-filters.d.ts.map +1 -0
- package/dist/src/commands/doc-filters.js +64 -0
- package/dist/src/commands/doc-filters.js.map +1 -0
- package/dist/src/commands/doc-library.d.ts +22 -0
- package/dist/src/commands/doc-library.d.ts.map +1 -0
- package/dist/src/commands/doc-library.js +454 -0
- package/dist/src/commands/doc-library.js.map +1 -0
- package/dist/src/commands/doc-list.d.ts +9 -0
- package/dist/src/commands/doc-list.d.ts.map +1 -0
- package/dist/src/commands/doc-list.js +99 -0
- package/dist/src/commands/doc-list.js.map +1 -0
- package/dist/src/commands/doc.d.ts.map +1 -1
- package/dist/src/commands/doc.js +51 -37
- package/dist/src/commands/doc.js.map +1 -1
- package/dist/src/commands/ui.d.ts.map +1 -1
- package/dist/src/commands/ui.js +60 -0
- package/dist/src/commands/ui.js.map +1 -1
- package/dist/src/knowledge/capture-envelope.d.ts +85 -0
- package/dist/src/knowledge/capture-envelope.d.ts.map +1 -0
- package/dist/src/knowledge/capture-envelope.js +167 -0
- package/dist/src/knowledge/capture-envelope.js.map +1 -0
- package/dist/src/knowledge/capture-text.d.ts +23 -0
- package/dist/src/knowledge/capture-text.d.ts.map +1 -0
- package/dist/src/knowledge/capture-text.js +49 -0
- package/dist/src/knowledge/capture-text.js.map +1 -0
- package/dist/src/knowledge/citation.d.ts +121 -0
- package/dist/src/knowledge/citation.d.ts.map +1 -0
- package/dist/src/knowledge/citation.js +252 -0
- package/dist/src/knowledge/citation.js.map +1 -0
- package/dist/src/knowledge/document-chunking.d.ts +42 -0
- package/dist/src/knowledge/document-chunking.d.ts.map +1 -0
- package/dist/src/knowledge/document-chunking.js +288 -0
- package/dist/src/knowledge/document-chunking.js.map +1 -0
- package/dist/src/knowledge/document-index.d.ts +124 -0
- package/dist/src/knowledge/document-index.d.ts.map +1 -0
- package/dist/src/knowledge/document-index.js +314 -0
- package/dist/src/knowledge/document-index.js.map +1 -0
- package/dist/src/knowledge/document-ingest.d.ts +23 -0
- package/dist/src/knowledge/document-ingest.d.ts.map +1 -0
- package/dist/src/knowledge/document-ingest.js +368 -0
- package/dist/src/knowledge/document-ingest.js.map +1 -0
- package/dist/src/knowledge/document-pipeline.d.ts +26 -148
- package/dist/src/knowledge/document-pipeline.d.ts.map +1 -1
- package/dist/src/knowledge/document-pipeline.js +25 -920
- package/dist/src/knowledge/document-pipeline.js.map +1 -1
- package/dist/src/knowledge/document-search.d.ts +27 -0
- package/dist/src/knowledge/document-search.d.ts.map +1 -0
- package/dist/src/knowledge/document-search.js +138 -0
- package/dist/src/knowledge/document-search.js.map +1 -0
- package/dist/src/knowledge/document-store.d.ts +28 -0
- package/dist/src/knowledge/document-store.d.ts.map +1 -0
- package/dist/src/knowledge/document-store.js +61 -0
- package/dist/src/knowledge/document-store.js.map +1 -0
- package/dist/src/knowledge/document-types.d.ts +103 -0
- package/dist/src/knowledge/document-types.d.ts.map +1 -0
- package/dist/src/knowledge/document-types.js +12 -0
- package/dist/src/knowledge/document-types.js.map +1 -0
- package/dist/src/knowledge/highlights.d.ts +168 -0
- package/dist/src/knowledge/highlights.d.ts.map +1 -0
- package/dist/src/knowledge/highlights.js +311 -0
- package/dist/src/knowledge/highlights.js.map +1 -0
- package/dist/src/knowledge/html-extract.d.ts +43 -0
- package/dist/src/knowledge/html-extract.d.ts.map +1 -0
- package/dist/src/knowledge/html-extract.js +286 -0
- package/dist/src/knowledge/html-extract.js.map +1 -0
- package/dist/src/knowledge/html-tags.d.ts +30 -0
- package/dist/src/knowledge/html-tags.d.ts.map +1 -0
- package/dist/src/knowledge/html-tags.js +229 -0
- package/dist/src/knowledge/html-tags.js.map +1 -0
- package/dist/src/knowledge/library.d.ts +106 -0
- package/dist/src/knowledge/library.d.ts.map +1 -0
- package/dist/src/knowledge/library.js +195 -0
- package/dist/src/knowledge/library.js.map +1 -0
- package/dist/src/knowledge/lookup.d.ts +80 -0
- package/dist/src/knowledge/lookup.d.ts.map +1 -0
- package/dist/src/knowledge/lookup.js +156 -0
- package/dist/src/knowledge/lookup.js.map +1 -0
- package/dist/src/knowledge/mhtml.d.ts +60 -0
- package/dist/src/knowledge/mhtml.d.ts.map +1 -0
- package/dist/src/knowledge/mhtml.js +154 -0
- package/dist/src/knowledge/mhtml.js.map +1 -0
- package/dist/src/knowledge/okf-bundle.d.ts +19 -0
- package/dist/src/knowledge/okf-bundle.d.ts.map +1 -0
- package/dist/src/knowledge/okf-bundle.js +106 -0
- package/dist/src/knowledge/okf-bundle.js.map +1 -0
- package/dist/src/knowledge/profile-store.d.ts +134 -0
- package/dist/src/knowledge/profile-store.d.ts.map +1 -0
- package/dist/src/knowledge/profile-store.js +237 -0
- package/dist/src/knowledge/profile-store.js.map +1 -0
- package/dist/src/knowledge/related.d.ts +58 -0
- package/dist/src/knowledge/related.d.ts.map +1 -0
- package/dist/src/knowledge/related.js +222 -0
- package/dist/src/knowledge/related.js.map +1 -0
- package/dist/src/knowledge/section-diff.d.ts +48 -0
- package/dist/src/knowledge/section-diff.d.ts.map +1 -0
- package/dist/src/knowledge/section-diff.js +151 -0
- package/dist/src/knowledge/section-diff.js.map +1 -0
- package/dist/src/knowledge/watch.d.ts +92 -0
- package/dist/src/knowledge/watch.d.ts.map +1 -0
- package/dist/src/knowledge/watch.js +221 -0
- package/dist/src/knowledge/watch.js.map +1 -0
- package/dist/src/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp-server.js +44 -87
- package/dist/src/mcp-server.js.map +1 -1
- package/dist/src/mcp-tools/browser-instrument-tools.d.ts +35 -0
- package/dist/src/mcp-tools/browser-instrument-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/browser-instrument-tools.js +359 -0
- package/dist/src/mcp-tools/browser-instrument-tools.js.map +1 -0
- package/dist/src/mcp-tools/browser-metrics.d.ts +42 -0
- package/dist/src/mcp-tools/browser-metrics.d.ts.map +1 -0
- package/dist/src/mcp-tools/browser-metrics.js +91 -0
- package/dist/src/mcp-tools/browser-metrics.js.map +1 -0
- package/dist/src/mcp-tools/browser-profile-tools.d.ts +16 -0
- package/dist/src/mcp-tools/browser-profile-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/browser-profile-tools.js +324 -0
- package/dist/src/mcp-tools/browser-profile-tools.js.map +1 -0
- package/dist/src/mcp-tools/browser-session.d.ts +68 -0
- package/dist/src/mcp-tools/browser-session.d.ts.map +1 -0
- package/dist/src/mcp-tools/browser-session.js +224 -0
- package/dist/src/mcp-tools/browser-session.js.map +1 -0
- package/dist/src/mcp-tools/browser-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/browser-tools.js +10 -176
- package/dist/src/mcp-tools/browser-tools.js.map +1 -1
- package/dist/src/mcp-tools/capture-resource-read.d.ts +115 -0
- package/dist/src/mcp-tools/capture-resource-read.d.ts.map +1 -0
- package/dist/src/mcp-tools/capture-resource-read.js +296 -0
- package/dist/src/mcp-tools/capture-resource-read.js.map +1 -0
- package/dist/src/mcp-tools/capture-resource-tools.d.ts +22 -0
- package/dist/src/mcp-tools/capture-resource-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/capture-resource-tools.js +182 -0
- package/dist/src/mcp-tools/capture-resource-tools.js.map +1 -0
- package/dist/src/mcp-tools/capture-resources.d.ts +142 -0
- package/dist/src/mcp-tools/capture-resources.d.ts.map +1 -0
- package/dist/src/mcp-tools/capture-resources.js +289 -0
- package/dist/src/mcp-tools/capture-resources.js.map +1 -0
- package/dist/src/mcp-tools/index.d.ts +3 -0
- package/dist/src/mcp-tools/index.d.ts.map +1 -1
- package/dist/src/mcp-tools/index.js +6 -0
- package/dist/src/mcp-tools/index.js.map +1 -1
- package/dist/src/mcp-tools/knowledge-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/knowledge-tools.js +9 -1
- package/dist/src/mcp-tools/knowledge-tools.js.map +1 -1
- package/dist/src/mcp-tools/resource-router.d.ts +86 -0
- package/dist/src/mcp-tools/resource-router.d.ts.map +1 -0
- package/dist/src/mcp-tools/resource-router.js +181 -0
- package/dist/src/mcp-tools/resource-router.js.map +1 -0
- package/dist/src/memory/memory-bridge.js +1 -1
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/orgrt/agent-runner.d.ts +4 -0
- package/dist/src/orgrt/agent-runner.d.ts.map +1 -1
- package/dist/src/orgrt/agent-runner.js +22 -0
- package/dist/src/orgrt/agent-runner.js.map +1 -1
- package/dist/src/orgrt/antigravity-runner.d.ts.map +1 -1
- package/dist/src/orgrt/antigravity-runner.js +2 -1
- package/dist/src/orgrt/antigravity-runner.js.map +1 -1
- package/dist/src/orgrt/authority-mask.d.ts +32 -0
- package/dist/src/orgrt/authority-mask.d.ts.map +1 -0
- package/dist/src/orgrt/authority-mask.js +135 -0
- package/dist/src/orgrt/authority-mask.js.map +1 -0
- package/dist/src/orgrt/codex-runner.d.ts.map +1 -1
- package/dist/src/orgrt/codex-runner.js +2 -1
- package/dist/src/orgrt/codex-runner.js.map +1 -1
- package/dist/src/orgrt/copilot-runner.d.ts.map +1 -1
- package/dist/src/orgrt/copilot-runner.js +2 -1
- package/dist/src/orgrt/copilot-runner.js.map +1 -1
- package/dist/src/orgrt/crush-runner.d.ts.map +1 -1
- package/dist/src/orgrt/crush-runner.js +6 -1
- package/dist/src/orgrt/crush-runner.js.map +1 -1
- package/dist/src/orgrt/daemon.d.ts +4 -0
- package/dist/src/orgrt/daemon.d.ts.map +1 -1
- package/dist/src/orgrt/daemon.js +13 -2
- package/dist/src/orgrt/daemon.js.map +1 -1
- package/dist/src/orgrt/decisions.d.ts +9 -0
- package/dist/src/orgrt/decisions.d.ts.map +1 -1
- package/dist/src/orgrt/decisions.js +32 -5
- package/dist/src/orgrt/decisions.js.map +1 -1
- package/dist/src/orgrt/file-roots.d.ts +2 -1
- package/dist/src/orgrt/file-roots.d.ts.map +1 -1
- package/dist/src/orgrt/file-roots.js +7 -3
- package/dist/src/orgrt/file-roots.js.map +1 -1
- package/dist/src/orgrt/grok-runner.d.ts.map +1 -1
- package/dist/src/orgrt/grok-runner.js +2 -1
- package/dist/src/orgrt/grok-runner.js.map +1 -1
- package/dist/src/orgrt/hermes-runner.d.ts.map +1 -1
- package/dist/src/orgrt/hermes-runner.js +2 -1
- package/dist/src/orgrt/hermes-runner.js.map +1 -1
- package/dist/src/orgrt/inbox.d.ts.map +1 -1
- package/dist/src/orgrt/inbox.js +70 -4
- package/dist/src/orgrt/inbox.js.map +1 -1
- package/dist/src/orgrt/kimicode-runner.d.ts.map +1 -1
- package/dist/src/orgrt/kimicode-runner.js +2 -1
- package/dist/src/orgrt/kimicode-runner.js.map +1 -1
- package/dist/src/orgrt/opencode-runner.d.ts.map +1 -1
- package/dist/src/orgrt/opencode-runner.js +3 -2
- package/dist/src/orgrt/opencode-runner.js.map +1 -1
- package/dist/src/orgrt/pi-rpc-runner.d.ts.map +1 -1
- package/dist/src/orgrt/pi-rpc-runner.js +3 -2
- package/dist/src/orgrt/pi-rpc-runner.js.map +1 -1
- package/dist/src/orgrt/pi-runner.d.ts.map +1 -1
- package/dist/src/orgrt/pi-runner.js +2 -1
- package/dist/src/orgrt/pi-runner.js.map +1 -1
- package/dist/src/orgrt/policy.d.ts.map +1 -1
- package/dist/src/orgrt/policy.js +3 -0
- package/dist/src/orgrt/policy.js.map +1 -1
- package/dist/src/orgrt/qwen-rpc-runner.d.ts.map +1 -1
- package/dist/src/orgrt/qwen-rpc-runner.js +3 -2
- package/dist/src/orgrt/qwen-rpc-runner.js.map +1 -1
- package/dist/src/orgrt/qwen-runner.d.ts.map +1 -1
- package/dist/src/orgrt/qwen-runner.js +2 -1
- package/dist/src/orgrt/qwen-runner.js.map +1 -1
- package/dist/src/orgrt/role-sandbox.d.ts +21 -0
- package/dist/src/orgrt/role-sandbox.d.ts.map +1 -1
- package/dist/src/orgrt/role-sandbox.js +31 -6
- package/dist/src/orgrt/role-sandbox.js.map +1 -1
- package/dist/src/orgrt/session-ledger.d.ts +7 -0
- package/dist/src/orgrt/session-ledger.d.ts.map +1 -1
- package/dist/src/orgrt/session-ledger.js +15 -0
- package/dist/src/orgrt/session-ledger.js.map +1 -1
- package/dist/src/orgrt/session.d.ts.map +1 -1
- package/dist/src/orgrt/session.js +42 -10
- package/dist/src/orgrt/session.js.map +1 -1
- package/dist/src/orgrt/task-dag.d.ts +3 -0
- package/dist/src/orgrt/task-dag.d.ts.map +1 -1
- package/dist/src/orgrt/task-dag.js +1 -0
- package/dist/src/orgrt/task-dag.js.map +1 -1
- package/dist/src/orgrt/types.d.ts +5 -0
- package/dist/src/orgrt/types.d.ts.map +1 -1
- package/dist/src/orgrt/types.js +23 -1
- package/dist/src/orgrt/types.js.map +1 -1
- package/dist/src/ui/dashboard.html +28 -848
- package/dist/src/ui/human-auth.mjs +103 -0
- package/dist/src/ui/org-hil.mjs +86 -37
- package/dist/src/ui/org-runtime.mjs +21 -13
- package/dist/src/ui/routes-org.mjs +49 -445
- package/dist/src/ui/server.mjs +90 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-profile capture stores (the browser track's profile feature).
|
|
3
|
+
*
|
|
4
|
+
* mono-agent has profiles — work, personal, a client — and a page captured
|
|
5
|
+
* from the browser can name the one it belongs to (`meta.profile` on the
|
|
6
|
+
* capture envelope). The promise that makes that worth having is negative:
|
|
7
|
+
* a page saved into `work` must NOT come back from a search of `personal`.
|
|
8
|
+
*
|
|
9
|
+
* WHICH MECHANISM, AND WHY. A store here is three things — a vector db
|
|
10
|
+
* directory (`dbPath`), a namespace, and the root the `doc-metadata.jsonl`
|
|
11
|
+
* lives under — and `scope` already decides all three (see
|
|
12
|
+
* document-pipeline: `namespace()`, `effectiveRoot()`, `storeDbPath()`).
|
|
13
|
+
* `global` is the existing proof: one scope value routes to a directory
|
|
14
|
+
* outside any project. A profile is the same shape, so it is expressed the
|
|
15
|
+
* same way — a scope of `profile:<id>` — rather than as a second, parallel
|
|
16
|
+
* "which brain" parameter threaded through ingest, search, cite and
|
|
17
|
+
* related. Nothing here is a new concept; it is one more value of an
|
|
18
|
+
* existing one.
|
|
19
|
+
*
|
|
20
|
+
* WHERE THE DIRECTORY GOES. Inside the global brain
|
|
21
|
+
* (`~/.monomind/global-brain/profiles/<id>`), not beside it. That is not
|
|
22
|
+
* cosmetic: `memory-bridge.getDbPath` refuses any dbPath outside the
|
|
23
|
+
* project, the per-project data dir, or the global brain, and — this is the
|
|
24
|
+
* dangerous part — a refused path does not throw, it silently falls back to
|
|
25
|
+
* the DEFAULT store. A profile brain anywhere else would quietly put every
|
|
26
|
+
* profile's captures in one database, which is the exact failure this
|
|
27
|
+
* feature exists to prevent. Being inside the global brain also inherits
|
|
28
|
+
* its protection from `cleanup --data`, which prunes `~/.monomind/projects`
|
|
29
|
+
* and deliberately never touches its sibling.
|
|
30
|
+
*
|
|
31
|
+
* UNTRUSTED INPUT. The profile id arrives from a browser extension and
|
|
32
|
+
* becomes a directory name. `isValidProfileId` is the guard, kept
|
|
33
|
+
* deliberately identical to Go's `profiledir.ValidProfileID` — the two
|
|
34
|
+
* sides of this contract must reject exactly the same strings.
|
|
35
|
+
*
|
|
36
|
+
* @module v1/cli/knowledge/profile-store
|
|
37
|
+
*/
|
|
38
|
+
import * as fs from 'node:fs';
|
|
39
|
+
import * as os from 'node:os';
|
|
40
|
+
import * as path from 'node:path';
|
|
41
|
+
/** Scopes of the form `profile:<id>` address one profile's own store. */
|
|
42
|
+
export const PROFILE_SCOPE_PREFIX = 'profile:';
|
|
43
|
+
/** Where a profile's brain lives inside the global brain. */
|
|
44
|
+
const PROFILE_BRAIN_DIR = 'profiles';
|
|
45
|
+
/** Resolved lazily so a test's env override works whatever the import order. */
|
|
46
|
+
const globalBrainRoot = () => process.env.MONOMIND_GLOBAL_BRAIN_DIR || path.join(os.homedir(), '.monomind', 'global-brain');
|
|
47
|
+
/**
|
|
48
|
+
* The root mono-agent keeps profile directories under. Mirrors Go's
|
|
49
|
+
* `profiledir` (`~/.monoagent/profiles/<id>/`); the env override exists for
|
|
50
|
+
* tests on this side, since the Go side's own knob is `$HOME`.
|
|
51
|
+
*/
|
|
52
|
+
const profilesRoot = () => process.env.MONOAGENT_PROFILES_DIR || path.join(os.homedir(), '.monoagent', 'profiles');
|
|
53
|
+
/**
|
|
54
|
+
* isValidProfileId mirrors `profiledir.ValidProfileID` (Go): non-empty, no
|
|
55
|
+
* path separator, no parent-directory component. Every other function here
|
|
56
|
+
* returns undefined for an id this rejects, so a hostile id can never
|
|
57
|
+
* become a path — not even a path that is then refused.
|
|
58
|
+
*/
|
|
59
|
+
export function isValidProfileId(id) {
|
|
60
|
+
if (typeof id !== 'string')
|
|
61
|
+
return false;
|
|
62
|
+
const value = id.trim();
|
|
63
|
+
if (!value)
|
|
64
|
+
return false;
|
|
65
|
+
if (value.includes('/') || value.includes('\\'))
|
|
66
|
+
return false;
|
|
67
|
+
if (value.includes('..'))
|
|
68
|
+
return false;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
/** The scope value addressing one profile's store, or undefined. */
|
|
72
|
+
export function profileScope(id) {
|
|
73
|
+
return isValidProfileId(id) ? `${PROFILE_SCOPE_PREFIX}${id.trim()}` : undefined;
|
|
74
|
+
}
|
|
75
|
+
/** The profile id inside a `profile:<id>` scope, or undefined for any other. */
|
|
76
|
+
export function parseProfileScope(scope) {
|
|
77
|
+
if (typeof scope !== 'string' || !scope.startsWith(PROFILE_SCOPE_PREFIX))
|
|
78
|
+
return undefined;
|
|
79
|
+
const id = scope.slice(PROFILE_SCOPE_PREFIX.length);
|
|
80
|
+
return isValidProfileId(id) ? id.trim() : undefined;
|
|
81
|
+
}
|
|
82
|
+
/** True for a scope that names a profile. */
|
|
83
|
+
export const isProfileScope = (scope) => parseProfileScope(scope) !== undefined;
|
|
84
|
+
// Directories already materialized in this process. See profileBrainDir.
|
|
85
|
+
const created = new Set();
|
|
86
|
+
/**
|
|
87
|
+
* profileBrainDir is a profile's store directory — the value used BOTH as
|
|
88
|
+
* the dbPath and as the metadata root, exactly as the global brain uses one
|
|
89
|
+
* directory for both.
|
|
90
|
+
*
|
|
91
|
+
* It creates the directory, which a resolver would not normally do. The
|
|
92
|
+
* reason is specific: `memory-bridge.getDbPath` compares
|
|
93
|
+
* `fs.realpathSync(dbPath)` against `fs.realpathSync(globalBrainDir)`, and
|
|
94
|
+
* a path that does not exist yet cannot be realpath'd. On a machine where
|
|
95
|
+
* the home directory is reached through a symlink, the two sides would then
|
|
96
|
+
* resolve differently, the guard would reject the path, and — because that
|
|
97
|
+
* guard falls back instead of throwing — the profile's captures would land
|
|
98
|
+
* in the default store together with every other profile's. Creating the
|
|
99
|
+
* directory first makes both sides resolvable and the comparison honest.
|
|
100
|
+
*/
|
|
101
|
+
export function profileBrainDir(id) {
|
|
102
|
+
if (!isValidProfileId(id))
|
|
103
|
+
return undefined;
|
|
104
|
+
const dir = path.join(globalBrainRoot(), PROFILE_BRAIN_DIR, id.trim());
|
|
105
|
+
if (!created.has(dir)) {
|
|
106
|
+
try {
|
|
107
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
108
|
+
created.add(dir);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Best effort: an unwritable brain directory is the store layer's
|
|
112
|
+
// problem to report, not a reason to fail resolution here.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return dir;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* profileStoreDir is profileBrainDir for a `profile:<id>` SCOPE, and
|
|
119
|
+
* undefined for every other scope — the single function
|
|
120
|
+
* `document-store`'s `effectiveRoot`/`storeDbPath` consult, so that the
|
|
121
|
+
* routing rule lives in one place and the pipeline keeps a two-line hook.
|
|
122
|
+
*
|
|
123
|
+
* DO NOT MOVE THIS DIRECTORY OUT OF THE GLOBAL BRAIN. The nesting looks
|
|
124
|
+
* arbitrary and is not: `memory-bridge.getDbPath` accepts a dbPath only
|
|
125
|
+
* inside the project, the per-project data dir, or the global brain, and a
|
|
126
|
+
* path it does not accept is NOT rejected — it SILENTLY RETURNS THE DEFAULT
|
|
127
|
+
* STORE. So a profile brain sited anywhere else (beside the global brain,
|
|
128
|
+
* under `~/.monoagent`, under the profile's own root) does not fail: every
|
|
129
|
+
* profile's captures quietly land in one database, `work` turns up in a
|
|
130
|
+
* `personal` search, and nothing anywhere reports a problem. The feature
|
|
131
|
+
* fails by succeeding. `src/__tests__/profile-store-dbpath.test.ts` holds
|
|
132
|
+
* both halves of that — this path surviving, and an outside path silently
|
|
133
|
+
* becoming the default.
|
|
134
|
+
*/
|
|
135
|
+
export function profileStoreDir(scope) {
|
|
136
|
+
const id = parseProfileScope(scope);
|
|
137
|
+
return id === undefined ? undefined : profileBrainDir(id);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* profileInboxDir is where that profile's captures land, as the Go side
|
|
141
|
+
* writes them: `~/.monoagent/profiles/<id>/.monomind/inbox`.
|
|
142
|
+
*
|
|
143
|
+
* Keyed by profile id alone. mono-agent lets a profile's root be moved
|
|
144
|
+
* (`profiles.root_dir`), and the capture inbox deliberately does NOT follow
|
|
145
|
+
* it on either side — see `ProfileInbox` in `internal/capture/profile.go`
|
|
146
|
+
* for why. This path and that one are the same rule written twice, which is
|
|
147
|
+
* the cost of the two repos agreeing about a directory at all.
|
|
148
|
+
*/
|
|
149
|
+
export function profileInboxDir(id) {
|
|
150
|
+
if (!isValidProfileId(id))
|
|
151
|
+
return undefined;
|
|
152
|
+
return path.join(profilesRoot(), id.trim(), '.monomind', 'inbox');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* profileInboxes lists the profile inboxes that exist on this machine, for
|
|
156
|
+
* a sweep that wants to ingest every profile's captures rather than only
|
|
157
|
+
* the shared inbox. Missing directories are simply absent; nothing here
|
|
158
|
+
* throws.
|
|
159
|
+
*/
|
|
160
|
+
export function profileInboxes() {
|
|
161
|
+
const out = [];
|
|
162
|
+
let entries;
|
|
163
|
+
try {
|
|
164
|
+
entries = fs.readdirSync(profilesRoot(), { withFileTypes: true });
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
for (const entry of entries) {
|
|
170
|
+
if (!entry.isDirectory() || !isValidProfileId(entry.name))
|
|
171
|
+
continue;
|
|
172
|
+
const inbox = profileInboxDir(entry.name);
|
|
173
|
+
const scope = profileScope(entry.name);
|
|
174
|
+
if (!inbox || !scope)
|
|
175
|
+
continue;
|
|
176
|
+
try {
|
|
177
|
+
if (!fs.statSync(inbox).isDirectory())
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
out.push({ id: entry.name, inbox, scope });
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* captureScope is the routing rule, applied once per ingest: a capture
|
|
189
|
+
* whose envelope names a profile belongs to that profile's store, whatever
|
|
190
|
+
* scope the caller reached for.
|
|
191
|
+
*
|
|
192
|
+
* The envelope wins on purpose. The profile was chosen by a person at save
|
|
193
|
+
* time, in the browser, and every automatic caller (`doc ingest` on a path
|
|
194
|
+
* outside any project, the dashboard's watcher, an inbox sweep) picks its
|
|
195
|
+
* scope from where the FILE is rather than from what it holds. A caller
|
|
196
|
+
* that genuinely means a particular store says so by passing a
|
|
197
|
+
* `profile:<id>` scope, which is returned untouched.
|
|
198
|
+
*/
|
|
199
|
+
export function captureScope(scope, filePath) {
|
|
200
|
+
if (isProfileScope(scope))
|
|
201
|
+
return scope;
|
|
202
|
+
const id = envelopeProfile(filePath);
|
|
203
|
+
return id ? `${PROFILE_SCOPE_PREFIX}${id}` : scope;
|
|
204
|
+
}
|
|
205
|
+
/** How much `meta.json` is worth reading before deciding it is not one. */
|
|
206
|
+
const MAX_META_BYTES = 1024 * 1024;
|
|
207
|
+
/**
|
|
208
|
+
* envelopeProfile reads the `profile` out of the capture envelope holding
|
|
209
|
+
* `filePath`, if there is one. Defensive on the same terms as the rest of
|
|
210
|
+
* capture-envelope: `meta.json` is written by a browser extension on a
|
|
211
|
+
* machine we do not control, so every failure — absent, oversized,
|
|
212
|
+
* truncated, wrong-typed, or an id that could escape a directory — is
|
|
213
|
+
* "this capture names no profile", never a thrown error.
|
|
214
|
+
*/
|
|
215
|
+
export function envelopeProfile(filePath) {
|
|
216
|
+
try {
|
|
217
|
+
const metaPath = path.join(path.dirname(path.resolve(filePath)), 'meta.json');
|
|
218
|
+
if (!fs.existsSync(metaPath))
|
|
219
|
+
return undefined;
|
|
220
|
+
if (fs.statSync(metaPath).size > MAX_META_BYTES)
|
|
221
|
+
return undefined;
|
|
222
|
+
const raw = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
223
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
224
|
+
return undefined;
|
|
225
|
+
const id = raw.profile;
|
|
226
|
+
return isValidProfileId(id) ? id.trim() : undefined;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/** A human-readable name for a scope, for a listing or a search result. */
|
|
233
|
+
export function describeScope(scope) {
|
|
234
|
+
const id = parseProfileScope(scope);
|
|
235
|
+
return id ? `profile ${id}` : scope;
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=profile-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-store.js","sourceRoot":"","sources":["../../../src/knowledge/profile-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Related-at-save (RCL-03) — "3 things in your brain relate to this".
|
|
3
|
+
*
|
|
4
|
+
* The extension calls this on EVERY capture, while the user watches. That
|
|
5
|
+
* single fact sets every design decision here:
|
|
6
|
+
*
|
|
7
|
+
* - it never throws. An empty store, a missing embedder, a URL that was
|
|
8
|
+
* never captured — all of them are an empty list, because a capture must
|
|
9
|
+
* not fail because the sidebar had nothing to say;
|
|
10
|
+
* - it is bounded. The vector pass races a timeout (default 2s) and the
|
|
11
|
+
* cheap signals — same URL, same site, read straight off the metadata log —
|
|
12
|
+
* are computed first, so a slow or absent embedder degrades to a fast
|
|
13
|
+
* answer instead of no answer;
|
|
14
|
+
* - the target itself is never in its own results, by path AND by canonical
|
|
15
|
+
* URL, so a re-capture does not "relate" to the version it replaced.
|
|
16
|
+
*
|
|
17
|
+
* Three signals, combined: exact URL (the same page, captured before),
|
|
18
|
+
* same site, and vector similarity over the chunks already embedded.
|
|
19
|
+
*
|
|
20
|
+
* @module v1/cli/knowledge/related
|
|
21
|
+
*/
|
|
22
|
+
export type RelatedReason = 'same-url' | 'same-site' | 'similar';
|
|
23
|
+
export interface RelatedDocument {
|
|
24
|
+
filePath: string;
|
|
25
|
+
scope: string;
|
|
26
|
+
title: string;
|
|
27
|
+
url?: string;
|
|
28
|
+
site?: string;
|
|
29
|
+
/** 0–1. Exact-URL matches lead; similarity and site agreement compound. */
|
|
30
|
+
score: number;
|
|
31
|
+
reasons: RelatedReason[];
|
|
32
|
+
capturedAt?: string;
|
|
33
|
+
/** The most similar passage, when similarity is one of the reasons. */
|
|
34
|
+
excerpt?: string;
|
|
35
|
+
chunkIndex?: number;
|
|
36
|
+
anchor?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface RelatedOptions {
|
|
39
|
+
rootDir?: string;
|
|
40
|
+
scope?: string;
|
|
41
|
+
limit?: number;
|
|
42
|
+
/** Budget for the vector pass. The metadata signals ignore it: they are
|
|
43
|
+
* synchronous file reads. */
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
minScore?: number;
|
|
46
|
+
store?: 'project' | 'global' | 'all';
|
|
47
|
+
/** Text to compare against, when the caller already has it (a capture that
|
|
48
|
+
* has not been ingested yet). */
|
|
49
|
+
text?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The documents most related to `target` (a URL, a docId, or an indexed path).
|
|
53
|
+
*
|
|
54
|
+
* Safe to call on every capture: bounded, and empty rather than thrown on any
|
|
55
|
+
* failure.
|
|
56
|
+
*/
|
|
57
|
+
export declare function relatedDocuments(target: string, opts?: RelatedOptions): Promise<RelatedDocument[]>;
|
|
58
|
+
//# sourceMappingURL=related.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"related.d.ts","sourceRoot":"","sources":["../../../src/knowledge/related.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AASH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;kCAC8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,KAAK,CAAC;IACrC;sCACkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAwFD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,eAAe,EAAE,CAAC,CAmH5B"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Related-at-save (RCL-03) — "3 things in your brain relate to this".
|
|
3
|
+
*
|
|
4
|
+
* The extension calls this on EVERY capture, while the user watches. That
|
|
5
|
+
* single fact sets every design decision here:
|
|
6
|
+
*
|
|
7
|
+
* - it never throws. An empty store, a missing embedder, a URL that was
|
|
8
|
+
* never captured — all of them are an empty list, because a capture must
|
|
9
|
+
* not fail because the sidebar had nothing to say;
|
|
10
|
+
* - it is bounded. The vector pass races a timeout (default 2s) and the
|
|
11
|
+
* cheap signals — same URL, same site, read straight off the metadata log —
|
|
12
|
+
* are computed first, so a slow or absent embedder degrades to a fast
|
|
13
|
+
* answer instead of no answer;
|
|
14
|
+
* - the target itself is never in its own results, by path AND by canonical
|
|
15
|
+
* URL, so a re-capture does not "relate" to the version it replaced.
|
|
16
|
+
*
|
|
17
|
+
* Three signals, combined: exact URL (the same page, captured before),
|
|
18
|
+
* same site, and vector similarity over the chunks already embedded.
|
|
19
|
+
*
|
|
20
|
+
* @module v1/cli/knowledge/related
|
|
21
|
+
*/
|
|
22
|
+
import * as fs from 'node:fs';
|
|
23
|
+
import * as path from 'node:path';
|
|
24
|
+
import { captureIdentityUrl, readCaptureProvenance } from './capture-envelope.js';
|
|
25
|
+
import { passageQuote } from './citation.js';
|
|
26
|
+
import { documentCapturedAt, documentSite } from './library.js';
|
|
27
|
+
const DEFAULT_LIMIT = 3;
|
|
28
|
+
const DEFAULT_TIMEOUT_MS = 2000;
|
|
29
|
+
const DEFAULT_MIN_SCORE = 0.25;
|
|
30
|
+
const QUERY_CHARS = 1200;
|
|
31
|
+
const SAME_URL_SCORE = 1;
|
|
32
|
+
/** Ceiling for everything that is not literally the same page. */
|
|
33
|
+
const NEAR_SCORE_CAP = 0.95;
|
|
34
|
+
const SAME_SITE_SCORE = 0.45;
|
|
35
|
+
const BOTH_SIGNALS_BONUS = 0.1;
|
|
36
|
+
const EXCERPT_CHARS = 200;
|
|
37
|
+
function stripFragment(url) {
|
|
38
|
+
const hash = url.indexOf('#');
|
|
39
|
+
return (hash === -1 ? url : url.slice(0, hash)).trim();
|
|
40
|
+
}
|
|
41
|
+
function looksLikeUrl(target) {
|
|
42
|
+
return /^https?:\/\//i.test(target.trim());
|
|
43
|
+
}
|
|
44
|
+
function hostOf(url) {
|
|
45
|
+
if (!url)
|
|
46
|
+
return undefined;
|
|
47
|
+
try {
|
|
48
|
+
return new URL(url).hostname.toLowerCase().replace(/^www\./, '');
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Words a URL alone gives us — the fallback query when there is no text. */
|
|
55
|
+
function urlWords(url) {
|
|
56
|
+
try {
|
|
57
|
+
const u = new URL(url);
|
|
58
|
+
return [u.hostname.replace(/^www\./, ''), ...u.pathname.split(/[/\-_.]+/)]
|
|
59
|
+
.filter((w) => w && !/^\d+$/.test(w) && w.length > 1)
|
|
60
|
+
.join(' ');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return url;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function queryText(record, target, provided) {
|
|
67
|
+
if (provided?.trim())
|
|
68
|
+
return provided.slice(0, QUERY_CHARS);
|
|
69
|
+
const parts = [];
|
|
70
|
+
if (record?.provenance?.title)
|
|
71
|
+
parts.push(record.provenance.title);
|
|
72
|
+
if (record && fs.existsSync(record.filePath)) {
|
|
73
|
+
try {
|
|
74
|
+
const { extractText } = await import('../capabilities/cap-documents.js');
|
|
75
|
+
const stat = fs.statSync(record.filePath);
|
|
76
|
+
const text = await extractText({
|
|
77
|
+
path: record.filePath,
|
|
78
|
+
absolutePath: record.filePath,
|
|
79
|
+
extension: path.extname(record.filePath).toLowerCase(),
|
|
80
|
+
size: stat.size,
|
|
81
|
+
modified: stat.mtime,
|
|
82
|
+
created: stat.birthtime,
|
|
83
|
+
});
|
|
84
|
+
if (text)
|
|
85
|
+
parts.push(text.slice(0, QUERY_CHARS));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
/* fall through to the URL words */
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!parts.length)
|
|
92
|
+
parts.push(urlWords(looksLikeUrl(target) ? target : path.basename(target)));
|
|
93
|
+
return parts.join('\n').slice(0, QUERY_CHARS);
|
|
94
|
+
}
|
|
95
|
+
/** Resolve the vector pass or give up on time — whichever comes first. */
|
|
96
|
+
function withTimeout(work, ms, fallback) {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
const timer = setTimeout(() => resolve(fallback), ms);
|
|
99
|
+
work
|
|
100
|
+
.then((value) => {
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
resolve(value);
|
|
103
|
+
})
|
|
104
|
+
.catch(() => {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
resolve(fallback);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The documents most related to `target` (a URL, a docId, or an indexed path).
|
|
112
|
+
*
|
|
113
|
+
* Safe to call on every capture: bounded, and empty rather than thrown on any
|
|
114
|
+
* failure.
|
|
115
|
+
*/
|
|
116
|
+
export async function relatedDocuments(target, opts = {}) {
|
|
117
|
+
try {
|
|
118
|
+
const pipeline = await import('./document-pipeline.js');
|
|
119
|
+
const scope = opts.scope;
|
|
120
|
+
const rootDir = opts.rootDir ?? pipeline.getKnowledgeRoot(scope ?? 'shared');
|
|
121
|
+
const limit = opts.limit ?? DEFAULT_LIMIT;
|
|
122
|
+
const self = pipeline.findDocumentRecord(rootDir, target, scope);
|
|
123
|
+
// A capture that has been written but not yet ingested is the case this
|
|
124
|
+
// story exists for: the extension has the directory on disk and wants to
|
|
125
|
+
// know what it relates to BEFORE the ingest finishes. Its identity comes
|
|
126
|
+
// from the envelope's own meta.json.
|
|
127
|
+
const pending = !self && !looksLikeUrl(target) ? readCaptureProvenance(target) : null;
|
|
128
|
+
const selfUrl = self?.canonicalUrl ??
|
|
129
|
+
(looksLikeUrl(target) ? stripFragment(target) : captureIdentityUrl(pending));
|
|
130
|
+
const selfSite = hostOf(selfUrl);
|
|
131
|
+
const selfPaths = new Set([self?.filePath, looksLikeUrl(target) ? undefined : path.resolve(target)].filter((p) => !!p));
|
|
132
|
+
// Excluding by URL is right when the target IS that page — an indexed
|
|
133
|
+
// document, or a caller asking about the URL itself. It is wrong for a
|
|
134
|
+
// capture still sitting in the inbox: there the earlier capture of the
|
|
135
|
+
// same page is the single most related thing in the brain.
|
|
136
|
+
const excludeByUrl = !!self || looksLikeUrl(target);
|
|
137
|
+
const isSelf = (doc) => selfPaths.has(doc.filePath) || (excludeByUrl && !!selfUrl && doc.canonicalUrl === selfUrl);
|
|
138
|
+
const docs = pipeline.listDocuments(rootDir, scope).filter((d) => !isSelf(d));
|
|
139
|
+
const byPath = new Map(docs.map((d) => [d.filePath, d]));
|
|
140
|
+
const scored = new Map();
|
|
141
|
+
const put = (doc, reason, score, extra = {}) => {
|
|
142
|
+
const existing = scored.get(doc.filePath);
|
|
143
|
+
if (existing) {
|
|
144
|
+
if (!existing.reasons.includes(reason))
|
|
145
|
+
existing.reasons.push(reason);
|
|
146
|
+
// Agreeing signals compound, but only the same page itself reaches 1 —
|
|
147
|
+
// otherwise a same-site neighbour that also embeds well would tie with
|
|
148
|
+
// the earlier capture of this very page and win on sort order.
|
|
149
|
+
const cap = existing.reasons.includes('same-url') ? SAME_URL_SCORE : NEAR_SCORE_CAP;
|
|
150
|
+
existing.score = Math.min(cap, Math.max(existing.score, score) + BOTH_SIGNALS_BONUS);
|
|
151
|
+
Object.assign(existing, extra);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const url = doc.canonicalUrl ?? doc.provenance?.canonicalUrl ?? doc.provenance?.url;
|
|
155
|
+
scored.set(doc.filePath, {
|
|
156
|
+
filePath: doc.filePath,
|
|
157
|
+
scope: doc.scope,
|
|
158
|
+
title: doc.provenance?.title ?? path.basename(doc.filePath),
|
|
159
|
+
...(url ? { url } : {}),
|
|
160
|
+
...(documentSite(doc) ? { site: documentSite(doc) } : {}),
|
|
161
|
+
score,
|
|
162
|
+
reasons: [reason],
|
|
163
|
+
...(documentCapturedAt(doc) ? { capturedAt: documentCapturedAt(doc) } : {}),
|
|
164
|
+
...extra,
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
// 1 + 2 — the cheap signals, straight off the metadata log.
|
|
168
|
+
for (const doc of docs) {
|
|
169
|
+
const url = doc.canonicalUrl ?? doc.provenance?.canonicalUrl;
|
|
170
|
+
if (selfUrl && url && stripFragment(url) === selfUrl)
|
|
171
|
+
put(doc, 'same-url', SAME_URL_SCORE);
|
|
172
|
+
else if (selfSite && documentSite(doc) === selfSite)
|
|
173
|
+
put(doc, 'same-site', SAME_SITE_SCORE);
|
|
174
|
+
}
|
|
175
|
+
// 3 — similarity, bounded. A store with no embeddings comes back empty
|
|
176
|
+
// here and the metadata signals still stand.
|
|
177
|
+
// For a not-yet-ingested capture the text to compare against is the file
|
|
178
|
+
// the caller pointed at, read through the same extraction the ingest uses.
|
|
179
|
+
const queryRecord = self ?? (pending ? { filePath: path.resolve(target), provenance: pending } : undefined);
|
|
180
|
+
const query = await queryText(queryRecord, target, opts.text);
|
|
181
|
+
if (query.trim()) {
|
|
182
|
+
const hits = await withTimeout(pipeline.searchKnowledge(query, {
|
|
183
|
+
scope: scope ?? 'shared',
|
|
184
|
+
rootDir,
|
|
185
|
+
limit: Math.max(limit * 6, 12),
|
|
186
|
+
minScore: opts.minScore ?? DEFAULT_MIN_SCORE,
|
|
187
|
+
store: opts.store ?? 'all',
|
|
188
|
+
}), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS, []);
|
|
189
|
+
const best = new Map();
|
|
190
|
+
for (const hit of hits) {
|
|
191
|
+
if (!hit.filePath || selfPaths.has(hit.filePath))
|
|
192
|
+
continue;
|
|
193
|
+
const current = best.get(hit.filePath);
|
|
194
|
+
if (!current || hit.similarity > current.similarity)
|
|
195
|
+
best.set(hit.filePath, hit);
|
|
196
|
+
}
|
|
197
|
+
for (const [filePath, hit] of best) {
|
|
198
|
+
const doc = byPath.get(filePath);
|
|
199
|
+
if (!doc || isSelf(doc))
|
|
200
|
+
continue;
|
|
201
|
+
put(doc, 'similar', Math.min(1, hit.similarity), {
|
|
202
|
+
excerpt: passageQuote(hit.text ?? '', EXCERPT_CHARS),
|
|
203
|
+
chunkIndex: hit.chunkIndex,
|
|
204
|
+
...(hit.anchor ? { anchor: hit.anchor } : {}),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return [...scored.values()]
|
|
209
|
+
.sort((a, b) => b.score - a.score || (b.capturedAt ?? '').localeCompare(a.capturedAt ?? ''))
|
|
210
|
+
.slice(0, limit);
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
// Called on every capture: an unexpected failure is "nothing related",
|
|
214
|
+
// never a broken save. It is still a bug when it is one of ours, so it is
|
|
215
|
+
// visible under DEBUG rather than only in an empty sidebar.
|
|
216
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG) {
|
|
217
|
+
console.error(`[relatedDocuments] ${target}:`, err);
|
|
218
|
+
}
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=related.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"related.js","sourceRoot":"","sources":["../../../src/knowledge/related.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic diff between two versions of a captured page (RCL-08).
|
|
3
|
+
*
|
|
4
|
+
* A character diff of a re-captured page is noise: the byline date moved, an
|
|
5
|
+
* ad slot re-rendered, the markdown re-wrapped. What a watcher actually wants
|
|
6
|
+
* to be told is WHICH SECTIONS changed and roughly how much — "Pricing gained
|
|
7
|
+
* a paragraph, Changelog got three new entries, everything else is the same".
|
|
8
|
+
*
|
|
9
|
+
* So the unit here is the markdown section (heading + body), keyed by its
|
|
10
|
+
* full heading path, and the measure is words and sentences rather than
|
|
11
|
+
* characters. Sentences are also what gives a change its sample line: the
|
|
12
|
+
* first sentence that is in the new version and was not in the old one is the
|
|
13
|
+
* most useful single thing to print.
|
|
14
|
+
*
|
|
15
|
+
* No dependency, no LLM: heading paths and sentence sets are enough to say
|
|
16
|
+
* what moved.
|
|
17
|
+
*
|
|
18
|
+
* @module v1/cli/knowledge/section-diff
|
|
19
|
+
*/
|
|
20
|
+
export interface Section {
|
|
21
|
+
/** Full heading path, e.g. `Pricing > Enterprise`. `(intro)` for text
|
|
22
|
+
* before the first heading. */
|
|
23
|
+
key: string;
|
|
24
|
+
heading: string;
|
|
25
|
+
level: number;
|
|
26
|
+
body: string;
|
|
27
|
+
}
|
|
28
|
+
export interface SectionChange {
|
|
29
|
+
key: string;
|
|
30
|
+
addedWords: number;
|
|
31
|
+
removedWords: number;
|
|
32
|
+
/** First sentence present in the new version and absent from the old. */
|
|
33
|
+
sample?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface SectionDiff {
|
|
36
|
+
added: string[];
|
|
37
|
+
removed: string[];
|
|
38
|
+
changed: SectionChange[];
|
|
39
|
+
unchanged: number;
|
|
40
|
+
/** One line, ready to print or to put in a notification. */
|
|
41
|
+
summary: string;
|
|
42
|
+
}
|
|
43
|
+
/** Split markdown into sections keyed by heading path. Duplicate paths get a
|
|
44
|
+
* `#2` suffix so two "Notes" sections never collapse into one. */
|
|
45
|
+
export declare function splitSections(text: string): Section[];
|
|
46
|
+
/** What changed between two versions of the same page. */
|
|
47
|
+
export declare function diffSections(oldText: string, newText: string): SectionDiff;
|
|
48
|
+
//# sourceMappingURL=section-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-diff.d.ts","sourceRoot":"","sources":["../../../src/knowledge/section-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,OAAO;IACtB;oCACgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;CACjB;AAKD;mEACmE;AACnE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAmCrD;AA2DD,0DAA0D;AAC1D,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CAgC1E"}
|