@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,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* truncate — Pure string truncation and escaping utilities for context-mode.
|
|
3
|
+
*
|
|
4
|
+
* These helpers are used by the core ContentStore (chunking) and
|
|
5
|
+
* SessionDB (snapshot building). They are extracted here so any
|
|
6
|
+
* consumer can import them without pulling in the full store or executor.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Return a prefix of `str` no longer than `maxChars` UTF-16 code units, with
|
|
10
|
+
* the cut backed off by one unit if it would split a surrogate pair. Mirrors
|
|
11
|
+
* the surrogate-safety semantics of the internal `byteSafePrefix`, but uses
|
|
12
|
+
* a character budget rather than a byte budget.
|
|
13
|
+
*
|
|
14
|
+
* Use this instead of bare `str.slice(0, n)` whenever the result is later
|
|
15
|
+
* JSON-encoded for an RFC 8259-strict consumer (e.g. tool_result payloads
|
|
16
|
+
* sent back to the host LLM API). `JSON.stringify` will emit a lone high
|
|
17
|
+
* surrogate as a literal `\uD8xx` escape, which is invalid JSON.
|
|
18
|
+
*
|
|
19
|
+
* @param str - Input string.
|
|
20
|
+
* @param maxChars - Hard cap in UTF-16 code units.
|
|
21
|
+
*/
|
|
22
|
+
export declare function charSafePrefix(str: string, maxChars: number): string;
|
|
23
|
+
/**
|
|
24
|
+
* Serialize a value to JSON, then truncate the result to `maxBytes` bytes.
|
|
25
|
+
* If truncation occurs, the string is cut at a UTF-8-safe boundary and
|
|
26
|
+
* "... [truncated]" is appended. The result is NOT guaranteed to be valid
|
|
27
|
+
* JSON after truncation — it is suitable only for display/logging.
|
|
28
|
+
*
|
|
29
|
+
* The returned string is always <= `maxBytes` bytes. When `maxBytes` is
|
|
30
|
+
* smaller than the marker, the marker itself is byte-safely truncated.
|
|
31
|
+
*
|
|
32
|
+
* @param value - Any JSON-serializable value.
|
|
33
|
+
* @param maxBytes - Maximum byte length of the returned string.
|
|
34
|
+
* @param indent - JSON indentation spaces (default 2). Pass 0 for compact.
|
|
35
|
+
*/
|
|
36
|
+
export declare function truncateJSON(value: unknown, maxBytes: number, indent?: number): string;
|
|
37
|
+
/**
|
|
38
|
+
* Escape a string for safe embedding in an XML or HTML attribute or text node.
|
|
39
|
+
* Replaces the five XML-reserved characters: `&`, `<`, `>`, `"`, `'`.
|
|
40
|
+
*
|
|
41
|
+
* Used by the resume snapshot template builder to embed user content in
|
|
42
|
+
* `<tool_response>` and `<user_message>` XML tags without breaking the
|
|
43
|
+
* structured prompt format.
|
|
44
|
+
*/
|
|
45
|
+
export declare function escapeXML(str: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Return `str` unchanged if it fits within `maxBytes`, otherwise return a
|
|
48
|
+
* byte-safe slice with an ellipsis appended. Useful for single-value fields
|
|
49
|
+
* (e.g., tool response strings) where head+tail splitting is not needed.
|
|
50
|
+
*
|
|
51
|
+
* The returned string is always <= `maxBytes` bytes. When `maxBytes` is
|
|
52
|
+
* smaller than the ellipsis marker, the marker itself is byte-safely truncated.
|
|
53
|
+
*
|
|
54
|
+
* @param str - Input string.
|
|
55
|
+
* @param maxBytes - Hard byte cap.
|
|
56
|
+
*/
|
|
57
|
+
export declare function capBytes(str: string, maxBytes: number): string;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* truncate — Pure string truncation and escaping utilities for context-mode.
|
|
3
|
+
*
|
|
4
|
+
* These helpers are used by the core ContentStore (chunking) and
|
|
5
|
+
* SessionDB (snapshot building). They are extracted here so any
|
|
6
|
+
* consumer can import them without pulling in the full store or executor.
|
|
7
|
+
*/
|
|
8
|
+
// ─────────────────────────────────────────────────────────
|
|
9
|
+
// Internal: byte-safe prefix
|
|
10
|
+
// ─────────────────────────────────────────────────────────
|
|
11
|
+
/**
|
|
12
|
+
* Return the longest character-prefix of `str` whose UTF-8 encoding is at
|
|
13
|
+
* most `maxBytes` bytes. Uses binary search to avoid O(n²) scanning. Returns
|
|
14
|
+
* "" when `maxBytes` is <= 0 so callers never exceed their budget.
|
|
15
|
+
*
|
|
16
|
+
* Guards against splitting a UTF-16 surrogate pair: if the prefix would end
|
|
17
|
+
* on a lone high surrogate, back off one code unit so the result round-trips
|
|
18
|
+
* through UTF-8 without producing a U+FFFD replacement character.
|
|
19
|
+
*/
|
|
20
|
+
function byteSafePrefix(str, maxBytes) {
|
|
21
|
+
if (maxBytes <= 0)
|
|
22
|
+
return "";
|
|
23
|
+
if (Buffer.byteLength(str) <= maxBytes)
|
|
24
|
+
return str;
|
|
25
|
+
let lo = 0;
|
|
26
|
+
let hi = str.length;
|
|
27
|
+
while (lo < hi) {
|
|
28
|
+
const mid = (lo + hi + 1) >> 1;
|
|
29
|
+
if (Buffer.byteLength(str.slice(0, mid)) <= maxBytes) {
|
|
30
|
+
lo = mid;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
hi = mid - 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// If we landed between a high and low surrogate, back off so the prefix
|
|
37
|
+
// ends on a valid code point boundary.
|
|
38
|
+
if (lo > 0) {
|
|
39
|
+
const code = str.charCodeAt(lo - 1);
|
|
40
|
+
if (code >= 0xd800 && code <= 0xdbff)
|
|
41
|
+
lo -= 1;
|
|
42
|
+
}
|
|
43
|
+
return str.slice(0, lo);
|
|
44
|
+
}
|
|
45
|
+
// ─────────────────────────────────────────────────────────
|
|
46
|
+
// Char-count prefix (UTF-16 surrogate-safe)
|
|
47
|
+
// ─────────────────────────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* Return a prefix of `str` no longer than `maxChars` UTF-16 code units, with
|
|
50
|
+
* the cut backed off by one unit if it would split a surrogate pair. Mirrors
|
|
51
|
+
* the surrogate-safety semantics of the internal `byteSafePrefix`, but uses
|
|
52
|
+
* a character budget rather than a byte budget.
|
|
53
|
+
*
|
|
54
|
+
* Use this instead of bare `str.slice(0, n)` whenever the result is later
|
|
55
|
+
* JSON-encoded for an RFC 8259-strict consumer (e.g. tool_result payloads
|
|
56
|
+
* sent back to the host LLM API). `JSON.stringify` will emit a lone high
|
|
57
|
+
* surrogate as a literal `\uD8xx` escape, which is invalid JSON.
|
|
58
|
+
*
|
|
59
|
+
* @param str - Input string.
|
|
60
|
+
* @param maxChars - Hard cap in UTF-16 code units.
|
|
61
|
+
*/
|
|
62
|
+
export function charSafePrefix(str, maxChars) {
|
|
63
|
+
if (maxChars <= 0)
|
|
64
|
+
return "";
|
|
65
|
+
if (str.length <= maxChars)
|
|
66
|
+
return str;
|
|
67
|
+
let end = maxChars;
|
|
68
|
+
const code = str.charCodeAt(end - 1);
|
|
69
|
+
if (code >= 0xd800 && code <= 0xdbff)
|
|
70
|
+
end -= 1;
|
|
71
|
+
return str.slice(0, end);
|
|
72
|
+
}
|
|
73
|
+
// ─────────────────────────────────────────────────────────
|
|
74
|
+
// JSON truncation
|
|
75
|
+
// ─────────────────────────────────────────────────────────
|
|
76
|
+
/**
|
|
77
|
+
* Serialize a value to JSON, then truncate the result to `maxBytes` bytes.
|
|
78
|
+
* If truncation occurs, the string is cut at a UTF-8-safe boundary and
|
|
79
|
+
* "... [truncated]" is appended. The result is NOT guaranteed to be valid
|
|
80
|
+
* JSON after truncation — it is suitable only for display/logging.
|
|
81
|
+
*
|
|
82
|
+
* The returned string is always <= `maxBytes` bytes. When `maxBytes` is
|
|
83
|
+
* smaller than the marker, the marker itself is byte-safely truncated.
|
|
84
|
+
*
|
|
85
|
+
* @param value - Any JSON-serializable value.
|
|
86
|
+
* @param maxBytes - Maximum byte length of the returned string.
|
|
87
|
+
* @param indent - JSON indentation spaces (default 2). Pass 0 for compact.
|
|
88
|
+
*/
|
|
89
|
+
export function truncateJSON(value, maxBytes, indent = 2) {
|
|
90
|
+
const serialized = JSON.stringify(value, null, indent) ?? "null";
|
|
91
|
+
if (Buffer.byteLength(serialized) <= maxBytes)
|
|
92
|
+
return serialized;
|
|
93
|
+
const marker = "... [truncated]";
|
|
94
|
+
const markerBytes = Buffer.byteLength(marker);
|
|
95
|
+
// Degenerate budget: can't fit serialized content + marker. Fit as much of
|
|
96
|
+
// the marker as we can so the return still honors `maxBytes`.
|
|
97
|
+
if (maxBytes <= markerBytes)
|
|
98
|
+
return byteSafePrefix(marker, maxBytes);
|
|
99
|
+
return byteSafePrefix(serialized, maxBytes - markerBytes) + marker;
|
|
100
|
+
}
|
|
101
|
+
// ─────────────────────────────────────────────────────────
|
|
102
|
+
// XML / HTML escaping
|
|
103
|
+
// ─────────────────────────────────────────────────────────
|
|
104
|
+
/**
|
|
105
|
+
* Escape a string for safe embedding in an XML or HTML attribute or text node.
|
|
106
|
+
* Replaces the five XML-reserved characters: `&`, `<`, `>`, `"`, `'`.
|
|
107
|
+
*
|
|
108
|
+
* Used by the resume snapshot template builder to embed user content in
|
|
109
|
+
* `<tool_response>` and `<user_message>` XML tags without breaking the
|
|
110
|
+
* structured prompt format.
|
|
111
|
+
*/
|
|
112
|
+
export function escapeXML(str) {
|
|
113
|
+
return str
|
|
114
|
+
.replace(/&/g, "&")
|
|
115
|
+
.replace(/</g, "<")
|
|
116
|
+
.replace(/>/g, ">")
|
|
117
|
+
.replace(/"/g, """)
|
|
118
|
+
.replace(/'/g, "'");
|
|
119
|
+
}
|
|
120
|
+
// ─────────────────────────────────────────────────────────
|
|
121
|
+
// maxBytes guard
|
|
122
|
+
// ─────────────────────────────────────────────────────────
|
|
123
|
+
/**
|
|
124
|
+
* Return `str` unchanged if it fits within `maxBytes`, otherwise return a
|
|
125
|
+
* byte-safe slice with an ellipsis appended. Useful for single-value fields
|
|
126
|
+
* (e.g., tool response strings) where head+tail splitting is not needed.
|
|
127
|
+
*
|
|
128
|
+
* The returned string is always <= `maxBytes` bytes. When `maxBytes` is
|
|
129
|
+
* smaller than the ellipsis marker, the marker itself is byte-safely truncated.
|
|
130
|
+
*
|
|
131
|
+
* @param str - Input string.
|
|
132
|
+
* @param maxBytes - Hard byte cap.
|
|
133
|
+
*/
|
|
134
|
+
export function capBytes(str, maxBytes) {
|
|
135
|
+
if (Buffer.byteLength(str) <= maxBytes)
|
|
136
|
+
return str;
|
|
137
|
+
const marker = "...";
|
|
138
|
+
const markerBytes = Buffer.byteLength(marker);
|
|
139
|
+
if (maxBytes <= markerBytes)
|
|
140
|
+
return byteSafePrefix(marker, maxBytes);
|
|
141
|
+
return byteSafePrefix(str, maxBytes - markerBytes) + marker;
|
|
142
|
+
}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* types — Shared type definitions for context-mode packages.
|
|
3
|
+
*
|
|
4
|
+
* Contains interfaces that are genuinely shared between the core (ContentStore,
|
|
5
|
+
* PolyglotExecutor) and the session domain (SessionDB, event extraction).
|
|
6
|
+
* Import from "./types.js".
|
|
7
|
+
*/
|
|
8
|
+
/** Tool call representation used during event extraction from Claude messages. */
|
|
9
|
+
export interface ToolCall {
|
|
10
|
+
toolName: string;
|
|
11
|
+
toolInput: Record<string, unknown>;
|
|
12
|
+
toolResponse?: string;
|
|
13
|
+
isError?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** User message representation used during event extraction. */
|
|
16
|
+
export interface UserMessage {
|
|
17
|
+
content: string;
|
|
18
|
+
timestamp?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Session event as stored in SessionDB.
|
|
22
|
+
* Each event captures a discrete unit of session activity (tool use, user
|
|
23
|
+
* message, assistant response summary, etc.) for later resume-snapshot
|
|
24
|
+
* reconstruction.
|
|
25
|
+
*/
|
|
26
|
+
export interface SessionEvent {
|
|
27
|
+
type: string;
|
|
28
|
+
category: string;
|
|
29
|
+
data: string;
|
|
30
|
+
priority: number;
|
|
31
|
+
data_hash: string;
|
|
32
|
+
/**
|
|
33
|
+
* Best-effort project attribution for this event.
|
|
34
|
+
* Empty string means unattributed/unknown.
|
|
35
|
+
*/
|
|
36
|
+
project_dir?: string;
|
|
37
|
+
attribution_source?: string;
|
|
38
|
+
/** 0..1 confidence score for project attribution. */
|
|
39
|
+
attribution_confidence?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Result returned by PolyglotExecutor after running a code snippet.
|
|
43
|
+
* Shared here so SessionDB can record execution outcomes without importing
|
|
44
|
+
* the full executor module.
|
|
45
|
+
*/
|
|
46
|
+
export interface ExecResult {
|
|
47
|
+
stdout: string;
|
|
48
|
+
stderr: string;
|
|
49
|
+
exitCode: number;
|
|
50
|
+
timedOut: boolean;
|
|
51
|
+
/** Process was detached and continues running in the background. */
|
|
52
|
+
backgrounded?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Result returned after indexing content into the knowledge base.
|
|
56
|
+
* Shared so session tooling can record what was indexed without importing
|
|
57
|
+
* ContentStore.
|
|
58
|
+
*/
|
|
59
|
+
export interface IndexResult {
|
|
60
|
+
sourceId: number;
|
|
61
|
+
label: string;
|
|
62
|
+
totalChunks: number;
|
|
63
|
+
codeChunks: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A single search result returned from FTS5 BM25-ranked lookup.
|
|
67
|
+
* Shared for consumers that display or log results outside of ContentStore.
|
|
68
|
+
*/
|
|
69
|
+
export interface SearchResult {
|
|
70
|
+
title: string;
|
|
71
|
+
content: string;
|
|
72
|
+
source: string;
|
|
73
|
+
rank: number;
|
|
74
|
+
contentType: "code" | "prose";
|
|
75
|
+
matchLayer?: "porter" | "trigram" | "fuzzy" | "rrf" | "rrf-fuzzy";
|
|
76
|
+
highlighted?: string;
|
|
77
|
+
timestamp?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Session-id attribution copied from the `chunks.session_id` column
|
|
80
|
+
* (legacy unattributed chunks carry an empty string). Used by the
|
|
81
|
+
* `ctx_search` per-project filter (#737) to scope shared-DB results
|
|
82
|
+
* to the current project via the 2-step IN-clause strategy.
|
|
83
|
+
*/
|
|
84
|
+
sessionId?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Aggregate statistics for a ContentStore instance.
|
|
88
|
+
*/
|
|
89
|
+
export interface StoreStats {
|
|
90
|
+
sources: number;
|
|
91
|
+
chunks: number;
|
|
92
|
+
codeChunks: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Structured representation of a session resume snapshot, suitable for
|
|
96
|
+
* injecting into a new conversation as context. Generated from stored
|
|
97
|
+
* SessionEvents by the snapshot builder.
|
|
98
|
+
*/
|
|
99
|
+
export interface ResumeSnapshot {
|
|
100
|
+
/** ISO-8601 timestamp of when the snapshot was generated. */
|
|
101
|
+
generatedAt: string;
|
|
102
|
+
/** Human-readable summary of the session to this point. */
|
|
103
|
+
summary: string;
|
|
104
|
+
/** Ordered list of events selected for the snapshot (priority-filtered). */
|
|
105
|
+
events: SessionEvent[];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Priority levels for SessionEvent records. Higher numbers are more important
|
|
109
|
+
* and are retained when the snapshot budget is tight.
|
|
110
|
+
*/
|
|
111
|
+
export declare const EventPriority: {
|
|
112
|
+
readonly LOW: 1;
|
|
113
|
+
readonly NORMAL: 2;
|
|
114
|
+
readonly HIGH: 3;
|
|
115
|
+
readonly CRITICAL: 4;
|
|
116
|
+
};
|
|
117
|
+
export type EventPriorityLevel = (typeof EventPriority)[keyof typeof EventPriority];
|
package/build/types.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* types — Shared type definitions for context-mode packages.
|
|
3
|
+
*
|
|
4
|
+
* Contains interfaces that are genuinely shared between the core (ContentStore,
|
|
5
|
+
* PolyglotExecutor) and the session domain (SessionDB, event extraction).
|
|
6
|
+
* Import from "./types.js".
|
|
7
|
+
*/
|
|
8
|
+
// ─────────────────────────────────────────────────────────
|
|
9
|
+
// Priority constants
|
|
10
|
+
// ─────────────────────────────────────────────────────────
|
|
11
|
+
/**
|
|
12
|
+
* Priority levels for SessionEvent records. Higher numbers are more important
|
|
13
|
+
* and are retained when the snapshot budget is tight.
|
|
14
|
+
*/
|
|
15
|
+
export const EventPriority = {
|
|
16
|
+
LOW: 1,
|
|
17
|
+
NORMAL: 2,
|
|
18
|
+
HIGH: 3,
|
|
19
|
+
CRITICAL: 4,
|
|
20
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare function resolveClaudeConfigDir(env?: NodeJS.ProcessEnv): string;
|
|
2
|
+
/** Resolve the global settings.json path, honoring CLAUDE_CONFIG_DIR. */
|
|
3
|
+
export declare function resolveClaudeGlobalSettingsPath(env?: NodeJS.ProcessEnv): string;
|
|
4
|
+
/**
|
|
5
|
+
* Issue #451 round-3: cross-adapter deny-policy parity.
|
|
6
|
+
*
|
|
7
|
+
* `resolveClaudeGlobalSettingsPath` hardcodes the `.claude` segment, so
|
|
8
|
+
* non-Claude adapters (cursor, codex, qwen-code, gemini-cli, jetbrains-copilot,
|
|
9
|
+
* vscode-copilot, etc.) never had their global settings consulted by the
|
|
10
|
+
* security policy reader. This helper returns the union of:
|
|
11
|
+
*
|
|
12
|
+
* 1. The currently-detected adapter's home-rooted settings.json (when the
|
|
13
|
+
* adapter is non-claude — claude is already covered by entry 2).
|
|
14
|
+
* 2. The claude global settings.json (always — defense in depth).
|
|
15
|
+
*
|
|
16
|
+
* Static import of `../adapters/detect.js` is safe — detect.ts only imports
|
|
17
|
+
* `node:` builtins, `./types.js` (type-only), and `./client-map.js` (pure
|
|
18
|
+
* data). It does NOT import claude-config back, so no cycle.
|
|
19
|
+
*
|
|
20
|
+
* History: this used `createRequire(import.meta.url).resolve(...)` to lazy-
|
|
21
|
+
* load detect at call time. That pattern requires `require(esm)`, which is
|
|
22
|
+
* flag-gated on Node 22.x before 22.12 (`--experimental-require-module`).
|
|
23
|
+
* CI run 25877550371 on Node 22.5 silently failed every detect.* call —
|
|
24
|
+
* the catch block ate the error and every cross-adapter deny-policy test
|
|
25
|
+
* returned an empty policy list. Static import sidesteps the require(esm)
|
|
26
|
+
* gate entirely, so the same code works on every supported Node version
|
|
27
|
+
* (20.x, 22.5, 22.12+, 24+) without needing the experimental flag.
|
|
28
|
+
*
|
|
29
|
+
* The returned array is deduplicated and order-stable: adapter-specific path
|
|
30
|
+
* first (most specific), claude global second (fallback).
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveAdapterGlobalSettingsPaths(env?: NodeJS.ProcessEnv): string[];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code config directory resolver — single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* Issue #460 follow-up: every Claude-aware reader (adapters, security policy
|
|
5
|
+
* loader, hook helpers) MUST agree on where global settings live. Hardcoding
|
|
6
|
+
* `~/.claude` in any one reader silently breaks `CLAUDE_CONFIG_DIR` for that
|
|
7
|
+
* code path, producing policy drift that is invisible until a user sets the
|
|
8
|
+
* env var and watches their settings get ignored.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the contract of `hooks/session-helpers.mjs::resolveConfigDir` and
|
|
11
|
+
* `ClaudeCodeAdapter.getConfigDir`:
|
|
12
|
+
* - env unset, empty string, or whitespace-only → ~/.claude
|
|
13
|
+
* - env starts with `~`, `~/`, or `~\` → expanded against homedir()
|
|
14
|
+
* - otherwise → resolved to absolute (relative paths anchor to cwd)
|
|
15
|
+
*
|
|
16
|
+
* Whitespace guard: shells that quote-pad the env value (`CLAUDE_CONFIG_DIR=" "`)
|
|
17
|
+
* would otherwise resolve to `cwd/<spaces>` — silently writing settings into
|
|
18
|
+
* the project tree. Trim before the truthy check so quote-padding falls back
|
|
19
|
+
* to `~/.claude` like a sane default.
|
|
20
|
+
*
|
|
21
|
+
* Cross-platform note: tilde regex strips a single leading `/` OR `\` so
|
|
22
|
+
* `~\Users\foo` works on Windows. `path.resolve` handles drive-letter joining.
|
|
23
|
+
*/
|
|
24
|
+
import { resolve } from "node:path";
|
|
25
|
+
import { homedir } from "node:os";
|
|
26
|
+
import { detectPlatform, getSessionDirSegments } from "../adapters/detect.js";
|
|
27
|
+
export function resolveClaudeConfigDir(env = process.env) {
|
|
28
|
+
const envVal = env.CLAUDE_CONFIG_DIR;
|
|
29
|
+
if (envVal && envVal.trim() !== "") {
|
|
30
|
+
if (envVal.startsWith("~")) {
|
|
31
|
+
return resolve(homedir(), envVal.replace(/^~[/\\]?/, ""));
|
|
32
|
+
}
|
|
33
|
+
return resolve(envVal);
|
|
34
|
+
}
|
|
35
|
+
return resolve(homedir(), ".claude");
|
|
36
|
+
}
|
|
37
|
+
/** Resolve the global settings.json path, honoring CLAUDE_CONFIG_DIR. */
|
|
38
|
+
export function resolveClaudeGlobalSettingsPath(env = process.env) {
|
|
39
|
+
return resolve(resolveClaudeConfigDir(env), "settings.json");
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Issue #451 round-3: cross-adapter deny-policy parity.
|
|
43
|
+
*
|
|
44
|
+
* `resolveClaudeGlobalSettingsPath` hardcodes the `.claude` segment, so
|
|
45
|
+
* non-Claude adapters (cursor, codex, qwen-code, gemini-cli, jetbrains-copilot,
|
|
46
|
+
* vscode-copilot, etc.) never had their global settings consulted by the
|
|
47
|
+
* security policy reader. This helper returns the union of:
|
|
48
|
+
*
|
|
49
|
+
* 1. The currently-detected adapter's home-rooted settings.json (when the
|
|
50
|
+
* adapter is non-claude — claude is already covered by entry 2).
|
|
51
|
+
* 2. The claude global settings.json (always — defense in depth).
|
|
52
|
+
*
|
|
53
|
+
* Static import of `../adapters/detect.js` is safe — detect.ts only imports
|
|
54
|
+
* `node:` builtins, `./types.js` (type-only), and `./client-map.js` (pure
|
|
55
|
+
* data). It does NOT import claude-config back, so no cycle.
|
|
56
|
+
*
|
|
57
|
+
* History: this used `createRequire(import.meta.url).resolve(...)` to lazy-
|
|
58
|
+
* load detect at call time. That pattern requires `require(esm)`, which is
|
|
59
|
+
* flag-gated on Node 22.x before 22.12 (`--experimental-require-module`).
|
|
60
|
+
* CI run 25877550371 on Node 22.5 silently failed every detect.* call —
|
|
61
|
+
* the catch block ate the error and every cross-adapter deny-policy test
|
|
62
|
+
* returned an empty policy list. Static import sidesteps the require(esm)
|
|
63
|
+
* gate entirely, so the same code works on every supported Node version
|
|
64
|
+
* (20.x, 22.5, 22.12+, 24+) without needing the experimental flag.
|
|
65
|
+
*
|
|
66
|
+
* The returned array is deduplicated and order-stable: adapter-specific path
|
|
67
|
+
* first (most specific), claude global second (fallback).
|
|
68
|
+
*/
|
|
69
|
+
export function resolveAdapterGlobalSettingsPaths(env = process.env) {
|
|
70
|
+
const paths = [];
|
|
71
|
+
const detected = detectPlatform();
|
|
72
|
+
if (detected.platform !== "claude-code") {
|
|
73
|
+
const segments = getSessionDirSegments(detected.platform);
|
|
74
|
+
if (segments && segments.length > 0) {
|
|
75
|
+
paths.push(resolve(homedir(), ...segments, "settings.json"));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Always include claude global as fallback (defense in depth).
|
|
79
|
+
const claudePath = resolveClaudeGlobalSettingsPath(env);
|
|
80
|
+
if (!paths.includes(claudePath)) {
|
|
81
|
+
paths.push(claudePath);
|
|
82
|
+
}
|
|
83
|
+
return paths;
|
|
84
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type HookAdapter } from "../adapters/types.js";
|
|
2
|
+
export declare function getCommandsFromHookEntry(entry: unknown): string[];
|
|
3
|
+
/**
|
|
4
|
+
* Extract the hook script path from a hook command string.
|
|
5
|
+
*
|
|
6
|
+
* Post Algo-D2 this is a thin wrapper around `parseNodeCommand` with a
|
|
7
|
+
* single legacy fallback retained for stale-entry cleanup
|
|
8
|
+
* (`configureAllHooks` walks pre-v1.0.124 settings.json shapes that
|
|
9
|
+
* predate `buildNodeCommand`). The legacy branches are deliberately
|
|
10
|
+
* narrow:
|
|
11
|
+
*
|
|
12
|
+
* 1) Canonical: `"<nodePath>" "<scriptPath>.mjs"` — `parseNodeCommand`
|
|
13
|
+
* handles this; round-trips with `buildNodeCommand`.
|
|
14
|
+
* 2) Legacy quoted: `node "<scriptPath>.mjs"` — emitted by claude-code
|
|
15
|
+
* pre-D3. The script segment is fully quoted, no whitespace
|
|
16
|
+
* ambiguity.
|
|
17
|
+
* 3) Legacy unquoted: `node <scriptPath>.mjs` — only when the entire
|
|
18
|
+
* command is whitespace-safe (exactly two whitespace-separated
|
|
19
|
+
* tokens). The #548 wire shape — `node C:/Users/High Ground …` —
|
|
20
|
+
* contains internal whitespace so this branch refuses it. Returns
|
|
21
|
+
* `null` instead of grabbing the tail after the last whitespace.
|
|
22
|
+
*
|
|
23
|
+
* Anything else returns `null`, letting the doctor (Algo-D1) fall
|
|
24
|
+
* through to direct `existsSync` instead of trusting the regex.
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractHookScriptPath(command: string): string | null;
|
|
27
|
+
export declare function getHookScriptPaths(adapter: HookAdapter, pluginRoot: string): string[];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { parseNodeCommand } from "../adapters/types.js";
|
|
2
|
+
export function getCommandsFromHookEntry(entry) {
|
|
3
|
+
const commands = [];
|
|
4
|
+
if (entry && typeof entry === "object") {
|
|
5
|
+
const command = entry.command;
|
|
6
|
+
if (typeof command === "string")
|
|
7
|
+
commands.push(command);
|
|
8
|
+
const hooks = entry.hooks;
|
|
9
|
+
if (Array.isArray(hooks)) {
|
|
10
|
+
for (const hook of hooks) {
|
|
11
|
+
if (hook && typeof hook === "object") {
|
|
12
|
+
const nestedCommand = hook.command;
|
|
13
|
+
if (typeof nestedCommand === "string")
|
|
14
|
+
commands.push(nestedCommand);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return commands;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Extract the hook script path from a hook command string.
|
|
23
|
+
*
|
|
24
|
+
* Post Algo-D2 this is a thin wrapper around `parseNodeCommand` with a
|
|
25
|
+
* single legacy fallback retained for stale-entry cleanup
|
|
26
|
+
* (`configureAllHooks` walks pre-v1.0.124 settings.json shapes that
|
|
27
|
+
* predate `buildNodeCommand`). The legacy branches are deliberately
|
|
28
|
+
* narrow:
|
|
29
|
+
*
|
|
30
|
+
* 1) Canonical: `"<nodePath>" "<scriptPath>.mjs"` — `parseNodeCommand`
|
|
31
|
+
* handles this; round-trips with `buildNodeCommand`.
|
|
32
|
+
* 2) Legacy quoted: `node "<scriptPath>.mjs"` — emitted by claude-code
|
|
33
|
+
* pre-D3. The script segment is fully quoted, no whitespace
|
|
34
|
+
* ambiguity.
|
|
35
|
+
* 3) Legacy unquoted: `node <scriptPath>.mjs` — only when the entire
|
|
36
|
+
* command is whitespace-safe (exactly two whitespace-separated
|
|
37
|
+
* tokens). The #548 wire shape — `node C:/Users/High Ground …` —
|
|
38
|
+
* contains internal whitespace so this branch refuses it. Returns
|
|
39
|
+
* `null` instead of grabbing the tail after the last whitespace.
|
|
40
|
+
*
|
|
41
|
+
* Anything else returns `null`, letting the doctor (Algo-D1) fall
|
|
42
|
+
* through to direct `existsSync` instead of trusting the regex.
|
|
43
|
+
*/
|
|
44
|
+
export function extractHookScriptPath(command) {
|
|
45
|
+
const parsed = parseNodeCommand(command);
|
|
46
|
+
if (parsed) {
|
|
47
|
+
return parsed.scriptPath.endsWith(".mjs") ? parsed.scriptPath : null;
|
|
48
|
+
}
|
|
49
|
+
// Legacy quoted: `node "/path/with spaces/x.mjs"` (pre-D3 claude-code emit).
|
|
50
|
+
const legacyQuoted = command.match(/^\s*node\s+"([^"]+\.mjs)"\s*$/);
|
|
51
|
+
if (legacyQuoted)
|
|
52
|
+
return legacyQuoted[1];
|
|
53
|
+
// Legacy unquoted: `node /path/x.mjs` — refuses internal whitespace
|
|
54
|
+
// by anchoring both tokens. The #548 ambiguous shape has 3+ tokens
|
|
55
|
+
// (spaces in the path) and falls through to `null`.
|
|
56
|
+
const legacyBare = command.match(/^\s*node\s+(\S+\.mjs)\s*$/);
|
|
57
|
+
if (legacyBare)
|
|
58
|
+
return legacyBare[1];
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
export function getHookScriptPaths(adapter, pluginRoot) {
|
|
62
|
+
const paths = new Set();
|
|
63
|
+
const hookConfig = adapter.generateHookConfig(pluginRoot);
|
|
64
|
+
for (const entries of Object.values(hookConfig)) {
|
|
65
|
+
if (!Array.isArray(entries))
|
|
66
|
+
continue;
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
for (const command of getCommandsFromHookEntry(entry)) {
|
|
69
|
+
const scriptPath = extractHookScriptPath(command);
|
|
70
|
+
if (scriptPath)
|
|
71
|
+
paths.add(scriptPath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return [...paths];
|
|
76
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* util/jsonc — string-aware JSONC comment + trailing-comma stripping and a
|
|
3
|
+
* tolerant parse. Several agent CLIs ship config files as JSONC (VS Code
|
|
4
|
+
* `mcp.json`, Zed `settings.json`), so a strict `JSON.parse` false-fails on a
|
|
5
|
+
* perfectly valid commented file. Use `parseJsonc` whenever reading a
|
|
6
|
+
* platform config we did not write ourselves.
|
|
7
|
+
*/
|
|
8
|
+
/** Strip `//` line + `/* */` block comments and trailing commas, string-aware. */
|
|
9
|
+
export declare function stripJsonComments(str: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Parse JSON or JSONC. Tries strict `JSON.parse` first (fast, exact), then a
|
|
12
|
+
* comment/trailing-comma-stripped parse. Returns `undefined` when both fail.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseJsonc<T = unknown>(raw: string): T | undefined;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* util/jsonc — string-aware JSONC comment + trailing-comma stripping and a
|
|
3
|
+
* tolerant parse. Several agent CLIs ship config files as JSONC (VS Code
|
|
4
|
+
* `mcp.json`, Zed `settings.json`), so a strict `JSON.parse` false-fails on a
|
|
5
|
+
* perfectly valid commented file. Use `parseJsonc` whenever reading a
|
|
6
|
+
* platform config we did not write ourselves.
|
|
7
|
+
*/
|
|
8
|
+
/** Strip `//` line + `/* */` block comments and trailing commas, string-aware. */
|
|
9
|
+
export function stripJsonComments(str) {
|
|
10
|
+
let out = "";
|
|
11
|
+
let inString = false;
|
|
12
|
+
let escaped = false;
|
|
13
|
+
let inBlockComment = false;
|
|
14
|
+
for (let i = 0; i < str.length; i++) {
|
|
15
|
+
const c = str[i];
|
|
16
|
+
const next = str[i + 1];
|
|
17
|
+
if (inBlockComment) {
|
|
18
|
+
if (c === "*" && next === "/") {
|
|
19
|
+
inBlockComment = false;
|
|
20
|
+
i++;
|
|
21
|
+
}
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (escaped) {
|
|
25
|
+
out += c;
|
|
26
|
+
escaped = false;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (c === "\\") {
|
|
30
|
+
out += c;
|
|
31
|
+
escaped = inString;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (c === '"') {
|
|
35
|
+
inString = !inString;
|
|
36
|
+
out += c;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!inString && c === "/" && next === "/") {
|
|
40
|
+
while (i < str.length && str[i] !== "\n")
|
|
41
|
+
i++;
|
|
42
|
+
if (i < str.length)
|
|
43
|
+
out += "\n";
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!inString && c === "/" && next === "*") {
|
|
47
|
+
inBlockComment = true;
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
out += c;
|
|
52
|
+
}
|
|
53
|
+
// Trailing-comma removal, string-aware. The scan above already removed
|
|
54
|
+
// comments, so this second pass over `out` only needs to track string state:
|
|
55
|
+
// a comma is "trailing" when the next significant char is `}` or `]`. Doing
|
|
56
|
+
// it here — instead of a post-hoc trailing-comma regex over the whole string —
|
|
57
|
+
// preserves commas inside string literals (e.g. "[1, ]"), which that regex
|
|
58
|
+
// silently corrupted to "[1 ]". See #787 review.
|
|
59
|
+
let result = "";
|
|
60
|
+
let inStr = false;
|
|
61
|
+
let esc = false;
|
|
62
|
+
for (let i = 0; i < out.length; i++) {
|
|
63
|
+
const c = out[i];
|
|
64
|
+
if (esc) {
|
|
65
|
+
result += c;
|
|
66
|
+
esc = false;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (c === "\\") {
|
|
70
|
+
result += c;
|
|
71
|
+
esc = inStr;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (c === '"') {
|
|
75
|
+
inStr = !inStr;
|
|
76
|
+
result += c;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!inStr && c === ",") {
|
|
80
|
+
let j = i + 1;
|
|
81
|
+
while (j < out.length && (out[j] === " " || out[j] === "\t" || out[j] === "\r" || out[j] === "\n"))
|
|
82
|
+
j++;
|
|
83
|
+
if (out[j] === "}" || out[j] === "]")
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
result += c;
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Parse JSON or JSONC. Tries strict `JSON.parse` first (fast, exact), then a
|
|
92
|
+
* comment/trailing-comma-stripped parse. Returns `undefined` when both fail.
|
|
93
|
+
*/
|
|
94
|
+
export function parseJsonc(raw) {
|
|
95
|
+
for (const candidate of [raw, stripJsonComments(raw)]) {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(candidate);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* try next */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|