context-mode 1.0.103 → 1.0.105

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.
Files changed (98) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  4. package/.openclaw-plugin/package.json +1 -1
  5. package/README.md +39 -7
  6. package/bin/statusline.mjs +321 -0
  7. package/build/adapters/antigravity/index.d.ts +6 -0
  8. package/build/adapters/antigravity/index.js +10 -0
  9. package/build/adapters/base.d.ts +23 -0
  10. package/build/adapters/base.js +29 -0
  11. package/build/adapters/codex/index.d.ts +10 -0
  12. package/build/adapters/codex/index.js +22 -4
  13. package/build/adapters/cursor/index.d.ts +7 -0
  14. package/build/adapters/cursor/index.js +11 -0
  15. package/build/adapters/detect.d.ts +12 -1
  16. package/build/adapters/detect.js +69 -7
  17. package/build/adapters/gemini-cli/index.d.ts +8 -1
  18. package/build/adapters/gemini-cli/index.js +19 -7
  19. package/build/adapters/jetbrains-copilot/index.d.ts +7 -0
  20. package/build/adapters/jetbrains-copilot/index.js +12 -0
  21. package/build/adapters/kiro/index.d.ts +8 -0
  22. package/build/adapters/kiro/index.js +12 -0
  23. package/build/adapters/openclaw/index.d.ts +17 -0
  24. package/build/adapters/openclaw/index.js +29 -4
  25. package/build/adapters/opencode/index.d.ts +8 -0
  26. package/build/adapters/opencode/index.js +18 -6
  27. package/build/adapters/qwen-code/index.d.ts +1 -0
  28. package/build/adapters/qwen-code/index.js +3 -0
  29. package/build/adapters/types.d.ts +33 -0
  30. package/build/adapters/vscode-copilot/index.d.ts +6 -0
  31. package/build/adapters/vscode-copilot/index.js +10 -0
  32. package/build/adapters/zed/index.d.ts +1 -0
  33. package/build/adapters/zed/index.js +3 -0
  34. package/build/cli.d.ts +15 -0
  35. package/build/cli.js +62 -16
  36. package/build/concurrency/runPool.d.ts +36 -0
  37. package/build/concurrency/runPool.js +51 -0
  38. package/build/executor.d.ts +11 -1
  39. package/build/executor.js +77 -21
  40. package/build/fetch-cache.d.ts +13 -0
  41. package/build/fetch-cache.js +15 -0
  42. package/build/lifecycle.d.ts +6 -2
  43. package/build/lifecycle.js +29 -2
  44. package/build/opencode-plugin.d.ts +23 -0
  45. package/build/opencode-plugin.js +80 -6
  46. package/build/routing-block.d.ts +8 -0
  47. package/build/routing-block.js +86 -0
  48. package/build/runtime.d.ts +1 -0
  49. package/build/runtime.js +54 -3
  50. package/build/search/auto-memory.d.ts +23 -10
  51. package/build/search/auto-memory.js +64 -26
  52. package/build/search/unified.d.ts +3 -0
  53. package/build/search/unified.js +2 -2
  54. package/build/server.d.ts +47 -0
  55. package/build/server.js +736 -188
  56. package/build/session/analytics.d.ts +49 -1
  57. package/build/session/analytics.js +278 -16
  58. package/build/session/db.d.ts +53 -8
  59. package/build/session/db.js +200 -19
  60. package/build/session/extract.js +124 -2
  61. package/build/tool-naming.d.ts +4 -0
  62. package/build/tool-naming.js +24 -0
  63. package/cli.bundle.mjs +208 -158
  64. package/configs/antigravity/GEMINI.md +11 -0
  65. package/configs/claude-code/CLAUDE.md +11 -0
  66. package/configs/codex/AGENTS.md +11 -0
  67. package/configs/cursor/context-mode.mdc +11 -0
  68. package/configs/gemini-cli/GEMINI.md +11 -0
  69. package/configs/jetbrains-copilot/copilot-instructions.md +3 -0
  70. package/configs/kilo/AGENTS.md +11 -0
  71. package/configs/kiro/KIRO.md +11 -0
  72. package/configs/openclaw/AGENTS.md +11 -0
  73. package/configs/opencode/AGENTS.md +11 -0
  74. package/configs/pi/AGENTS.md +11 -0
  75. package/configs/qwen-code/QWEN.md +11 -0
  76. package/configs/vscode-copilot/copilot-instructions.md +3 -0
  77. package/configs/zed/AGENTS.md +11 -0
  78. package/hooks/auto-injection.mjs +36 -10
  79. package/hooks/cache-heal-utils.mjs +231 -0
  80. package/hooks/codex/sessionstart.mjs +7 -4
  81. package/hooks/core/routing.mjs +8 -2
  82. package/hooks/cursor/sessionstart.mjs +7 -4
  83. package/hooks/formatters/claude-code.mjs +20 -0
  84. package/hooks/gemini-cli/sessionstart.mjs +7 -2
  85. package/hooks/jetbrains-copilot/sessionstart.mjs +7 -2
  86. package/hooks/normalize-hooks.mjs +184 -0
  87. package/hooks/session-db.bundle.mjs +41 -14
  88. package/hooks/session-extract.bundle.mjs +2 -2
  89. package/hooks/session-helpers.mjs +68 -20
  90. package/hooks/session-loaders.mjs +8 -2
  91. package/hooks/sessionstart.mjs +8 -2
  92. package/hooks/vscode-copilot/sessionstart.mjs +7 -2
  93. package/openclaw.plugin.json +1 -1
  94. package/package.json +2 -1
  95. package/server.bundle.mjs +181 -134
  96. package/skills/ctx-doctor/SKILL.md +3 -3
  97. package/skills/ctx-insight/SKILL.md +1 -1
  98. package/start.mjs +63 -3
@@ -0,0 +1,8 @@
1
+ import type { ToolNamer } from "./tool-naming.js";
2
+ export interface RoutingBlockOptions {
3
+ includeCommands?: boolean;
4
+ }
5
+ export declare function createRoutingBlock(t: ToolNamer, options?: RoutingBlockOptions): string;
6
+ export declare function createReadGuidance(t: ToolNamer): string;
7
+ export declare function createGrepGuidance(t: ToolNamer): string;
8
+ export declare function createBashGuidance(t: ToolNamer): string;
@@ -0,0 +1,86 @@
1
+ export function createRoutingBlock(t, options = {}) {
2
+ const { includeCommands = true } = options;
3
+ return `
4
+ <context_window_protection>
5
+ <priority_instructions>
6
+ Raw tool output floods context window. MUST use context-mode MCP tools. Keep raw data in sandbox.
7
+ </priority_instructions>
8
+
9
+ <tool_selection_hierarchy>
10
+ 0. MEMORY: ${t("ctx_search")}(sort: "timeline")
11
+ - After resume, check prior context before asking user.
12
+ 1. GATHER: ${t("ctx_batch_execute")}(commands, queries)
13
+ - Primary research tool. Runs commands, auto-indexes, searches. ONE call replaces many steps.
14
+ - Each command: {label: "section header", command: "shell command"}
15
+ - label becomes FTS5 chunk title — descriptive labels improve search.
16
+ 2. FOLLOW-UP: ${t("ctx_search")}(queries: ["q1", "q2", ...])
17
+ - All follow-up questions. ONE call, many queries (default relevance mode).
18
+ 3. PROCESSING: ${t("ctx_execute")}(language, code) | ${t("ctx_execute_file")}(path, language, code)
19
+ - API calls, log analysis, data processing.
20
+ </tool_selection_hierarchy>
21
+
22
+ <forbidden_actions>
23
+ - NO Bash for commands producing >20 lines output.
24
+ - NO Read for analysis — use execute_file. Read IS correct for files you intend to Edit.
25
+ - NO WebFetch — use ${t("ctx_fetch_and_index")}.
26
+ - Bash ONLY for git/mkdir/rm/mv/navigation.
27
+ - NO ${t("ctx_execute")} or ${t("ctx_execute_file")} for file creation/modification.
28
+ ctx_execute is for analysis, processing, computation only.
29
+ </forbidden_actions>
30
+
31
+ <file_writing_policy>
32
+ ALWAYS use native Write/Edit tools for file creation/modification.
33
+ NEVER use ${t("ctx_execute")}, ${t("ctx_execute_file")}, or Bash to write files.
34
+ Applies to all file types: code, configs, plans, specs, YAML, JSON, markdown.
35
+ </file_writing_policy>
36
+
37
+ <output_constraints>
38
+ <communication_style>
39
+ Terse like caveman. Technical substance exact. Only fluff die.
40
+ Use fragments when clear. Short synonyms (fix not "implement a solution for").
41
+ Technical terms exact. Code blocks unchanged.
42
+ Auto-expand for: security warnings, irreversible actions, user confusion.
43
+ </communication_style>
44
+ <artifact_policy>
45
+ Write artifacts (code, configs, PRDs) to FILES. NEVER inline.
46
+ Return only: file path + 1-line description.
47
+ </artifact_policy>
48
+ <response_format>
49
+ Concise summary:
50
+ - Actions taken (2-3 bullets)
51
+ - File paths created/modified
52
+ - Key findings
53
+ </response_format>
54
+ </output_constraints>
55
+ <session_continuity>
56
+ Skills, roles, and decisions set during this session remain active until the user revokes them.
57
+ Do not drop behavioral directives as context grows.
58
+ </session_continuity>
59
+ ${includeCommands ? `
60
+ <ctx_commands>
61
+ "ctx stats" | "ctx-stats" | "/ctx-stats" | context savings question
62
+ → Call stats MCP tool, display full output verbatim.
63
+
64
+ "ctx doctor" | "ctx-doctor" | "/ctx-doctor" | diagnose context-mode
65
+ → Call doctor MCP tool, run returned shell command, display as checklist.
66
+
67
+ "ctx upgrade" | "ctx-upgrade" | "/ctx-upgrade" | update context-mode
68
+ → Call upgrade MCP tool, run returned shell command, display as checklist.
69
+
70
+ "ctx purge" | "ctx-purge" | "/ctx-purge" | wipe/reset knowledge base
71
+ → Call purge MCP tool with confirm: true. Warn: irreversible.
72
+
73
+ After /clear or /compact: knowledge base preserved. Tell user: "context-mode knowledge base preserved. Use \`ctx purge\` to start fresh."
74
+ </ctx_commands>
75
+ ` : ''}
76
+ </context_window_protection>`;
77
+ }
78
+ export function createReadGuidance(t) {
79
+ return '<context_guidance>\n <tip>\n Reading to Edit? Read is correct — Edit needs content in context.\n Reading to analyze/explore? Use ' + t("ctx_execute_file") + '(path, language, code) — only printed summary enters context.\n </tip>\n</context_guidance>';
80
+ }
81
+ export function createGrepGuidance(t) {
82
+ return '<context_guidance>\n <tip>\n May flood context. Use ' + t("ctx_execute") + '(language: "shell", code: "...") to run searches in sandbox. Only printed summary enters context.\n </tip>\n</context_guidance>';
83
+ }
84
+ export function createBashGuidance(t) {
85
+ return '<context_guidance>\n <tip>\n May produce large output. Use ' + t("ctx_batch_execute") + '(commands, queries) for multiple commands, ' + t("ctx_execute") + '(language: "shell", code: "...") for single. Only printed summary enters context. Bash only for: git, mkdir, rm, mv, navigation.\n </tip>\n</context_guidance>';
86
+ }
@@ -1,3 +1,4 @@
1
+ export declare function isAllowlistedShell(shellPath: string): boolean;
1
2
  export type Language = "javascript" | "typescript" | "python" | "shell" | "ruby" | "go" | "rust" | "php" | "perl" | "r" | "elixir";
2
3
  export interface RuntimeInfo {
3
4
  command: string;
package/build/runtime.js CHANGED
@@ -1,5 +1,23 @@
1
1
  import { execFileSync, execSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
+ /**
4
+ * Allowlist for SHELL env override. Only POSIX shells + Windows shells permit
5
+ * arbitrary command interpretation; anything else (e.g., /usr/bin/python set
6
+ * as SHELL) would let an attacker redirect the executor to a non-shell binary.
7
+ *
8
+ * basename split handles BOTH `/` and `\` separators so a Windows-style path
9
+ * (`C:\Program Files\PowerShell\7\pwsh.exe`) classifies correctly even when
10
+ * the runtime is on POSIX (where node:path.basename only splits on `/`).
11
+ *
12
+ * Match is case-insensitive; `.exe` extension tolerated for Windows binaries.
13
+ */
14
+ const ALLOWED_SHELL_BASENAMES = /^(bash|sh|zsh|dash|pwsh|powershell|cmd)(\.exe)?$/i;
15
+ export function isAllowlistedShell(shellPath) {
16
+ // Cross-OS basename: split on either separator, take the last segment.
17
+ const segments = shellPath.split(/[\\/]/);
18
+ const base = segments[segments.length - 1];
19
+ return ALLOWED_SHELL_BASENAMES.test(base);
20
+ }
3
21
  const isWindows = process.platform === "win32";
4
22
  function commandExists(cmd) {
5
23
  try {
@@ -93,6 +111,19 @@ function getVersion(cmd, args = ["--version"]) {
93
111
  export function detectRuntimes() {
94
112
  const hasBun = bunExists();
95
113
  const bun = hasBun ? bunCommand() : null;
114
+ // Honor SHELL env var when it points at a real binary AND the basename is
115
+ // an allowlisted shell. Lets users with non-standard setups (WSL, custom
116
+ // bash, msys2) pin context-mode to their preferred shell.
117
+ //
118
+ // Allowlist (PR #401 ops review): basename must match
119
+ // /^(bash|sh|zsh|dash|pwsh|cmd)(\.exe)?$/. Without this guard, an attacker
120
+ // who controls SHELL (e.g., supply-chain compromise of a profile script)
121
+ // could redirect the executor to /usr/bin/python or any arbitrary binary.
122
+ const userShell = process.env.SHELL;
123
+ const shellOverride = userShell && existsSync(userShell) && isAllowlistedShell(userShell)
124
+ ? userShell
125
+ : null;
126
+ const isWin = process.platform === "win32";
96
127
  return {
97
128
  javascript: bun ?? process.execPath,
98
129
  typescript: bun
@@ -107,9 +138,9 @@ export function detectRuntimes() {
107
138
  : commandExists("python")
108
139
  ? "python"
109
140
  : null,
110
- shell: isWindows
141
+ shell: shellOverride ?? (isWin
111
142
  ? (resolveWindowsBash() ?? (commandExists("sh") ? "sh" : commandExists("powershell") ? "powershell" : "cmd.exe"))
112
- : commandExists("bash") ? "bash" : "sh",
143
+ : commandExists("bash") ? "bash" : "sh"),
113
144
  ruby: commandExists("ruby") ? "ruby" : null,
114
145
  go: commandExists("go") ? "go" : null,
115
146
  rust: commandExists("rustc") ? "rustc" : null,
@@ -206,8 +237,28 @@ export function buildCommand(runtimes, language, filePath) {
206
237
  throw new Error("No Python runtime available. Install python3 or python.");
207
238
  }
208
239
  return [runtimes.python, filePath];
209
- case "shell":
240
+ case "shell": {
241
+ // Re-evaluate platform per call so detection-time and command-build-time
242
+ // can be tested independently (and to allow tests to stub process.platform).
243
+ const winNow = process.platform === "win32";
244
+ if (winNow) {
245
+ const shellName = runtimes.shell.toLowerCase();
246
+ if (shellName.includes("bash") || shellName.endsWith("/sh") || shellName.endsWith("\\sh.exe")) {
247
+ // bash -c "source 'path'" — avoids MSYS2 path mangling on non-C:
248
+ // drives. When bash.exe receives a script as a direct argument,
249
+ // MSYS rewrites D:\tmp\script → D:\c\tmp\script and execution
250
+ // breaks. The -c flag prevents MSYS from touching the file arg.
251
+ // Single-quote escape: ' → '\''
252
+ const escaped = filePath.replace(/'/g, "'\\''");
253
+ return [runtimes.shell, "-c", `source '${escaped}'`];
254
+ }
255
+ if (shellName.includes("powershell") || shellName.includes("pwsh")) {
256
+ return [runtimes.shell, "-File", filePath];
257
+ }
258
+ // cmd.exe and others: direct file (cmd reads .cmd association safely).
259
+ }
210
260
  return [runtimes.shell, filePath];
261
+ }
211
262
  case "ruby":
212
263
  if (!runtimes.ruby) {
213
264
  throw new Error("Ruby not available. Install ruby.");
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Auto-memory search — searches CLAUDE.md and MEMORY.md files for
3
- * persisted decisions, preferences, and context from prior sessions.
2
+ * Auto-memory search — searches CLAUDE.md / AGENTS.md / GEMINI.md / etc.
3
+ * and the platform's persistent memory directory for decisions,
4
+ * preferences, and context from prior sessions.
4
5
  *
5
6
  * Returns results in a format compatible with the unified search pipeline.
6
7
  */
@@ -12,18 +13,30 @@ export interface AutoMemoryResult {
12
13
  timestamp?: string;
13
14
  }
14
15
  /**
15
- * Search auto-memory files (CLAUDE.md, MEMORY.md, user identity files)
16
- * for content matching any of the given queries.
16
+ * Minimal adapter contract used by searchAutoMemory.
17
+ * Avoids depending on the full HookAdapter type to keep this module standalone.
18
+ */
19
+ export interface AutoMemoryAdapter {
20
+ getConfigDir(): string;
21
+ getInstructionFiles(): string[];
22
+ getMemoryDir(): string;
23
+ }
24
+ /**
25
+ * Search auto-memory files for content matching any of the given queries.
26
+ *
27
+ * When `adapter` is provided, the per-platform conventions are used:
28
+ * 1. Project-level: <projectDir>/<each instructionFile>
29
+ * 2. User-level: <configDir>/<each instructionFile>
30
+ * 3. Memory dir: <memoryDir>/*.md
17
31
  *
18
- * Scans:
19
- * 1. Project-level: <projectDir>/CLAUDE.md
20
- * 2. User-level: <configDir>/CLAUDE.md
21
- * 3. User memory: <configDir>/memory/*.md
32
+ * Without an adapter (legacy callers), defaults to Claude conventions
33
+ * (CLAUDE.md + ~/.claude/memory) for backwards compatibility.
22
34
  *
23
35
  * @param queries Array of search terms
24
36
  * @param limit Max results to return
25
37
  * @param projectDir Project directory path
26
- * @param configDir Config directory (e.g. ~/.claude)
38
+ * @param configDir Explicit config dir override (legacy callers)
39
+ * @param adapter Platform adapter — supplies instruction files + memory dir
27
40
  * @returns Matching auto-memory results
28
41
  */
29
- export declare function searchAutoMemory(queries: string[], limit?: number, projectDir?: string, configDir?: string): AutoMemoryResult[];
42
+ export declare function searchAutoMemory(queries: string[], limit?: number, projectDir?: string, configDir?: string, adapter?: AutoMemoryAdapter): AutoMemoryResult[];
@@ -1,48 +1,68 @@
1
1
  /**
2
- * Auto-memory search — searches CLAUDE.md and MEMORY.md files for
3
- * persisted decisions, preferences, and context from prior sessions.
2
+ * Auto-memory search — searches CLAUDE.md / AGENTS.md / GEMINI.md / etc.
3
+ * and the platform's persistent memory directory for decisions,
4
+ * preferences, and context from prior sessions.
4
5
  *
5
6
  * Returns results in a format compatible with the unified search pipeline.
6
7
  */
7
8
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
- import { join } from "node:path";
9
+ import { join, isAbsolute } from "node:path";
9
10
  import { homedir } from "node:os";
10
11
  const DEBUG = process.env.DEBUG?.includes("context-mode");
11
12
  /**
12
- * Search auto-memory files (CLAUDE.md, MEMORY.md, user identity files)
13
- * for content matching any of the given queries.
13
+ * Search auto-memory files for content matching any of the given queries.
14
14
  *
15
- * Scans:
16
- * 1. Project-level: <projectDir>/CLAUDE.md
17
- * 2. User-level: <configDir>/CLAUDE.md
18
- * 3. User memory: <configDir>/memory/*.md
15
+ * When `adapter` is provided, the per-platform conventions are used:
16
+ * 1. Project-level: <projectDir>/<each instructionFile>
17
+ * 2. User-level: <configDir>/<each instructionFile>
18
+ * 3. Memory dir: <memoryDir>/*.md
19
+ *
20
+ * Without an adapter (legacy callers), defaults to Claude conventions
21
+ * (CLAUDE.md + ~/.claude/memory) for backwards compatibility.
19
22
  *
20
23
  * @param queries Array of search terms
21
24
  * @param limit Max results to return
22
25
  * @param projectDir Project directory path
23
- * @param configDir Config directory (e.g. ~/.claude)
26
+ * @param configDir Explicit config dir override (legacy callers)
27
+ * @param adapter Platform adapter — supplies instruction files + memory dir
24
28
  * @returns Matching auto-memory results
25
29
  */
26
- export function searchAutoMemory(queries, limit = 5, projectDir, configDir) {
30
+ export function searchAutoMemory(queries, limit = 5, projectDir, configDir, adapter) {
27
31
  const results = [];
28
- const effectiveConfigDir = configDir || join(homedir(), ".claude");
32
+ // Resolve conventions adapter wins over explicit configDir, which wins
33
+ // over the historical Claude defaults.
34
+ const instructionFiles = adapter?.getInstructionFiles() ?? ["CLAUDE.md"];
35
+ const adapterConfigDir = adapter?.getConfigDir();
36
+ const effectiveConfigDir = adapterConfigDir
37
+ ? resolveAgainst(projectDir, adapterConfigDir)
38
+ : (configDir || join(homedir(), ".claude"));
39
+ const adapterMemoryDir = adapter?.getMemoryDir();
40
+ const memoryDir = adapterMemoryDir
41
+ ? resolveAgainst(projectDir, adapterMemoryDir)
42
+ : join(effectiveConfigDir, "memory");
29
43
  // Collect candidate files
30
44
  const candidates = [];
31
- // 1. Project-level CLAUDE.md
45
+ // 1. Project-level instruction files
32
46
  if (projectDir) {
33
- const projectClaude = join(projectDir, "CLAUDE.md");
34
- if (existsSync(projectClaude)) {
35
- candidates.push({ path: projectClaude, label: "project/CLAUDE.md" });
47
+ for (const fileName of instructionFiles) {
48
+ const p = join(projectDir, fileName);
49
+ if (existsSync(p)) {
50
+ candidates.push({ path: p, label: `project/${fileName}` });
51
+ }
36
52
  }
37
53
  }
38
- // 2. User-level CLAUDE.md
39
- const userClaude = join(effectiveConfigDir, "CLAUDE.md");
40
- if (existsSync(userClaude)) {
41
- candidates.push({ path: userClaude, label: "user/CLAUDE.md" });
54
+ // 2. User-level instruction files (skip when configDir resolves to the
55
+ // project root already covered by step 1, would emit dup labels).
56
+ if (effectiveConfigDir && effectiveConfigDir !== projectDir) {
57
+ for (const fileName of instructionFiles) {
58
+ const p = join(effectiveConfigDir, fileName);
59
+ if (existsSync(p)) {
60
+ candidates.push({ path: p, label: `user/${fileName}` });
61
+ }
62
+ }
42
63
  }
43
- // 3. User memory directory
44
- const memoryDir = join(effectiveConfigDir, "memory");
45
- if (existsSync(memoryDir)) {
64
+ // 3. Memory directory
65
+ if (memoryDir && existsSync(memoryDir)) {
46
66
  try {
47
67
  const files = readdirSync(memoryDir).filter(f => f.endsWith(".md"));
48
68
  for (const file of files) {
@@ -62,9 +82,13 @@ export function searchAutoMemory(queries, limit = 5, projectDir, configDir) {
62
82
  if (results.length >= limit)
63
83
  break;
64
84
  try {
65
- // Skip files larger than 1MB to avoid memory issues
85
+ // Single stat for both size guard and timestamp — saves one syscall
86
+ // per candidate file. Cross-platform: statSync semantics identical
87
+ // on macOS / Linux / Windows; size+mtime read in the same inode probe.
88
+ let stat;
66
89
  try {
67
- if (statSync(candidate.path).size > 1_000_000)
90
+ stat = statSync(candidate.path);
91
+ if (stat.size > 1_000_000)
68
92
  continue;
69
93
  }
70
94
  catch {
@@ -106,7 +130,7 @@ export function searchAutoMemory(queries, limit = 5, projectDir, configDir) {
106
130
  content: snippet,
107
131
  source: candidate.label,
108
132
  origin: "auto-memory",
109
- timestamp: statSync(candidate.path).mtime.toISOString(),
133
+ timestamp: stat.mtime.toISOString(),
110
134
  });
111
135
  break; // one result per file per query batch
112
136
  }
@@ -119,3 +143,17 @@ export function searchAutoMemory(queries, limit = 5, projectDir, configDir) {
119
143
  }
120
144
  return results.slice(0, limit);
121
145
  }
146
+ /**
147
+ * Resolve a possibly-relative path (e.g. ".github", "memory") against a
148
+ * project directory. Absolute paths and empty strings are returned as-is
149
+ * (empty == "use projectDir directly").
150
+ */
151
+ function resolveAgainst(projectDir, p) {
152
+ if (!p)
153
+ return projectDir ?? "";
154
+ if (isAbsolute(p))
155
+ return p;
156
+ if (!projectDir)
157
+ return p;
158
+ return join(projectDir, p);
159
+ }
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import type { ContentStore } from "../store.js";
9
9
  import type { SessionDB } from "../session/db.js";
10
+ import { type AutoMemoryAdapter } from "./auto-memory.js";
10
11
  export interface UnifiedSearchResult {
11
12
  title: string;
12
13
  content: string;
@@ -28,6 +29,8 @@ export interface SearchAllSourcesOpts {
28
29
  sessionDB?: SessionDB | null;
29
30
  projectDir?: string;
30
31
  configDir?: string;
32
+ /** Detected platform adapter — used for adapter-aware auto-memory. */
33
+ adapter?: AutoMemoryAdapter;
31
34
  }
32
35
  /**
33
36
  * Search across all available sources.
@@ -20,7 +20,7 @@ const DEBUG = process.env.DEBUG?.includes("context-mode");
20
20
  * are always returned.
21
21
  */
22
22
  export function searchAllSources(opts) {
23
- const { query, limit, store, sort = "relevance", source, contentType, sessionDB, projectDir, configDir, } = opts;
23
+ const { query, limit, store, sort = "relevance", source, contentType, sessionDB, projectDir, configDir, adapter, } = opts;
24
24
  const results = [];
25
25
  // Capture session start time once — used as proxy for ContentStore items
26
26
  // (we don't know exact indexing time, but all content is from current session)
@@ -65,7 +65,7 @@ export function searchAllSources(opts) {
65
65
  }
66
66
  // Source 3: Auto-memory
67
67
  try {
68
- const memResults = searchAutoMemory([query], limit, projectDir, configDir);
68
+ const memResults = searchAutoMemory([query], limit, projectDir, configDir, adapter);
69
69
  results.push(...memResults);
70
70
  }
71
71
  catch (e) {
package/build/server.d.ts CHANGED
@@ -8,3 +8,50 @@ import { ContentStore } from "./store.js";
8
8
  export declare function positionsFromHighlight(highlighted: string): number[];
9
9
  export declare function extractSnippet(content: string, query: string, maxLen?: number, highlighted?: string): string;
10
10
  export declare function formatBatchQueryResults(store: ContentStore, queries: string[], source: string, maxOutput?: number): string[];
11
+ export interface BatchCommand {
12
+ label: string;
13
+ command: string;
14
+ }
15
+ export interface BatchRunResult {
16
+ outputs: string[];
17
+ timedOut: boolean;
18
+ }
19
+ export interface BatchRunOptions {
20
+ /**
21
+ * Total budget (concurrency=1, shared) or per-command (concurrency>1).
22
+ * When `undefined`, no server-side timer fires — the MCP host's RPC
23
+ * timeout governs (Issue #406).
24
+ */
25
+ timeout: number | undefined;
26
+ concurrency: number;
27
+ nodeOptsPrefix: string;
28
+ onFsBytes?: (bytes: number) => void;
29
+ }
30
+ interface BatchExecutor {
31
+ execute(input: {
32
+ language: "shell";
33
+ code: string;
34
+ timeout: number | undefined;
35
+ }): Promise<{
36
+ stdout: string;
37
+ timedOut?: boolean;
38
+ }>;
39
+ }
40
+ /**
41
+ * Execute batch commands. concurrency=1 preserves the legacy serial path
42
+ * (shared timeout budget + cascading skip-on-timeout). concurrency>1 runs
43
+ * commands concurrently with at most N in flight; each command receives the
44
+ * full timeout, output is collated by input index, and per-command timeouts
45
+ * record `(timed out)` blocks without skipping siblings.
46
+ */
47
+ export declare function runBatchCommands(commands: BatchCommand[], opts: BatchRunOptions, executor: BatchExecutor): Promise<BatchRunResult>;
48
+ /**
49
+ * Classify an IP address.
50
+ * - "block": always blocked (link-local/IMDS/multicast/reserved/malformed)
51
+ * - "private": loopback or RFC1918 — allowed by default, blocked in strict mode
52
+ * - "public": safe to fetch
53
+ *
54
+ * Exported (via the function name) so SSRF tests can exercise the matcher directly.
55
+ */
56
+ export declare function classifyIp(ip: string): "block" | "private" | "public";
57
+ export {};