@mxalbert/context-mode 2.0.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-plugin/marketplace.json +33 -0
- package/.claude-plugin/plugin.json +31 -0
- package/.codex-plugin/hooks.json +65 -0
- package/.codex-plugin/mcp.json +13 -0
- package/.codex-plugin/plugin.json +31 -0
- package/.openclaw-plugin/index.ts +12 -0
- package/.openclaw-plugin/openclaw.plugin.json +38 -0
- package/.openclaw-plugin/package.json +28 -0
- package/LICENSE +94 -0
- package/README.md +1619 -0
- package/bin/statusline.mjs +378 -0
- package/build/adapters/antigravity/index.d.ts +51 -0
- package/build/adapters/antigravity/index.js +169 -0
- package/build/adapters/antigravity-cli/index.d.ts +51 -0
- package/build/adapters/antigravity-cli/index.js +342 -0
- package/build/adapters/base.d.ts +92 -0
- package/build/adapters/base.js +135 -0
- package/build/adapters/claude-code/config.d.ts +8 -0
- package/build/adapters/claude-code/config.js +8 -0
- package/build/adapters/claude-code/hooks.d.ts +106 -0
- package/build/adapters/claude-code/hooks.js +183 -0
- package/build/adapters/claude-code/index.d.ts +80 -0
- package/build/adapters/claude-code/index.js +569 -0
- package/build/adapters/claude-code-base.d.ts +49 -0
- package/build/adapters/claude-code-base.js +113 -0
- package/build/adapters/client-map.d.ts +10 -0
- package/build/adapters/client-map.js +42 -0
- package/build/adapters/codex/config.d.ts +8 -0
- package/build/adapters/codex/config.js +8 -0
- package/build/adapters/codex/hooks.d.ts +55 -0
- package/build/adapters/codex/hooks.js +64 -0
- package/build/adapters/codex/index.d.ts +87 -0
- package/build/adapters/codex/index.js +995 -0
- package/build/adapters/codex/paths.d.ts +1 -0
- package/build/adapters/codex/paths.js +12 -0
- package/build/adapters/codex/usage.d.ts +107 -0
- package/build/adapters/codex/usage.js +227 -0
- package/build/adapters/copilot-base.d.ts +94 -0
- package/build/adapters/copilot-base.js +297 -0
- package/build/adapters/copilot-cli/hooks.d.ts +33 -0
- package/build/adapters/copilot-cli/hooks.js +64 -0
- package/build/adapters/copilot-cli/index.d.ts +48 -0
- package/build/adapters/copilot-cli/index.js +341 -0
- package/build/adapters/cursor/config.d.ts +4 -0
- package/build/adapters/cursor/config.js +4 -0
- package/build/adapters/cursor/hooks.d.ts +55 -0
- package/build/adapters/cursor/hooks.js +82 -0
- package/build/adapters/cursor/index.d.ts +64 -0
- package/build/adapters/cursor/index.js +517 -0
- package/build/adapters/detect.d.ts +136 -0
- package/build/adapters/detect.js +623 -0
- package/build/adapters/gemini-cli/config.d.ts +8 -0
- package/build/adapters/gemini-cli/config.js +8 -0
- package/build/adapters/gemini-cli/hooks.d.ts +83 -0
- package/build/adapters/gemini-cli/hooks.js +115 -0
- package/build/adapters/gemini-cli/index.d.ts +80 -0
- package/build/adapters/gemini-cli/index.js +483 -0
- package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
- package/build/adapters/jetbrains-copilot/config.js +8 -0
- package/build/adapters/jetbrains-copilot/hooks.d.ts +59 -0
- package/build/adapters/jetbrains-copilot/hooks.js +87 -0
- package/build/adapters/jetbrains-copilot/index.d.ts +31 -0
- package/build/adapters/jetbrains-copilot/index.js +131 -0
- package/build/adapters/kimi/config.d.ts +8 -0
- package/build/adapters/kimi/config.js +8 -0
- package/build/adapters/kimi/hooks.d.ts +28 -0
- package/build/adapters/kimi/hooks.js +34 -0
- package/build/adapters/kimi/index.d.ts +66 -0
- package/build/adapters/kimi/index.js +537 -0
- package/build/adapters/kimi/paths.d.ts +21 -0
- package/build/adapters/kimi/paths.js +52 -0
- package/build/adapters/kimi/usage.d.ts +82 -0
- package/build/adapters/kimi/usage.js +217 -0
- package/build/adapters/kiro/hooks.d.ts +61 -0
- package/build/adapters/kiro/hooks.js +94 -0
- package/build/adapters/kiro/index.d.ts +61 -0
- package/build/adapters/kiro/index.js +301 -0
- package/build/adapters/omp/index.d.ts +65 -0
- package/build/adapters/omp/index.js +182 -0
- package/build/adapters/omp/plugin.d.ts +81 -0
- package/build/adapters/omp/plugin.js +331 -0
- package/build/adapters/omp/usage.d.ts +49 -0
- package/build/adapters/omp/usage.js +110 -0
- package/build/adapters/openclaw/config.d.ts +8 -0
- package/build/adapters/openclaw/config.js +8 -0
- package/build/adapters/openclaw/hooks.d.ts +50 -0
- package/build/adapters/openclaw/hooks.js +61 -0
- package/build/adapters/openclaw/index.d.ts +74 -0
- package/build/adapters/openclaw/index.js +439 -0
- package/build/adapters/openclaw/mcp-tools.d.ts +54 -0
- package/build/adapters/openclaw/mcp-tools.js +207 -0
- package/build/adapters/openclaw/plugin.d.ts +141 -0
- package/build/adapters/openclaw/plugin.js +818 -0
- package/build/adapters/openclaw/session-db.d.ts +55 -0
- package/build/adapters/openclaw/session-db.js +88 -0
- package/build/adapters/openclaw/usage.d.ts +34 -0
- package/build/adapters/openclaw/usage.js +52 -0
- package/build/adapters/openclaw/workspace-router.d.ts +29 -0
- package/build/adapters/openclaw/workspace-router.js +64 -0
- package/build/adapters/opencode/config.d.ts +8 -0
- package/build/adapters/opencode/config.js +8 -0
- package/build/adapters/opencode/hooks.d.ts +38 -0
- package/build/adapters/opencode/hooks.js +50 -0
- package/build/adapters/opencode/index.d.ts +144 -0
- package/build/adapters/opencode/index.js +638 -0
- package/build/adapters/opencode/plugin.d.ts +249 -0
- package/build/adapters/opencode/plugin.js +1536 -0
- package/build/adapters/opencode/v2.d.ts +117 -0
- package/build/adapters/opencode/v2.js +224 -0
- package/build/adapters/opencode/zod3tov4.d.ts +1 -0
- package/build/adapters/opencode/zod3tov4.js +132 -0
- package/build/adapters/pi/extension.d.ts +81 -0
- package/build/adapters/pi/extension.js +861 -0
- package/build/adapters/pi/index.d.ts +57 -0
- package/build/adapters/pi/index.js +173 -0
- package/build/adapters/pi/mcp-bridge.d.ts +268 -0
- package/build/adapters/pi/mcp-bridge.js +894 -0
- package/build/adapters/qwen-code/hooks.d.ts +26 -0
- package/build/adapters/qwen-code/hooks.js +29 -0
- package/build/adapters/qwen-code/index.d.ts +40 -0
- package/build/adapters/qwen-code/index.js +361 -0
- package/build/adapters/qwen-code/usage.d.ts +90 -0
- package/build/adapters/qwen-code/usage.js +222 -0
- package/build/adapters/types.d.ts +360 -0
- package/build/adapters/types.js +118 -0
- package/build/adapters/vscode-copilot/config.d.ts +8 -0
- package/build/adapters/vscode-copilot/config.js +8 -0
- package/build/adapters/vscode-copilot/hooks.d.ts +71 -0
- package/build/adapters/vscode-copilot/hooks.js +98 -0
- package/build/adapters/vscode-copilot/index.d.ts +32 -0
- package/build/adapters/vscode-copilot/index.js +222 -0
- package/build/adapters/zed/index.d.ts +40 -0
- package/build/adapters/zed/index.js +163 -0
- package/build/cli.d.ts +34 -0
- package/build/cli.js +1828 -0
- package/build/db-base.d.ts +250 -0
- package/build/db-base.js +756 -0
- package/build/executor.d.ts +72 -0
- package/build/executor.js +678 -0
- package/build/exit-classify.d.ts +19 -0
- package/build/exit-classify.js +12 -0
- package/build/fetch/blocks.d.ts +145 -0
- package/build/fetch/blocks.js +244 -0
- package/build/fetch/extract.d.ts +68 -0
- package/build/fetch/extract.js +123 -0
- package/build/fetch/page-store.d.ts +66 -0
- package/build/fetch/page-store.js +174 -0
- package/build/fetch-cache.d.ts +13 -0
- package/build/fetch-cache.js +15 -0
- package/build/lifecycle.d.ts +124 -0
- package/build/lifecycle.js +278 -0
- package/build/package-identity.d.ts +26 -0
- package/build/package-identity.js +28 -0
- package/build/runPool.d.ts +36 -0
- package/build/runPool.js +51 -0
- package/build/runtime.d.ts +71 -0
- package/build/runtime.js +659 -0
- package/build/search/auto-memory.d.ts +47 -0
- package/build/search/auto-memory.js +170 -0
- package/build/search/ctx-search-schema.d.ts +90 -0
- package/build/search/ctx-search-schema.js +135 -0
- package/build/search/flood-guard.d.ts +57 -0
- package/build/search/flood-guard.js +80 -0
- package/build/search/unified.d.ts +56 -0
- package/build/search/unified.js +104 -0
- package/build/security.d.ts +206 -0
- package/build/security.js +780 -0
- package/build/server.d.ts +240 -0
- package/build/server.js +5034 -0
- package/build/session/analytics.d.ts +773 -0
- package/build/session/analytics.js +2420 -0
- package/build/session/db.d.ts +468 -0
- package/build/session/db.js +1330 -0
- package/build/session/error-classifier.d.ts +87 -0
- package/build/session/error-classifier.js +303 -0
- package/build/session/event-emit.d.ts +48 -0
- package/build/session/event-emit.js +101 -0
- package/build/session/extract.d.ts +333 -0
- package/build/session/extract.js +2688 -0
- package/build/session/model-prices.json +429 -0
- package/build/session/persist-tool-calls.d.ts +54 -0
- package/build/session/persist-tool-calls.js +105 -0
- package/build/session/pricing.d.ts +64 -0
- package/build/session/pricing.js +151 -0
- package/build/session/project-attribution.d.ts +73 -0
- package/build/session/project-attribution.js +245 -0
- package/build/session/purge.d.ts +138 -0
- package/build/session/purge.js +240 -0
- package/build/session/retrieval-marker.d.ts +39 -0
- package/build/session/retrieval-marker.js +65 -0
- package/build/session/snapshot.d.ts +46 -0
- package/build/session/snapshot.js +532 -0
- package/build/store-directory.d.ts +56 -0
- package/build/store-directory.js +254 -0
- package/build/store.d.ts +147 -0
- package/build/store.js +1714 -0
- package/build/truncate.d.ts +57 -0
- package/build/truncate.js +142 -0
- package/build/types.d.ts +117 -0
- package/build/types.js +20 -0
- package/build/util/claude-config.d.ts +32 -0
- package/build/util/claude-config.js +84 -0
- package/build/util/hook-config.d.ts +27 -0
- package/build/util/hook-config.js +76 -0
- package/build/util/jsonc.d.ts +14 -0
- package/build/util/jsonc.js +104 -0
- package/build/util/plugin-cache-integrity.d.ts +51 -0
- package/build/util/plugin-cache-integrity.js +146 -0
- package/build/util/project-dir.d.ts +149 -0
- package/build/util/project-dir.js +340 -0
- package/build/util/sibling-mcp.d.ts +79 -0
- package/build/util/sibling-mcp.js +181 -0
- package/cli.bundle.mjs +1495 -0
- package/configs/antigravity/GEMINI.md +68 -0
- package/configs/antigravity/mcp_config.json +7 -0
- package/configs/antigravity-cli/hooks/hooks.json +37 -0
- package/configs/antigravity-cli/hooks.json +37 -0
- package/configs/antigravity-cli/mcp_config.json +10 -0
- package/configs/antigravity-cli/plugin.json +14 -0
- package/configs/antigravity-cli/rules/context-mode.md +77 -0
- package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
- package/configs/claude-code/CLAUDE.md +91 -0
- package/configs/codex/AGENTS.md +99 -0
- package/configs/codex/config.toml +8 -0
- package/configs/codex/hooks.json +47 -0
- package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
- package/configs/copilot-cli/.mcp.json +12 -0
- package/configs/copilot-cli/README.md +47 -0
- package/configs/copilot-cli/hooks.json +41 -0
- package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
- package/configs/cursor/context-mode.mdc +76 -0
- package/configs/cursor/hooks.json +21 -0
- package/configs/cursor/mcp.json +7 -0
- package/configs/gemini-cli/GEMINI.md +88 -0
- package/configs/gemini-cli/mcp.json +7 -0
- package/configs/gemini-cli/settings.json +60 -0
- package/configs/jetbrains-copilot/copilot-instructions.md +80 -0
- package/configs/jetbrains-copilot/hooks.json +16 -0
- package/configs/jetbrains-copilot/mcp.json +7 -0
- package/configs/kilo/AGENTS.md +84 -0
- package/configs/kilo/kilo.json +6 -0
- package/configs/kimi/hooks.json +54 -0
- package/configs/kiro/KIRO.md +84 -0
- package/configs/kiro/agent.json +18 -0
- package/configs/kiro/mcp.json +7 -0
- package/configs/omp/SYSTEM.md +85 -0
- package/configs/omp/mcp.json +7 -0
- package/configs/openclaw/AGENTS.md +87 -0
- package/configs/openclaw/openclaw.json +13 -0
- package/configs/opencode/AGENTS.md +84 -0
- package/configs/opencode/opencode.json +6 -0
- package/configs/pi/AGENTS.md +3 -0
- package/configs/qwen-code/QWEN.md +91 -0
- package/configs/vscode-copilot/copilot-instructions.md +80 -0
- package/configs/vscode-copilot/hooks.json +16 -0
- package/configs/vscode-copilot/mcp.json +7 -0
- package/configs/zed/AGENTS.md +68 -0
- package/hooks/antigravity-cli/payload.mjs +98 -0
- package/hooks/antigravity-cli/posttooluse.mjs +138 -0
- package/hooks/antigravity-cli/pretooluse.mjs +78 -0
- package/hooks/antigravity-cli/stop.mjs +58 -0
- package/hooks/auto-injection.mjs +102 -0
- package/hooks/cache-heal-utils.mjs +379 -0
- package/hooks/codex/platform.mjs +1 -0
- package/hooks/codex/posttooluse.mjs +66 -0
- package/hooks/codex/precompact.mjs +70 -0
- package/hooks/codex/pretooluse.mjs +42 -0
- package/hooks/codex/sessionstart.mjs +121 -0
- package/hooks/codex/stop.mjs +138 -0
- package/hooks/codex/userpromptsubmit.mjs +75 -0
- package/hooks/copilot-cli/posttooluse.mjs +79 -0
- package/hooks/copilot-cli/precompact.mjs +66 -0
- package/hooks/copilot-cli/pretooluse.mjs +41 -0
- package/hooks/copilot-cli/sessionstart.mjs +121 -0
- package/hooks/copilot-cli/stop.mjs +59 -0
- package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
- package/hooks/core/codex-caps.mjs +112 -0
- package/hooks/core/formatters.mjs +356 -0
- package/hooks/core/mcp-ready.mjs +109 -0
- package/hooks/core/platform-detect.mjs +49 -0
- package/hooks/core/routing.mjs +1050 -0
- package/hooks/core/stdin.mjs +90 -0
- package/hooks/core/tool-naming.mjs +57 -0
- package/hooks/cursor/afteragentresponse.mjs +74 -0
- package/hooks/cursor/hooks.json +31 -0
- package/hooks/cursor/posttooluse.mjs +72 -0
- package/hooks/cursor/pretooluse.mjs +26 -0
- package/hooks/cursor/sessionstart.mjs +97 -0
- package/hooks/cursor/stop.mjs +48 -0
- package/hooks/ensure-deps.mjs +254 -0
- package/hooks/formatters/claude-code.mjs +113 -0
- package/hooks/formatters/cursor.mjs +37 -0
- package/hooks/formatters/gemini-cli.mjs +55 -0
- package/hooks/formatters/vscode-copilot.mjs +55 -0
- package/hooks/gemini-cli/aftermodel.mjs +70 -0
- package/hooks/gemini-cli/aftertool.mjs +61 -0
- package/hooks/gemini-cli/beforeagent.mjs +99 -0
- package/hooks/gemini-cli/beforetool.mjs +27 -0
- package/hooks/gemini-cli/precompress.mjs +55 -0
- package/hooks/gemini-cli/sessionstart.mjs +137 -0
- package/hooks/heal-partial-install.mjs +712 -0
- package/hooks/hooks.json +143 -0
- package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
- package/hooks/jetbrains-copilot/precompact.mjs +55 -0
- package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
- package/hooks/jetbrains-copilot/sessionstart.mjs +124 -0
- package/hooks/kimi/platform.mjs +1 -0
- package/hooks/kimi/posttooluse.mjs +72 -0
- package/hooks/kimi/precompact.mjs +80 -0
- package/hooks/kimi/pretooluse.mjs +42 -0
- package/hooks/kimi/sessionend.mjs +61 -0
- package/hooks/kimi/sessionstart.mjs +113 -0
- package/hooks/kimi/stop.mjs +132 -0
- package/hooks/kimi/userpromptsubmit.mjs +90 -0
- package/hooks/kiro/agentspawn.mjs +97 -0
- package/hooks/kiro/posttooluse.mjs +51 -0
- package/hooks/kiro/pretooluse.mjs +64 -0
- package/hooks/kiro/userpromptsubmit.mjs +88 -0
- package/hooks/normalize-hooks.mjs +323 -0
- package/hooks/platform-bridge.mjs +340 -0
- package/hooks/posttooluse.mjs +225 -0
- package/hooks/precompact.mjs +100 -0
- package/hooks/pretooluse.mjs +227 -0
- package/hooks/qwen-code/platform.mjs +1 -0
- package/hooks/qwen-code/stop.mjs +168 -0
- package/hooks/routing-block.mjs +105 -0
- package/hooks/run-hook.mjs +95 -0
- package/hooks/security.bundle.mjs +2 -0
- package/hooks/session-attribution.bundle.mjs +1 -0
- package/hooks/session-db.bundle.mjs +145 -0
- package/hooks/session-directive.mjs +520 -0
- package/hooks/session-extract.bundle.mjs +3 -0
- package/hooks/session-helpers.mjs +428 -0
- package/hooks/session-loaders.mjs +448 -0
- package/hooks/session-snapshot.bundle.mjs +32 -0
- package/hooks/sessionstart.mjs +464 -0
- package/hooks/stop.mjs +82 -0
- package/hooks/suppress-stderr.mjs +25 -0
- package/hooks/userpromptsubmit.mjs +108 -0
- package/hooks/vscode-copilot/posttooluse.mjs +61 -0
- package/hooks/vscode-copilot/precompact.mjs +55 -0
- package/hooks/vscode-copilot/pretooluse.mjs +27 -0
- package/hooks/vscode-copilot/sessionstart.mjs +129 -0
- package/openclaw.plugin.json +38 -0
- package/package.json +132 -0
- package/scripts/heal-better-sqlite3.mjs +430 -0
- package/scripts/heal-installed-plugins.mjs +635 -0
- package/scripts/plugin-cache-integrity.mjs +248 -0
- package/scripts/postinstall.mjs +397 -0
- package/server.bundle.mjs +1421 -0
- package/server.js +5 -0
- package/skills/.ignore +7 -0
- package/skills/context-mode/SKILL.md +300 -0
- package/skills/context-mode/references/anti-patterns.md +283 -0
- package/skills/context-mode/references/patterns-javascript.md +298 -0
- package/skills/context-mode/references/patterns-python.md +304 -0
- package/skills/context-mode/references/patterns-shell.md +277 -0
- package/skills/ctx-doctor/SKILL.md +22 -0
- package/skills/ctx-index/SKILL.md +46 -0
- package/skills/ctx-insight/SKILL.md +24 -0
- package/skills/ctx-purge/SKILL.md +49 -0
- package/skills/ctx-search/SKILL.md +35 -0
- package/skills/ctx-stats/SKILL.md +26 -0
- package/skills/ctx-upgrade/SKILL.md +31 -0
- package/start.mjs +616 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* walkDirectory — bounded recursive directory walker for ctx_index (#687).
|
|
3
|
+
*
|
|
4
|
+
* Issue: ctx_index refused directory paths via the security gate at
|
|
5
|
+
* src/store.ts:845 ("refusing to index <path>: not a regular file"). The gate
|
|
6
|
+
* is a TOCTOU defense from #442 round-3 and MUST be preserved — directory
|
|
7
|
+
* support is layered as a separate concern here. Each file produced by
|
|
8
|
+
* walkDirectory is then read via the existing per-file
|
|
9
|
+
* `openSync + fstatSync.isFile()` invariant in `ContentStore.index()`.
|
|
10
|
+
*
|
|
11
|
+
* Reported by @matiasduartee across 4 clients × Windows 11.
|
|
12
|
+
* https://github.com/anthropic-experimental/context-mode/issues/687
|
|
13
|
+
*
|
|
14
|
+
* Design constraints:
|
|
15
|
+
* - No new dependencies (avoid the `ignore` package — issue #687 Diagnose).
|
|
16
|
+
* - Cross-OS: path.sep / path.join everywhere, never raw "/" string ops.
|
|
17
|
+
* - Symlink cycle detection via a resolved-path Set.
|
|
18
|
+
* - Symlink-escape rejection: refuse to follow symlinks that resolve outside
|
|
19
|
+
* the rootPath (defense-in-depth alongside per-file checkFilePathDenyPolicy).
|
|
20
|
+
* - FTS5-blowup guard: hard cap maxFiles (default 200, per Architect).
|
|
21
|
+
*/
|
|
22
|
+
import { readdirSync, statSync, lstatSync, realpathSync, existsSync, readFileSync, } from "node:fs";
|
|
23
|
+
import { join, extname, relative, sep, resolve } from "node:path";
|
|
24
|
+
const DEFAULT_EXCLUDES = [
|
|
25
|
+
"node_modules",
|
|
26
|
+
".git",
|
|
27
|
+
"dist",
|
|
28
|
+
"build",
|
|
29
|
+
".next",
|
|
30
|
+
"coverage",
|
|
31
|
+
".venv",
|
|
32
|
+
"__pycache__",
|
|
33
|
+
".DS_Store",
|
|
34
|
+
];
|
|
35
|
+
const DEFAULT_EXTENSIONS = [
|
|
36
|
+
".md",
|
|
37
|
+
".mdx",
|
|
38
|
+
".txt",
|
|
39
|
+
".json",
|
|
40
|
+
".yaml",
|
|
41
|
+
".yml",
|
|
42
|
+
".ts",
|
|
43
|
+
".tsx",
|
|
44
|
+
".js",
|
|
45
|
+
".jsx",
|
|
46
|
+
".py",
|
|
47
|
+
".rs",
|
|
48
|
+
".go",
|
|
49
|
+
".sh",
|
|
50
|
+
];
|
|
51
|
+
const DEFAULT_MAX_DEPTH = 5;
|
|
52
|
+
const DEFAULT_MAX_FILES = 200;
|
|
53
|
+
/**
|
|
54
|
+
* Convert a simple glob pattern (`*`, `**`, `?`) to a RegExp. Anchors at
|
|
55
|
+
* boundaries so `node_modules` matches `node_modules` AND `node_modules/pkg`.
|
|
56
|
+
* Patterns are matched against POSIX-style relative paths (forward slashes)
|
|
57
|
+
* to give consistent behavior across macOS / Windows.
|
|
58
|
+
*/
|
|
59
|
+
function globToRegExp(pattern) {
|
|
60
|
+
// Escape regex metachars except glob ones.
|
|
61
|
+
let re = "";
|
|
62
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
63
|
+
const c = pattern[i];
|
|
64
|
+
if (c === "*") {
|
|
65
|
+
if (pattern[i + 1] === "*") {
|
|
66
|
+
re += ".*";
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
re += "[^/]*";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else if (c === "?") {
|
|
74
|
+
re += "[^/]";
|
|
75
|
+
}
|
|
76
|
+
else if ("\\^$.|+()[]{}".includes(c)) {
|
|
77
|
+
re += "\\" + c;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
re += c;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return new RegExp(`^${re}$`);
|
|
84
|
+
}
|
|
85
|
+
/** Match a posix-style relative path against any of the patterns. */
|
|
86
|
+
function matchesAny(relPosix, patterns) {
|
|
87
|
+
if (patterns.length === 0)
|
|
88
|
+
return false;
|
|
89
|
+
const basename = relPosix.split("/").pop() ?? relPosix;
|
|
90
|
+
for (const p of patterns) {
|
|
91
|
+
// Bare names match basename OR any path segment.
|
|
92
|
+
if (!p.includes("/") && !p.includes("*")) {
|
|
93
|
+
if (basename === p)
|
|
94
|
+
return true;
|
|
95
|
+
if (relPosix.split("/").includes(p))
|
|
96
|
+
return true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const re = globToRegExp(p);
|
|
100
|
+
if (re.test(relPosix))
|
|
101
|
+
return true;
|
|
102
|
+
if (re.test(basename))
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Parse a .gitignore file into a list of patterns. Comments and blank lines
|
|
109
|
+
* are stripped. Negation (`!`) is not supported — kept conservative.
|
|
110
|
+
*/
|
|
111
|
+
function parseGitignore(rootPath) {
|
|
112
|
+
const giPath = join(rootPath, ".gitignore");
|
|
113
|
+
if (!existsSync(giPath))
|
|
114
|
+
return [];
|
|
115
|
+
try {
|
|
116
|
+
const text = readFileSync(giPath, "utf-8");
|
|
117
|
+
return text
|
|
118
|
+
.split(/\r?\n/)
|
|
119
|
+
.map(l => l.trim())
|
|
120
|
+
.filter(l => l.length > 0 && !l.startsWith("#") && !l.startsWith("!"))
|
|
121
|
+
.map(l => l.replace(/^\//, "").replace(/\/$/, ""));
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Convert an absolute path under rootPath to a POSIX-style relative path
|
|
129
|
+
* so glob matching is identical across macOS/Linux/Windows.
|
|
130
|
+
*/
|
|
131
|
+
function toPosixRel(rootPath, absPath) {
|
|
132
|
+
const rel = relative(rootPath, absPath);
|
|
133
|
+
return rel.split(sep).join("/");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Walk `rootPath` recursively under the given bounds and return absolute file
|
|
137
|
+
* paths matching the filters. Pure synchronous traversal — no allocations
|
|
138
|
+
* beyond the result array. Symlink cycles are detected via a resolved-path
|
|
139
|
+
* Set; symlink escapes (resolving outside rootPath) are silently skipped.
|
|
140
|
+
*/
|
|
141
|
+
export function walkDirectory(rootPath, opts = {}) {
|
|
142
|
+
return walkDirectoryDetailed(rootPath, opts).files;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Same as walkDirectory but returns capped + totalSeen so callers can surface
|
|
146
|
+
* a "capped at N files" notice in their response.
|
|
147
|
+
*/
|
|
148
|
+
export function walkDirectoryDetailed(rootPath, opts = {}) {
|
|
149
|
+
const { include, exclude, maxDepth = DEFAULT_MAX_DEPTH, maxFiles = DEFAULT_MAX_FILES, extensions, respectGitignore = true, followSymlinks = false, } = opts;
|
|
150
|
+
// Normalize rootPath to its real path so symlink-escape detection is sound.
|
|
151
|
+
let rootReal;
|
|
152
|
+
try {
|
|
153
|
+
rootReal = realpathSync(rootPath);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return { files: [], capped: false, totalSeen: 0 };
|
|
157
|
+
}
|
|
158
|
+
const exts = (extensions && extensions.length > 0 ? extensions : DEFAULT_EXTENSIONS)
|
|
159
|
+
.map(e => (e.startsWith(".") ? e : "." + e).toLowerCase());
|
|
160
|
+
const excludes = [
|
|
161
|
+
...DEFAULT_EXCLUDES,
|
|
162
|
+
...(exclude ?? []),
|
|
163
|
+
...(respectGitignore ? parseGitignore(rootReal) : []),
|
|
164
|
+
];
|
|
165
|
+
const includes = include ?? [];
|
|
166
|
+
const out = [];
|
|
167
|
+
const visited = new Set([rootReal]);
|
|
168
|
+
let totalSeen = 0;
|
|
169
|
+
let capped = false;
|
|
170
|
+
function walk(absDir, depth) {
|
|
171
|
+
if (capped)
|
|
172
|
+
return;
|
|
173
|
+
if (depth > maxDepth)
|
|
174
|
+
return;
|
|
175
|
+
let entries;
|
|
176
|
+
try {
|
|
177
|
+
entries = readdirSync(absDir, { withFileTypes: true });
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return; // unreadable directory — skip silently
|
|
181
|
+
}
|
|
182
|
+
for (const ent of entries) {
|
|
183
|
+
if (capped)
|
|
184
|
+
return;
|
|
185
|
+
const absChild = join(absDir, ent.name);
|
|
186
|
+
const relPosix = toPosixRel(rootReal, absChild);
|
|
187
|
+
// Exclude check applies to both files and dirs — early prune.
|
|
188
|
+
if (matchesAny(relPosix, excludes))
|
|
189
|
+
continue;
|
|
190
|
+
// Include filter applies to files only — see below.
|
|
191
|
+
// Resolve symlinks once; reject escapes; track for cycle detection.
|
|
192
|
+
let isDirChild = ent.isDirectory();
|
|
193
|
+
let isFileChild = ent.isFile();
|
|
194
|
+
let isSymlink = false;
|
|
195
|
+
try {
|
|
196
|
+
const lst = lstatSync(absChild);
|
|
197
|
+
isSymlink = lst.isSymbolicLink();
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (isSymlink) {
|
|
203
|
+
if (!followSymlinks)
|
|
204
|
+
continue;
|
|
205
|
+
let resolved;
|
|
206
|
+
try {
|
|
207
|
+
resolved = realpathSync(absChild);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
continue; // dangling
|
|
211
|
+
}
|
|
212
|
+
// Symlink-escape: refuse to follow if the resolved target leaves rootReal.
|
|
213
|
+
const escapeRel = relative(rootReal, resolved);
|
|
214
|
+
if (escapeRel.startsWith("..") || resolve(escapeRel) === resolved) {
|
|
215
|
+
// resolve(absolute) === absolute → target is absolute outside root
|
|
216
|
+
if (escapeRel.startsWith(".."))
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (visited.has(resolved))
|
|
220
|
+
continue;
|
|
221
|
+
visited.add(resolved);
|
|
222
|
+
try {
|
|
223
|
+
const st = statSync(resolved);
|
|
224
|
+
isDirChild = st.isDirectory();
|
|
225
|
+
isFileChild = st.isFile();
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (isDirChild) {
|
|
232
|
+
walk(absChild, depth + 1);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (!isFileChild)
|
|
236
|
+
continue;
|
|
237
|
+
// Extension filter.
|
|
238
|
+
const ext = extname(absChild).toLowerCase();
|
|
239
|
+
if (!exts.includes(ext))
|
|
240
|
+
continue;
|
|
241
|
+
// Include filter (if any): file must match at least one include pattern.
|
|
242
|
+
if (includes.length > 0 && !matchesAny(relPosix, includes))
|
|
243
|
+
continue;
|
|
244
|
+
totalSeen++;
|
|
245
|
+
if (out.length >= maxFiles) {
|
|
246
|
+
capped = true;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
out.push(absChild);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
walk(rootReal, 0);
|
|
253
|
+
return { files: out, capped, totalSeen };
|
|
254
|
+
}
|
package/build/store.d.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContentStore — FTS5 BM25-based knowledge base for context-mode.
|
|
3
|
+
*
|
|
4
|
+
* Chunks markdown content by headings (keeping code blocks intact),
|
|
5
|
+
* stores in SQLite FTS5, and retrieves via BM25-ranked search.
|
|
6
|
+
*
|
|
7
|
+
* Use for documentation, API references, and any content where
|
|
8
|
+
* you need EXACT text later — not summaries.
|
|
9
|
+
*/
|
|
10
|
+
import { type WalkOptions } from "./store-directory.js";
|
|
11
|
+
type SourceMatchMode = "like" | "exact";
|
|
12
|
+
import type { IndexResult, SearchResult, StoreStats } from "./types.js";
|
|
13
|
+
export type { IndexResult, SearchResult, StoreStats } from "./types.js";
|
|
14
|
+
export declare function sanitizeQuery(query: string, mode?: "AND" | "OR"): string;
|
|
15
|
+
export declare function sanitizeTrigramQuery(query: string, mode?: "AND" | "OR"): string;
|
|
16
|
+
/**
|
|
17
|
+
* Remove stale DB files from previous sessions whose processes no longer exist.
|
|
18
|
+
*/
|
|
19
|
+
export declare function cleanupStaleDBs(): number;
|
|
20
|
+
/**
|
|
21
|
+
* Clean up stale per-project content store DBs older than maxAgeDays.
|
|
22
|
+
* Scans the given directory for *.db files and checks mtime.
|
|
23
|
+
* Also detects zombie processes holding WAL locks — if a WAL file exists
|
|
24
|
+
* but the owning PID is dead, the DB files are cleaned up regardless of age.
|
|
25
|
+
*/
|
|
26
|
+
export declare function cleanupStaleContentDBs(contentDir: string, maxAgeDays: number): number;
|
|
27
|
+
export declare class ContentStore {
|
|
28
|
+
#private;
|
|
29
|
+
static readonly OPTIMIZE_EVERY = 50;
|
|
30
|
+
static readonly FUZZY_CACHE_SIZE = 256;
|
|
31
|
+
constructor(dbPath?: string);
|
|
32
|
+
/** Delete this session's DB files. Call on process exit. */
|
|
33
|
+
cleanup(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Register a deny-policy checker. When set, #refreshStaleSources
|
|
36
|
+
* calls it before re-reading any file_path during auto-refresh.
|
|
37
|
+
* Returning `true` causes the source to be skipped (kept in cache,
|
|
38
|
+
* not re-indexed). server.ts wires this to the Read deny patterns.
|
|
39
|
+
*/
|
|
40
|
+
setDenyChecker(fn: ((filePath: string) => boolean) | undefined): void;
|
|
41
|
+
index(options: {
|
|
42
|
+
content?: string;
|
|
43
|
+
path?: string;
|
|
44
|
+
source?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional FK metadata recorded on each indexed chunk so per-session
|
|
47
|
+
* honest-savings stats can join chunks → session_events. When omitted,
|
|
48
|
+
* chunks fall back to empty-string columns (legacy behaviour).
|
|
49
|
+
*/
|
|
50
|
+
attribution?: {
|
|
51
|
+
sessionId?: string;
|
|
52
|
+
eventId?: string;
|
|
53
|
+
};
|
|
54
|
+
}): IndexResult;
|
|
55
|
+
/**
|
|
56
|
+
* Index every file under a directory by walking it with `walkDirectory` and
|
|
57
|
+
* delegating each discovered file to `this.index({ path })`. The per-file
|
|
58
|
+
* `openSync + fstatSync.isFile()` security gate at line ~845 stays active
|
|
59
|
+
* for every file — directory support never bypasses the TOCTOU defense
|
|
60
|
+
* from #442 round-3.
|
|
61
|
+
*
|
|
62
|
+
* Reported by @matiasduartee in #687.
|
|
63
|
+
*/
|
|
64
|
+
indexDirectory(opts: {
|
|
65
|
+
path: string;
|
|
66
|
+
source?: string;
|
|
67
|
+
attribution?: {
|
|
68
|
+
sessionId?: string;
|
|
69
|
+
eventId?: string;
|
|
70
|
+
};
|
|
71
|
+
/** Optional per-file deny check — runs INSIDE the walk loop so a denied
|
|
72
|
+
* file does not even open a fd. Returns true to deny. */
|
|
73
|
+
perFileDeny?: (absPath: string) => boolean;
|
|
74
|
+
} & WalkOptions): {
|
|
75
|
+
filesIndexed: number;
|
|
76
|
+
totalChunks: number;
|
|
77
|
+
capped: boolean;
|
|
78
|
+
totalSeen: number;
|
|
79
|
+
denied: number;
|
|
80
|
+
failed: number;
|
|
81
|
+
label: string;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Index plain-text output (logs, build output, test results) by splitting
|
|
85
|
+
* into fixed-size line groups. Unlike markdown indexing, this does not
|
|
86
|
+
* look for headings — it chunks by line count with overlap.
|
|
87
|
+
*/
|
|
88
|
+
indexPlainText(content: string, source: string, linesPerChunk?: number, attribution?: {
|
|
89
|
+
sessionId?: string;
|
|
90
|
+
eventId?: string;
|
|
91
|
+
}, maxChunkBytes?: number): IndexResult;
|
|
92
|
+
/**
|
|
93
|
+
* Index JSON content by walking the object tree and using key paths
|
|
94
|
+
* as chunk titles (analogous to heading hierarchy in markdown). Objects
|
|
95
|
+
* recurse by key; arrays batch items by size.
|
|
96
|
+
*
|
|
97
|
+
* Falls back to `indexPlainText` if the content is not valid JSON.
|
|
98
|
+
*/
|
|
99
|
+
indexJSON(content: string, source: string, maxChunkBytes?: number, attribution?: {
|
|
100
|
+
sessionId?: string;
|
|
101
|
+
eventId?: string;
|
|
102
|
+
}): IndexResult;
|
|
103
|
+
search(query: string, limit?: number, source?: string, mode?: "AND" | "OR", contentType?: "code" | "prose", sourceMatchMode?: SourceMatchMode): SearchResult[];
|
|
104
|
+
searchTrigram(query: string, limit?: number, source?: string, mode?: "AND" | "OR", contentType?: "code" | "prose", sourceMatchMode?: SourceMatchMode): SearchResult[];
|
|
105
|
+
fuzzyCorrect(query: string): string | null;
|
|
106
|
+
searchWithFallback(query: string, limit?: number, source?: string, contentType?: "code" | "prose", sourceMatchMode?: SourceMatchMode, sessionIdAllowSet?: Set<string>): SearchResult[];
|
|
107
|
+
/** Number of sources auto-refreshed in the last searchWithFallback call. */
|
|
108
|
+
lastRefreshCount: number;
|
|
109
|
+
getSourceMeta(label: string): {
|
|
110
|
+
label: string;
|
|
111
|
+
chunkCount: number;
|
|
112
|
+
codeChunkCount: number;
|
|
113
|
+
indexedAt: string;
|
|
114
|
+
filePath: string | null;
|
|
115
|
+
contentHash: string | null;
|
|
116
|
+
} | null;
|
|
117
|
+
listSources(): Array<{
|
|
118
|
+
label: string;
|
|
119
|
+
chunkCount: number;
|
|
120
|
+
}>;
|
|
121
|
+
/**
|
|
122
|
+
* Aggregate snapshot of the persistent content store. Returns total
|
|
123
|
+
* chunk count, source count, and the most recent indexed_at timestamp.
|
|
124
|
+
* Used by ctx_stats so callers can see observability state in the same
|
|
125
|
+
* round trip instead of inferring it from snapshot diffs.
|
|
126
|
+
*/
|
|
127
|
+
getIndexState(): {
|
|
128
|
+
totalChunks: number;
|
|
129
|
+
totalSources: number;
|
|
130
|
+
lastIndexedAt?: string;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Get all chunks for a given source by ID — bypasses FTS5 MATCH entirely.
|
|
134
|
+
* Use this for inventory/listing where you need all sections, not search.
|
|
135
|
+
*/
|
|
136
|
+
getChunksBySource(sourceId: number): SearchResult[];
|
|
137
|
+
getDistinctiveTerms(sourceId: number, maxTerms?: number): string[];
|
|
138
|
+
getStats(): StoreStats;
|
|
139
|
+
/**
|
|
140
|
+
* Delete sources (and their chunks) older than maxAgeDays.
|
|
141
|
+
* Returns count of deleted sources.
|
|
142
|
+
*/
|
|
143
|
+
cleanupStaleSources(maxAgeDays: number): number;
|
|
144
|
+
/** Get DB file size in bytes. */
|
|
145
|
+
getDBSizeBytes(): number;
|
|
146
|
+
close(): void;
|
|
147
|
+
}
|