@contextstream/mcp-server 0.4.67 → 0.4.71

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.
@@ -67,6 +67,8 @@ function getOrCreateEntry(state, cwd) {
67
67
  require_init: false,
68
68
  last_context_at: void 0,
69
69
  last_state_change_at: void 0,
70
+ index_wait_started_at: void 0,
71
+ index_wait_until: void 0,
70
72
  updated_at: nowIso()
71
73
  };
72
74
  state.workspaces[cwd] = created;
@@ -96,6 +98,8 @@ function clearContextRequired(cwd) {
96
98
  if (!target) return;
97
99
  target.entry.require_context = false;
98
100
  target.entry.last_context_at = nowIso();
101
+ target.entry.index_wait_started_at = void 0;
102
+ target.entry.index_wait_until = void 0;
99
103
  target.entry.updated_at = nowIso();
100
104
  writeState(state);
101
105
  }
@@ -147,6 +151,44 @@ function isContextFreshAndClean(cwd, maxAgeSeconds) {
147
151
  }
148
152
  return true;
149
153
  }
154
+ function startIndexWaitWindow(cwd, waitSeconds) {
155
+ if (!cwd.trim() || waitSeconds <= 0) return;
156
+ const state = readState();
157
+ const target = getOrCreateEntry(state, cwd);
158
+ if (!target) return;
159
+ const now = Date.now();
160
+ const existingUntil = target.entry.index_wait_until ? new Date(target.entry.index_wait_until).getTime() : NaN;
161
+ if (!Number.isNaN(existingUntil) && existingUntil > now) {
162
+ target.entry.updated_at = nowIso();
163
+ writeState(state);
164
+ return;
165
+ }
166
+ target.entry.index_wait_started_at = new Date(now).toISOString();
167
+ target.entry.index_wait_until = new Date(now + waitSeconds * 1e3).toISOString();
168
+ target.entry.updated_at = nowIso();
169
+ writeState(state);
170
+ }
171
+ function clearIndexWaitWindow(cwd) {
172
+ if (!cwd.trim()) return;
173
+ const state = readState();
174
+ const target = getOrCreateEntry(state, cwd);
175
+ if (!target) return;
176
+ target.entry.index_wait_started_at = void 0;
177
+ target.entry.index_wait_until = void 0;
178
+ target.entry.updated_at = nowIso();
179
+ writeState(state);
180
+ }
181
+ function indexWaitRemainingSeconds(cwd) {
182
+ if (!cwd.trim()) return null;
183
+ const state = readState();
184
+ const target = getOrCreateEntry(state, cwd);
185
+ if (!target?.entry.index_wait_until) return null;
186
+ const until = new Date(target.entry.index_wait_until).getTime();
187
+ if (Number.isNaN(until)) return null;
188
+ const remainingMs = until - Date.now();
189
+ if (remainingMs <= 0) return null;
190
+ return Math.ceil(remainingMs / 1e3);
191
+ }
150
192
 
151
193
  // src/hooks/pre-tool-use.ts
152
194
  var ENABLED = process.env.CONTEXTSTREAM_HOOK_ENABLED !== "false";
@@ -154,6 +196,9 @@ var INDEX_STATUS_FILE = path2.join(homedir2(), ".contextstream", "indexed-projec
154
196
  var DEBUG_FILE = "/tmp/pretooluse-hook-debug.log";
155
197
  var STALE_THRESHOLD_DAYS = 7;
156
198
  var CONTEXT_FRESHNESS_SECONDS = 120;
199
+ var DEFAULT_INDEX_WAIT_SECONDS = 20;
200
+ var MIN_INDEX_WAIT_SECONDS = 15;
201
+ var MAX_INDEX_WAIT_SECONDS = 20;
157
202
  var DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"];
158
203
  function isDiscoveryGlob(pattern) {
159
204
  const patternLower = pattern.toLowerCase();
@@ -179,6 +224,33 @@ function isDiscoveryGrep(filePath) {
179
224
  }
180
225
  return false;
181
226
  }
227
+ function configuredIndexWaitSeconds() {
228
+ const parsed = Number.parseInt(process.env.CONTEXTSTREAM_INDEX_WAIT_SECONDS ?? "", 10);
229
+ if (Number.isNaN(parsed)) return DEFAULT_INDEX_WAIT_SECONDS;
230
+ return Math.min(MAX_INDEX_WAIT_SECONDS, Math.max(MIN_INDEX_WAIT_SECONDS, parsed));
231
+ }
232
+ function isLocalDiscoveryToolDuringIndexWait(tool, toolInput) {
233
+ if (tool === "Glob" || tool === "Explore" || tool === "SemanticSearch" || tool === "codebase_search") {
234
+ return true;
235
+ }
236
+ if (tool === "Task") {
237
+ const subagentTypeRaw = toolInput?.subagent_type || toolInput?.subagentType || "";
238
+ return subagentTypeRaw.toLowerCase().includes("explore");
239
+ }
240
+ if (tool === "Grep" || tool === "Search" || tool === "grep_search" || tool === "code_search") {
241
+ const filePath = toolInput?.path || "";
242
+ return isDiscoveryGrep(filePath);
243
+ }
244
+ if (tool === "Read" || tool === "ReadFile" || tool === "read_file") {
245
+ const filePath = toolInput?.file_path || toolInput?.path || toolInput?.file || toolInput?.target_file || "";
246
+ return isDiscoveryGrep(filePath);
247
+ }
248
+ if (tool === "list_files" || tool === "search_files" || tool === "search_files_content" || tool === "find_files" || tool === "find_by_name") {
249
+ const pattern = toolInput?.path || toolInput?.regex || toolInput?.pattern || toolInput?.query || "";
250
+ return !pattern || isDiscoveryGlob(pattern) || isDiscoveryGrep(pattern);
251
+ }
252
+ return false;
253
+ }
182
254
  function isProjectIndexed(cwd) {
183
255
  if (!fs2.existsSync(INDEX_STATUS_FILE)) {
184
256
  return { isIndexed: false, isStale: false };
@@ -440,12 +512,22 @@ async function runPreToolUseHook() {
440
512
  blockWithMessage(editorFormat, msg);
441
513
  }
442
514
  }
443
- const { isIndexed } = isProjectIndexed(cwd);
444
- fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] isIndexed=${isIndexed}
445
- `);
446
- if (!isIndexed) {
447
- fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] Project not indexed, allowing
515
+ const { isIndexed, isStale } = isProjectIndexed(cwd);
516
+ fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] isIndexed=${isIndexed}, isStale=${isStale}
448
517
  `);
518
+ if (isIndexed && !isStale) {
519
+ clearIndexWaitWindow(cwd);
520
+ } else {
521
+ const waitSeconds = configuredIndexWaitSeconds();
522
+ if (isLocalDiscoveryToolDuringIndexWait(tool, toolInput)) {
523
+ startIndexWaitWindow(cwd, waitSeconds);
524
+ const remaining = indexWaitRemainingSeconds(cwd);
525
+ if (remaining && remaining > 0) {
526
+ const msg = `Index refresh grace window is active (${remaining}s remaining). Keep ContextStream search-first flow: mcp__contextstream__search(mode="auto", query="..."). Do not use local discovery tools yet for stale/not-indexed projects. Retry after refresh; local fallback is allowed only after ~${waitSeconds}s if index is still unavailable.`;
527
+ blockWithMessage(editorFormat, msg);
528
+ }
529
+ allowTool(editorFormat, cwd, recordStateChange);
530
+ }
449
531
  allowTool(editorFormat, cwd, recordStateChange);
450
532
  }
451
533
  if (tool === "Glob") {
@@ -76,6 +76,8 @@ function getOrCreateEntry(state, cwd) {
76
76
  require_init: false,
77
77
  last_context_at: void 0,
78
78
  last_state_change_at: void 0,
79
+ index_wait_started_at: void 0,
80
+ index_wait_until: void 0,
79
81
  updated_at: nowIso()
80
82
  };
81
83
  state.workspaces[cwd] = created;
@@ -114,6 +116,8 @@ function clearContextRequired(cwd) {
114
116
  if (!target) return;
115
117
  target.entry.require_context = false;
116
118
  target.entry.last_context_at = nowIso();
119
+ target.entry.index_wait_started_at = void 0;
120
+ target.entry.index_wait_until = void 0;
117
121
  target.entry.updated_at = nowIso();
118
122
  writeState(state);
119
123
  }
@@ -174,6 +178,44 @@ function isContextFreshAndClean(cwd, maxAgeSeconds) {
174
178
  }
175
179
  return true;
176
180
  }
181
+ function startIndexWaitWindow(cwd, waitSeconds) {
182
+ if (!cwd.trim() || waitSeconds <= 0) return;
183
+ const state = readState();
184
+ const target = getOrCreateEntry(state, cwd);
185
+ if (!target) return;
186
+ const now = Date.now();
187
+ const existingUntil = target.entry.index_wait_until ? new Date(target.entry.index_wait_until).getTime() : NaN;
188
+ if (!Number.isNaN(existingUntil) && existingUntil > now) {
189
+ target.entry.updated_at = nowIso();
190
+ writeState(state);
191
+ return;
192
+ }
193
+ target.entry.index_wait_started_at = new Date(now).toISOString();
194
+ target.entry.index_wait_until = new Date(now + waitSeconds * 1e3).toISOString();
195
+ target.entry.updated_at = nowIso();
196
+ writeState(state);
197
+ }
198
+ function clearIndexWaitWindow(cwd) {
199
+ if (!cwd.trim()) return;
200
+ const state = readState();
201
+ const target = getOrCreateEntry(state, cwd);
202
+ if (!target) return;
203
+ target.entry.index_wait_started_at = void 0;
204
+ target.entry.index_wait_until = void 0;
205
+ target.entry.updated_at = nowIso();
206
+ writeState(state);
207
+ }
208
+ function indexWaitRemainingSeconds(cwd) {
209
+ if (!cwd.trim()) return null;
210
+ const state = readState();
211
+ const target = getOrCreateEntry(state, cwd);
212
+ if (!target?.entry.index_wait_until) return null;
213
+ const until = new Date(target.entry.index_wait_until).getTime();
214
+ if (Number.isNaN(until)) return null;
215
+ const remainingMs = until - Date.now();
216
+ if (remainingMs <= 0) return null;
217
+ return Math.ceil(remainingMs / 1e3);
218
+ }
177
219
  var STATE_PATH;
178
220
  var init_prompt_state = __esm({
179
221
  "src/hooks/prompt-state.ts"() {
@@ -214,6 +256,33 @@ function isDiscoveryGrep(filePath) {
214
256
  }
215
257
  return false;
216
258
  }
259
+ function configuredIndexWaitSeconds() {
260
+ const parsed = Number.parseInt(process.env.CONTEXTSTREAM_INDEX_WAIT_SECONDS ?? "", 10);
261
+ if (Number.isNaN(parsed)) return DEFAULT_INDEX_WAIT_SECONDS;
262
+ return Math.min(MAX_INDEX_WAIT_SECONDS, Math.max(MIN_INDEX_WAIT_SECONDS, parsed));
263
+ }
264
+ function isLocalDiscoveryToolDuringIndexWait(tool, toolInput) {
265
+ if (tool === "Glob" || tool === "Explore" || tool === "SemanticSearch" || tool === "codebase_search") {
266
+ return true;
267
+ }
268
+ if (tool === "Task") {
269
+ const subagentTypeRaw = toolInput?.subagent_type || toolInput?.subagentType || "";
270
+ return subagentTypeRaw.toLowerCase().includes("explore");
271
+ }
272
+ if (tool === "Grep" || tool === "Search" || tool === "grep_search" || tool === "code_search") {
273
+ const filePath = toolInput?.path || "";
274
+ return isDiscoveryGrep(filePath);
275
+ }
276
+ if (tool === "Read" || tool === "ReadFile" || tool === "read_file") {
277
+ const filePath = toolInput?.file_path || toolInput?.path || toolInput?.file || toolInput?.target_file || "";
278
+ return isDiscoveryGrep(filePath);
279
+ }
280
+ if (tool === "list_files" || tool === "search_files" || tool === "search_files_content" || tool === "find_files" || tool === "find_by_name") {
281
+ const pattern = toolInput?.path || toolInput?.regex || toolInput?.pattern || toolInput?.query || "";
282
+ return !pattern || isDiscoveryGlob(pattern) || isDiscoveryGrep(pattern);
283
+ }
284
+ return false;
285
+ }
217
286
  function isProjectIndexed(cwd) {
218
287
  if (!fs2.existsSync(INDEX_STATUS_FILE)) {
219
288
  return { isIndexed: false, isStale: false };
@@ -475,12 +544,22 @@ async function runPreToolUseHook() {
475
544
  blockWithMessage(editorFormat, msg);
476
545
  }
477
546
  }
478
- const { isIndexed } = isProjectIndexed(cwd);
479
- fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] isIndexed=${isIndexed}
480
- `);
481
- if (!isIndexed) {
482
- fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] Project not indexed, allowing
547
+ const { isIndexed, isStale } = isProjectIndexed(cwd);
548
+ fs2.appendFileSync(DEBUG_FILE, `[PreToolUse] isIndexed=${isIndexed}, isStale=${isStale}
483
549
  `);
550
+ if (isIndexed && !isStale) {
551
+ clearIndexWaitWindow(cwd);
552
+ } else {
553
+ const waitSeconds = configuredIndexWaitSeconds();
554
+ if (isLocalDiscoveryToolDuringIndexWait(tool, toolInput)) {
555
+ startIndexWaitWindow(cwd, waitSeconds);
556
+ const remaining = indexWaitRemainingSeconds(cwd);
557
+ if (remaining && remaining > 0) {
558
+ const msg = `Index refresh grace window is active (${remaining}s remaining). Keep ContextStream search-first flow: mcp__contextstream__search(mode="auto", query="..."). Do not use local discovery tools yet for stale/not-indexed projects. Retry after refresh; local fallback is allowed only after ~${waitSeconds}s if index is still unavailable.`;
559
+ blockWithMessage(editorFormat, msg);
560
+ }
561
+ allowTool(editorFormat, cwd, recordStateChange);
562
+ }
484
563
  allowTool(editorFormat, cwd, recordStateChange);
485
564
  }
486
565
  if (tool === "Glob") {
@@ -572,7 +651,7 @@ async function runPreToolUseHook() {
572
651
  }
573
652
  allowTool(editorFormat, cwd, recordStateChange);
574
653
  }
575
- var ENABLED, INDEX_STATUS_FILE, DEBUG_FILE, STALE_THRESHOLD_DAYS, CONTEXT_FRESHNESS_SECONDS, DISCOVERY_PATTERNS, isDirectRun;
654
+ var ENABLED, INDEX_STATUS_FILE, DEBUG_FILE, STALE_THRESHOLD_DAYS, CONTEXT_FRESHNESS_SECONDS, DEFAULT_INDEX_WAIT_SECONDS, MIN_INDEX_WAIT_SECONDS, MAX_INDEX_WAIT_SECONDS, DISCOVERY_PATTERNS, isDirectRun;
576
655
  var init_pre_tool_use = __esm({
577
656
  "src/hooks/pre-tool-use.ts"() {
578
657
  "use strict";
@@ -582,6 +661,9 @@ var init_pre_tool_use = __esm({
582
661
  DEBUG_FILE = "/tmp/pretooluse-hook-debug.log";
583
662
  STALE_THRESHOLD_DAYS = 7;
584
663
  CONTEXT_FRESHNESS_SECONDS = 120;
664
+ DEFAULT_INDEX_WAIT_SECONDS = 20;
665
+ MIN_INDEX_WAIT_SECONDS = 15;
666
+ MAX_INDEX_WAIT_SECONDS = 20;
585
667
  DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"];
586
668
  isDirectRun = process.argv[1]?.includes("pre-tool-use") || process.argv[2] === "pre-tool-use";
587
669
  if (isDirectRun) {
@@ -1681,23 +1763,22 @@ Returns: \`indexed\` (true/false), \`last_indexed_at\`, \`file_count\`
1681
1763
  \u2192 Use \`search(mode="auto", query="...")\`
1682
1764
 
1683
1765
  **IF indexed=false OR last_indexed_at is stale (>7 days):**
1684
- \u2192 Use local tools (Glob/Grep/Read) directly
1685
- \u2192 OR run \`project(action="index")\` first, then search
1766
+ \u2192 Wait up to ~20s for background refresh, retry \`search(mode="auto", query="...")\`
1767
+ \u2192 After grace window: local tools are allowed if search still misses
1686
1768
 
1687
- **IF search returns 0 results or errors:**
1769
+ **IF search still returns 0 results or errors after retry/window:**
1688
1770
  \u2192 Fallback to local tools (Glob/Grep/Read)
1689
1771
 
1690
1772
  ### \u2705 When Local Tools (Glob/Grep/Read) Are OK:
1691
- - Project is NOT indexed
1692
- - Index is stale/outdated (>7 days)
1693
- - ContextStream search returns 0 results
1773
+ - Stale/not-indexed grace window has elapsed (~20s default, configurable)
1774
+ - ContextStream search still returns 0 results after retry
1694
1775
  - ContextStream returns errors
1695
1776
  - User explicitly requests local tools
1696
1777
 
1697
1778
  ### On Session Start:
1698
1779
  1. Call \`init(folder_path="...")\` - triggers initial indexing
1699
1780
  2. Check \`project(action="index_status")\` before searching
1700
- 3. If not indexed: use local tools OR wait for indexing
1781
+ 3. If not indexed: wait for background refresh (~20s), retry search, then use local tools only after the grace window
1701
1782
 
1702
1783
  ### After File Changes (Edit/Write/Create):
1703
1784
  Files are NOT auto-indexed. You MUST:
@@ -1832,13 +1913,12 @@ COMMON MEMORY CALLS: list docs via memory(action="list_docs"), list lessons via
1832
1913
  2. FOR CODE SEARCH: Check index status, then search appropriately
1833
1914
  \u26A0\uFE0F BEFORE searching: mcp__contextstream__project(action="index_status")
1834
1915
  \u2705 IF indexed & fresh: Use mcp__contextstream__search(mode="auto", query="...") BEFORE Glob/Grep/Read/Explore/Task/EnterPlanMode
1835
- \u2705 IF NOT indexed OR stale: Use local tools (Glob/Grep/Read) directly
1836
- \u2705 IF search returns 0 results: Fallback to local tools (Glob/Grep/Read)
1916
+ \u2705 IF NOT indexed OR stale: wait for background refresh up to ~20s, retry mcp__contextstream__search(mode="auto", query="..."), then allow local tools only after the grace window
1917
+ \u2705 IF search still returns 0 results after retry/window: fallback to local tools (Glob/Grep/Read)
1837
1918
 
1838
1919
  3. WHEN LOCAL TOOLS (Glob/Grep/Read) ARE OK:
1839
- \u2705 Project is NOT indexed (index_status.indexed=false)
1840
- \u2705 Index is stale/outdated (>7 days old)
1841
- \u2705 ContextStream search returns 0 results or errors
1920
+ \u2705 Stale/not-indexed grace window has elapsed (~20s default, configurable)
1921
+ \u2705 ContextStream search still returns 0 results or errors after retry
1842
1922
  \u2705 User explicitly requests local tools
1843
1923
 
1844
1924
  4. FOR PLANS & TASKS: Use ContextStream, not file-based plans
@@ -2540,7 +2620,7 @@ Use \`context()\` by default to get task-specific rules, lessons from past mista
2540
2620
  2. **PreToolUse blocking** - If you try to use Glob/Grep/Search/Explore:
2541
2621
  - Hook returns error: \`STOP: Use mcp__contextstream__search(mode="auto") instead\`
2542
2622
  - **You MUST use the suggested ContextStream tool instead**
2543
- - Local tools are only allowed if project is not indexed or ContextStream returns 0 results
2623
+ - For stale/not-indexed projects, wait for background refresh (up to ~20s), retry search, then allow local tools only after the grace window
2544
2624
 
2545
2625
  3. **PostToolUse indexing** - After Edit/Write operations:
2546
2626
  - Changed files are automatically re-indexed
@@ -2577,7 +2657,7 @@ STOP \u2192 Call search(mode="auto", query="...") FIRST
2577
2657
 
2578
2658
  \u2705 **ALWAYS DO THIS:**
2579
2659
  1. \`search(mode="auto", query="what you're looking for")\`
2580
- 2. Only use local tools (Glob/Grep/Read) if ContextStream returns **0 results**
2660
+ 2. Only use local tools (Glob/Grep/Read) after stale/not-indexed refresh grace window elapses (~20s) or ContextStream still returns **0 results** after retry
2581
2661
  3. Use Read ONLY for exact file edits after you know the file path
2582
2662
 
2583
2663
  This applies to **EVERY search** throughout the **ENTIRE conversation**, not just the first message.
@@ -2840,7 +2920,7 @@ session(action="capture", event_type="session_snapshot", title="Pre-compaction s
2840
2920
 
2841
2921
  ### Search & Code Intelligence (ContextStream-first)
2842
2922
 
2843
- \u26A0\uFE0F **STOP: Before using Search/Glob/Grep/Read/Explore** \u2192 Call \`search(mode="auto")\` FIRST. Use local tools ONLY if ContextStream returns 0 results.
2923
+ \u26A0\uFE0F **STOP: Before using Search/Glob/Grep/Read/Explore** \u2192 Call \`search(mode="auto")\` FIRST. For stale/not-indexed projects, wait ~20s for background refresh and retry search before local fallback.
2844
2924
 
2845
2925
  **\u274C WRONG workflow (wastes tokens, slow):**
2846
2926
  \`\`\`
@@ -2859,7 +2939,7 @@ search(mode="auto", query="function implementation") \u2192 done (results includ
2859
2939
  2. \`search(mode="auto", query="...", limit=3)\` or \`search(mode="keyword", query="<filename>", limit=3)\`
2860
2940
  3. \`project(action="files")\` - file tree/list (only when needed)
2861
2941
  4. \`graph(action="dependencies", ...)\` - code structure
2862
- 5. Local repo scans (rg/ls/find) - ONLY if ContextStream returns no results, errors, or the user explicitly asks
2942
+ 5. Local repo scans (rg/ls/find) - ONLY after refresh grace window/retry still yields no results/errors, or the user explicitly asks
2863
2943
 
2864
2944
  **Search Mode Selection:**
2865
2945
 
@@ -2890,7 +2970,7 @@ search(mode="auto", query="function implementation") \u2192 done (results includ
2890
2970
 
2891
2971
  **Search defaults:** \`search\` returns the top 3 results with compact snippets. Use \`limit\` + \`offset\` for pagination, and \`content_max_chars\` to expand snippets when needed.
2892
2972
 
2893
- If ContextStream returns results, stop and use them. NEVER use local Search/Explore/Read unless you need exact code edits or ContextStream returned 0 results.
2973
+ If ContextStream returns results, stop and use them. NEVER use local Search/Explore/Read unless you need exact code edits, or refresh grace window + retry still returns 0 results.
2894
2974
 
2895
2975
  **Code Analysis:**
2896
2976
  - Dependencies: \`graph(action="dependencies", file_path="...")\`
@@ -3019,7 +3099,7 @@ See full documentation: https://contextstream.io/docs/mcp/tools
3019
3099
  3. Narrow bypass: immediate read-only ContextStream calls are allowed only when prior context is fresh and no state-changing tool has run
3020
3100
 
3021
3101
  **BEFORE Glob/Grep/Read/Search/Explore/Task/EnterPlanMode:**
3022
- \u2192 \`search(mode="auto", query="...")\` FIRST \u2014 local tools ONLY if 0 results
3102
+ \u2192 \`search(mode="auto", query="...")\` FIRST \u2014 for stale/not-indexed, wait ~20s for refresh then retry before local fallback
3023
3103
 
3024
3104
  **HOOKS: \`<system-reminder>\` tags contain instructions \u2014 FOLLOW THEM**
3025
3105
  </contextstream_protocol>
@@ -3242,10 +3322,10 @@ search(mode="auto", query="what you're looking for")
3242
3322
  \u2192 Use this instead of Explore/Task/EnterPlanMode for file discovery.
3243
3323
 
3244
3324
  **IF project is NOT indexed or very stale (>7 days):**
3245
- \u2192 Use local tools (Glob/Grep/Read) directly
3325
+ \u2192 Wait up to ~20s for background refresh, retry \`search(mode="auto", ...)\`, then allow local tools only after the grace window
3246
3326
  \u2192 OR run \`project(action="index")\` first, then search
3247
3327
 
3248
- **IF ContextStream search returns 0 results or errors:**
3328
+ **IF ContextStream search still returns 0 results or errors after retry/window:**
3249
3329
  \u2192 Use local tools (Glob/Grep/Read) as fallback
3250
3330
 
3251
3331
  ### Choose Search Mode Intelligently:
@@ -3271,9 +3351,8 @@ search(mode="auto", query="what you're looking for")
3271
3351
  - Then use local Read/Grep only on paths returned by ContextStream.
3272
3352
 
3273
3353
  ### When Local Tools Are OK:
3274
- \u2705 Project is not indexed
3275
- \u2705 Index is stale/outdated (>7 days old)
3276
- \u2705 ContextStream search returns 0 results
3354
+ \u2705 Stale/not-indexed grace window has elapsed (~20s default, configurable)
3355
+ \u2705 ContextStream search still returns 0 results after retry
3277
3356
  \u2705 ContextStream returns errors
3278
3357
  \u2705 User explicitly requests local tools
3279
3358
 
@@ -3368,7 +3447,7 @@ After updating, user should restart their AI tool.
3368
3447
  2. Project is indexed and \`search(mode="auto", ...)\` is retried before local fallbacks
3369
3448
  3. Instructions file contains the current ContextStream managed block
3370
3449
  `;
3371
- NO_HOOKS_EDITORS = ["copilot", "codex", "opencode", "aider", "antigravity"];
3450
+ NO_HOOKS_EDITORS = ["copilot", "codex", "opencode", "aider", "antigravity", "kilo"];
3372
3451
  TEMPLATES = {
3373
3452
  codex: {
3374
3453
  filename: "AGENTS.md",
@@ -3410,7 +3489,7 @@ ${rules}
3410
3489
  `
3411
3490
  },
3412
3491
  kilo: {
3413
- filename: ".kilocode/rules/contextstream.md",
3492
+ filename: ".kilo/rules/contextstream.md",
3414
3493
  description: "Kilo Code AI rules",
3415
3494
  build: (rules) => `# Kilo Code Rules
3416
3495
  ${rules}
@@ -448,7 +448,7 @@ Use \`context()\` by default to get task-specific rules, lessons from past mista
448
448
  2. **PreToolUse blocking** - If you try to use Glob/Grep/Search/Explore:
449
449
  - Hook returns error: \`STOP: Use mcp__contextstream__search(mode="auto") instead\`
450
450
  - **You MUST use the suggested ContextStream tool instead**
451
- - Local tools are only allowed if project is not indexed or ContextStream returns 0 results
451
+ - For stale/not-indexed projects, wait for background refresh (up to ~20s), retry search, then allow local tools only after the grace window
452
452
 
453
453
  3. **PostToolUse indexing** - After Edit/Write operations:
454
454
  - Changed files are automatically re-indexed
@@ -485,7 +485,7 @@ STOP \u2192 Call search(mode="auto", query="...") FIRST
485
485
 
486
486
  \u2705 **ALWAYS DO THIS:**
487
487
  1. \`search(mode="auto", query="what you're looking for")\`
488
- 2. Only use local tools (Glob/Grep/Read) if ContextStream returns **0 results**
488
+ 2. Only use local tools (Glob/Grep/Read) after stale/not-indexed refresh grace window elapses (~20s) or ContextStream still returns **0 results** after retry
489
489
  3. Use Read ONLY for exact file edits after you know the file path
490
490
 
491
491
  This applies to **EVERY search** throughout the **ENTIRE conversation**, not just the first message.
@@ -748,7 +748,7 @@ session(action="capture", event_type="session_snapshot", title="Pre-compaction s
748
748
 
749
749
  ### Search & Code Intelligence (ContextStream-first)
750
750
 
751
- \u26A0\uFE0F **STOP: Before using Search/Glob/Grep/Read/Explore** \u2192 Call \`search(mode="auto")\` FIRST. Use local tools ONLY if ContextStream returns 0 results.
751
+ \u26A0\uFE0F **STOP: Before using Search/Glob/Grep/Read/Explore** \u2192 Call \`search(mode="auto")\` FIRST. For stale/not-indexed projects, wait ~20s for background refresh and retry search before local fallback.
752
752
 
753
753
  **\u274C WRONG workflow (wastes tokens, slow):**
754
754
  \`\`\`
@@ -767,7 +767,7 @@ search(mode="auto", query="function implementation") \u2192 done (results includ
767
767
  2. \`search(mode="auto", query="...", limit=3)\` or \`search(mode="keyword", query="<filename>", limit=3)\`
768
768
  3. \`project(action="files")\` - file tree/list (only when needed)
769
769
  4. \`graph(action="dependencies", ...)\` - code structure
770
- 5. Local repo scans (rg/ls/find) - ONLY if ContextStream returns no results, errors, or the user explicitly asks
770
+ 5. Local repo scans (rg/ls/find) - ONLY after refresh grace window/retry still yields no results/errors, or the user explicitly asks
771
771
 
772
772
  **Search Mode Selection:**
773
773
 
@@ -798,7 +798,7 @@ search(mode="auto", query="function implementation") \u2192 done (results includ
798
798
 
799
799
  **Search defaults:** \`search\` returns the top 3 results with compact snippets. Use \`limit\` + \`offset\` for pagination, and \`content_max_chars\` to expand snippets when needed.
800
800
 
801
- If ContextStream returns results, stop and use them. NEVER use local Search/Explore/Read unless you need exact code edits or ContextStream returned 0 results.
801
+ If ContextStream returns results, stop and use them. NEVER use local Search/Explore/Read unless you need exact code edits, or refresh grace window + retry still returns 0 results.
802
802
 
803
803
  **Code Analysis:**
804
804
  - Dependencies: \`graph(action="dependencies", file_path="...")\`
@@ -927,7 +927,7 @@ var CONTEXTSTREAM_RULES_MINIMAL = `
927
927
  3. Narrow bypass: immediate read-only ContextStream calls are allowed only when prior context is fresh and no state-changing tool has run
928
928
 
929
929
  **BEFORE Glob/Grep/Read/Search/Explore/Task/EnterPlanMode:**
930
- \u2192 \`search(mode="auto", query="...")\` FIRST \u2014 local tools ONLY if 0 results
930
+ \u2192 \`search(mode="auto", query="...")\` FIRST \u2014 for stale/not-indexed, wait ~20s for refresh then retry before local fallback
931
931
 
932
932
  **HOOKS: \`<system-reminder>\` tags contain instructions \u2014 FOLLOW THEM**
933
933
  </contextstream_protocol>
@@ -1150,10 +1150,10 @@ search(mode="auto", query="what you're looking for")
1150
1150
  \u2192 Use this instead of Explore/Task/EnterPlanMode for file discovery.
1151
1151
 
1152
1152
  **IF project is NOT indexed or very stale (>7 days):**
1153
- \u2192 Use local tools (Glob/Grep/Read) directly
1153
+ \u2192 Wait up to ~20s for background refresh, retry \`search(mode="auto", ...)\`, then allow local tools only after the grace window
1154
1154
  \u2192 OR run \`project(action="index")\` first, then search
1155
1155
 
1156
- **IF ContextStream search returns 0 results or errors:**
1156
+ **IF ContextStream search still returns 0 results or errors after retry/window:**
1157
1157
  \u2192 Use local tools (Glob/Grep/Read) as fallback
1158
1158
 
1159
1159
  ### Choose Search Mode Intelligently:
@@ -1179,9 +1179,8 @@ search(mode="auto", query="what you're looking for")
1179
1179
  - Then use local Read/Grep only on paths returned by ContextStream.
1180
1180
 
1181
1181
  ### When Local Tools Are OK:
1182
- \u2705 Project is not indexed
1183
- \u2705 Index is stale/outdated (>7 days old)
1184
- \u2705 ContextStream search returns 0 results
1182
+ \u2705 Stale/not-indexed grace window has elapsed (~20s default, configurable)
1183
+ \u2705 ContextStream search still returns 0 results after retry
1185
1184
  \u2705 ContextStream returns errors
1186
1185
  \u2705 User explicitly requests local tools
1187
1186
 
@@ -1276,7 +1275,7 @@ var ANTIGRAVITY_SUPPLEMENT = `
1276
1275
  2. Project is indexed and \`search(mode="auto", ...)\` is retried before local fallbacks
1277
1276
  3. Instructions file contains the current ContextStream managed block
1278
1277
  `;
1279
- var NO_HOOKS_EDITORS = ["copilot", "codex", "opencode", "aider", "antigravity"];
1278
+ var NO_HOOKS_EDITORS = ["copilot", "codex", "opencode", "aider", "antigravity", "kilo"];
1280
1279
  var TEMPLATES = {
1281
1280
  codex: {
1282
1281
  filename: "AGENTS.md",
@@ -1318,7 +1317,7 @@ ${rules}
1318
1317
  `
1319
1318
  },
1320
1319
  kilo: {
1321
- filename: ".kilocode/rules/contextstream.md",
1320
+ filename: ".kilo/rules/contextstream.md",
1322
1321
  description: "Kilo Code AI rules",
1323
1322
  build: (rules) => `# Kilo Code Rules
1324
1323
  ${rules}
@@ -1476,6 +1475,8 @@ function getOrCreateEntry(state, cwd) {
1476
1475
  require_init: false,
1477
1476
  last_context_at: void 0,
1478
1477
  last_state_change_at: void 0,
1478
+ index_wait_started_at: void 0,
1479
+ index_wait_until: void 0,
1479
1480
  updated_at: nowIso()
1480
1481
  };
1481
1482
  state.workspaces[cwd] = created;
@@ -235,6 +235,8 @@ function getOrCreateEntry(state, cwd) {
235
235
  require_init: false,
236
236
  last_context_at: void 0,
237
237
  last_state_change_at: void 0,
238
+ index_wait_started_at: void 0,
239
+ index_wait_until: void 0,
238
240
  updated_at: nowIso()
239
241
  };
240
242
  state.workspaces[cwd] = created;
@@ -298,13 +300,12 @@ var FULL_REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
298
300
  2. FOR CODE SEARCH: Check index status, then search appropriately
299
301
  \u26A0\uFE0F BEFORE searching: mcp__contextstream__project(action="index_status")
300
302
  \u2705 IF indexed & fresh: Use mcp__contextstream__search(mode="auto", query="...") BEFORE Glob/Grep/Read/Explore/Task/EnterPlanMode
301
- \u2705 IF NOT indexed OR stale: Use local tools (Glob/Grep/Read) directly
302
- \u2705 IF search returns 0 results: Fallback to local tools (Glob/Grep/Read)
303
+ \u2705 IF NOT indexed OR stale: wait for background refresh up to ~20s, retry mcp__contextstream__search(mode="auto", query="..."), then allow local tools only after the grace window
304
+ \u2705 IF search still returns 0 results after retry/window: fallback to local tools (Glob/Grep/Read)
303
305
 
304
306
  3. WHEN LOCAL TOOLS (Glob/Grep/Read) ARE OK:
305
- \u2705 Project is NOT indexed (index_status.indexed=false)
306
- \u2705 Index is stale/outdated (>7 days old)
307
- \u2705 ContextStream search returns 0 results or errors
307
+ \u2705 Stale/not-indexed grace window has elapsed (~20s default, configurable)
308
+ \u2705 ContextStream search still returns 0 results or errors after retry
308
309
  \u2705 User explicitly requests local tools
309
310
 
310
311
  4. FOR PLANS & TASKS: Use ContextStream, not file-based plans
@@ -727,23 +728,22 @@ Returns: \`indexed\` (true/false), \`last_indexed_at\`, \`file_count\`
727
728
  \u2192 Use \`search(mode="auto", query="...")\`
728
729
 
729
730
  **IF indexed=false OR last_indexed_at is stale (>7 days):**
730
- \u2192 Use local tools (Glob/Grep/Read) directly
731
- \u2192 OR run \`project(action="index")\` first, then search
731
+ \u2192 Wait up to ~20s for background refresh, retry \`search(mode="auto", query="...")\`
732
+ \u2192 After grace window: local tools are allowed if search still misses
732
733
 
733
- **IF search returns 0 results or errors:**
734
+ **IF search still returns 0 results or errors after retry/window:**
734
735
  \u2192 Fallback to local tools (Glob/Grep/Read)
735
736
 
736
737
  ### \u2705 When Local Tools (Glob/Grep/Read) Are OK:
737
- - Project is NOT indexed
738
- - Index is stale/outdated (>7 days)
739
- - ContextStream search returns 0 results
738
+ - Stale/not-indexed grace window has elapsed (~20s default, configurable)
739
+ - ContextStream search still returns 0 results after retry
740
740
  - ContextStream returns errors
741
741
  - User explicitly requests local tools
742
742
 
743
743
  ### On Session Start:
744
744
  1. Call \`init(folder_path="...")\` - triggers initial indexing
745
745
  2. Check \`project(action="index_status")\` before searching
746
- 3. If not indexed: use local tools OR wait for indexing
746
+ 3. If not indexed: wait for background refresh (~20s), retry search, then use local tools only after the grace window
747
747
 
748
748
  ### After File Changes (Edit/Write/Create):
749
749
  Files are NOT auto-indexed. You MUST: