@contextstream/mcp-server 0.4.61 → 0.4.63

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/dist/index.js CHANGED
@@ -335,7 +335,7 @@ function detectUpdateMethod() {
335
335
  return "curl";
336
336
  }
337
337
  async function runUpdate(method) {
338
- return new Promise((resolve16, reject) => {
338
+ return new Promise((resolve18, reject) => {
339
339
  let command;
340
340
  let args;
341
341
  let shell;
@@ -366,13 +366,13 @@ async function runUpdate(method) {
366
366
  });
367
367
  proc.on("close", (code) => {
368
368
  if (code === 0) {
369
- resolve16();
369
+ resolve18();
370
370
  } else {
371
371
  reject(new Error(`Update process exited with code ${code}`));
372
372
  }
373
373
  });
374
374
  proc.unref();
375
- setTimeout(() => resolve16(), 1e3);
375
+ setTimeout(() => resolve18(), 1e3);
376
376
  });
377
377
  }
378
378
  function writeUpdateMarker(previousVersion, newVersion) {
@@ -733,7 +733,7 @@ var require_ignore = __commonJS({
733
733
  // path matching.
734
734
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
735
735
  // @returns {TestResult} true if a file is ignored
736
- test(path22, checkUnignored, mode) {
736
+ test(path23, checkUnignored, mode) {
737
737
  let ignored = false;
738
738
  let unignored = false;
739
739
  let matchedRule;
@@ -742,7 +742,7 @@ var require_ignore = __commonJS({
742
742
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
743
743
  return;
744
744
  }
745
- const matched = rule[mode].test(path22);
745
+ const matched = rule[mode].test(path23);
746
746
  if (!matched) {
747
747
  return;
748
748
  }
@@ -763,17 +763,17 @@ var require_ignore = __commonJS({
763
763
  var throwError = (message, Ctor) => {
764
764
  throw new Ctor(message);
765
765
  };
766
- var checkPath = (path22, originalPath, doThrow) => {
767
- if (!isString(path22)) {
766
+ var checkPath = (path23, originalPath, doThrow) => {
767
+ if (!isString(path23)) {
768
768
  return doThrow(
769
769
  `path must be a string, but got \`${originalPath}\``,
770
770
  TypeError
771
771
  );
772
772
  }
773
- if (!path22) {
773
+ if (!path23) {
774
774
  return doThrow(`path must not be empty`, TypeError);
775
775
  }
776
- if (checkPath.isNotRelative(path22)) {
776
+ if (checkPath.isNotRelative(path23)) {
777
777
  const r = "`path.relative()`d";
778
778
  return doThrow(
779
779
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -782,7 +782,7 @@ var require_ignore = __commonJS({
782
782
  }
783
783
  return true;
784
784
  };
785
- var isNotRelative = (path22) => REGEX_TEST_INVALID_PATH.test(path22);
785
+ var isNotRelative = (path23) => REGEX_TEST_INVALID_PATH.test(path23);
786
786
  checkPath.isNotRelative = isNotRelative;
787
787
  checkPath.convert = (p) => p;
788
788
  var Ignore2 = class {
@@ -812,19 +812,19 @@ var require_ignore = __commonJS({
812
812
  }
813
813
  // @returns {TestResult}
814
814
  _test(originalPath, cache, checkUnignored, slices) {
815
- const path22 = originalPath && checkPath.convert(originalPath);
815
+ const path23 = originalPath && checkPath.convert(originalPath);
816
816
  checkPath(
817
- path22,
817
+ path23,
818
818
  originalPath,
819
819
  this._strictPathCheck ? throwError : RETURN_FALSE
820
820
  );
821
- return this._t(path22, cache, checkUnignored, slices);
821
+ return this._t(path23, cache, checkUnignored, slices);
822
822
  }
823
- checkIgnore(path22) {
824
- if (!REGEX_TEST_TRAILING_SLASH.test(path22)) {
825
- return this.test(path22);
823
+ checkIgnore(path23) {
824
+ if (!REGEX_TEST_TRAILING_SLASH.test(path23)) {
825
+ return this.test(path23);
826
826
  }
827
- const slices = path22.split(SLASH).filter(Boolean);
827
+ const slices = path23.split(SLASH).filter(Boolean);
828
828
  slices.pop();
829
829
  if (slices.length) {
830
830
  const parent = this._t(
@@ -837,18 +837,18 @@ var require_ignore = __commonJS({
837
837
  return parent;
838
838
  }
839
839
  }
840
- return this._rules.test(path22, false, MODE_CHECK_IGNORE);
840
+ return this._rules.test(path23, false, MODE_CHECK_IGNORE);
841
841
  }
842
- _t(path22, cache, checkUnignored, slices) {
843
- if (path22 in cache) {
844
- return cache[path22];
842
+ _t(path23, cache, checkUnignored, slices) {
843
+ if (path23 in cache) {
844
+ return cache[path23];
845
845
  }
846
846
  if (!slices) {
847
- slices = path22.split(SLASH).filter(Boolean);
847
+ slices = path23.split(SLASH).filter(Boolean);
848
848
  }
849
849
  slices.pop();
850
850
  if (!slices.length) {
851
- return cache[path22] = this._rules.test(path22, checkUnignored, MODE_IGNORE);
851
+ return cache[path23] = this._rules.test(path23, checkUnignored, MODE_IGNORE);
852
852
  }
853
853
  const parent = this._t(
854
854
  slices.join(SLASH) + SLASH,
@@ -856,29 +856,29 @@ var require_ignore = __commonJS({
856
856
  checkUnignored,
857
857
  slices
858
858
  );
859
- return cache[path22] = parent.ignored ? parent : this._rules.test(path22, checkUnignored, MODE_IGNORE);
859
+ return cache[path23] = parent.ignored ? parent : this._rules.test(path23, checkUnignored, MODE_IGNORE);
860
860
  }
861
- ignores(path22) {
862
- return this._test(path22, this._ignoreCache, false).ignored;
861
+ ignores(path23) {
862
+ return this._test(path23, this._ignoreCache, false).ignored;
863
863
  }
864
864
  createFilter() {
865
- return (path22) => !this.ignores(path22);
865
+ return (path23) => !this.ignores(path23);
866
866
  }
867
867
  filter(paths) {
868
868
  return makeArray(paths).filter(this.createFilter());
869
869
  }
870
870
  // @returns {TestResult}
871
- test(path22) {
872
- return this._test(path22, this._testCache, true);
871
+ test(path23) {
872
+ return this._test(path23, this._testCache, true);
873
873
  }
874
874
  };
875
875
  var factory = (options) => new Ignore2(options);
876
- var isPathValid = (path22) => checkPath(path22 && checkPath.convert(path22), path22, RETURN_FALSE);
876
+ var isPathValid = (path23) => checkPath(path23 && checkPath.convert(path23), path23, RETURN_FALSE);
877
877
  var setupWindows = () => {
878
878
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
879
879
  checkPath.convert = makePosix;
880
880
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
881
- checkPath.isNotRelative = (path22) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path22) || isNotRelative(path22);
881
+ checkPath.isNotRelative = (path23) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path23) || isNotRelative(path23);
882
882
  };
883
883
  if (
884
884
  // Detect `process` so that it can run in browsers.
@@ -1549,7 +1549,7 @@ var init_files = __esm({
1549
1549
  "Gemfile.lock",
1550
1550
  "composer.lock"
1551
1551
  ]);
1552
- MAX_FILE_SIZE = 1024 * 1024;
1552
+ MAX_FILE_SIZE = 5 * 1024 * 1024;
1553
1553
  MAX_BATCH_BYTES = 10 * 1024 * 1024;
1554
1554
  LARGE_FILE_THRESHOLD = 2 * 1024 * 1024;
1555
1555
  MAX_FILES_PER_BATCH = 200;
@@ -3280,7 +3280,7 @@ var init_rules_templates = __esm({
3280
3280
  | Message | Required |
3281
3281
  |---------|----------|
3282
3282
  | **1st message** | \`init()\` \u2192 \`context(user_message="...")\` |
3283
- | **Every message** | \`context(user_message="...")\` FIRST |
3283
+ | **Subsequent messages (default)** | \`context(user_message="...")\` FIRST (narrow read-only bypass when context is fresh and no state-changing tool has run) |
3284
3284
  | **Before file search** | \`search(mode="auto")\` BEFORE Glob/Grep/Read |
3285
3285
  </contextstream_rules>
3286
3286
 
@@ -3294,27 +3294,25 @@ v${RULES_VERSION}
3294
3294
  `.trim();
3295
3295
  CONTEXTSTREAM_RULES_DYNAMIC = CONTEXTSTREAM_RULES_BOOTSTRAP;
3296
3296
  CONTEXTSTREAM_RULES_FULL = `
3297
- ## \u{1F6A8} MANDATORY: CALL CONTEXT EVERY MESSAGE \u{1F6A8}
3297
+ ## \u{1F6A8} MANDATORY STARTUP: CONTEXT-FIRST FLOW \u{1F6A8}
3298
3298
 
3299
3299
  <contextstream_rules>
3300
- **EVERY response MUST start with \`context(user_message="...")\`**
3301
-
3302
3300
  | Message | What to Call |
3303
3301
  |---------|--------------|
3304
- | 1st message | \`init()\` \u2192 \`context(user_message="<msg>")\` |
3305
- | **EVERY message after** | \`context(user_message="<msg>")\` **FIRST** |
3306
-
3307
- **BEFORE Glob/Grep/Read/Search:** \u2192 \`search(mode="auto")\` FIRST
3302
+ | **First message in session** | \`init()\` \u2192 \`context(user_message="<msg>")\` BEFORE any other tool |
3303
+ | **Subsequent messages (default)** | \`context(user_message="<msg>")\` FIRST, then other tools |
3304
+ | **Narrow bypass** | Immediate read-only ContextStream calls are allowed only when prior context is fresh and no state-changing tool has run |
3305
+ | **Before Glob/Grep/Read/Search** | \`search(mode="auto", query="...")\` FIRST |
3308
3306
  </contextstream_rules>
3309
3307
 
3310
- **This block MUST appear at the start of EVERY response.** Failing to call \`context()\` means missing rules, lessons, and relevant context.
3308
+ Use \`context()\` by default to get task-specific rules, lessons from past mistakes, and relevant decisions.
3311
3309
 
3312
3310
  ---
3313
3311
 
3314
- ## Why \`context()\` is MANDATORY Every Message
3312
+ ## Why Default Context-First
3315
3313
 
3316
- \u274C **WRONG:** "I already called init, I don't need context"
3317
- \u2705 **CORRECT:** \`context()\` is required EVERY message, not just the first
3314
+ \u274C **Wrong:** "I already called init, so I can skip context for everything"
3315
+ \u2705 **Correct:** \`context()\` is the default first call for subsequent messages, with a narrow read-only bypass when context is still fresh and state is unchanged
3318
3316
 
3319
3317
  **What you lose without \`context()\`:**
3320
3318
  - Dynamic rules matched to your current task
@@ -3322,7 +3320,7 @@ v${RULES_VERSION}
3322
3320
  - Semantically relevant decisions and context
3323
3321
  - Warnings about risky operations
3324
3322
 
3325
- **\`init()\` gives recent items by TIME. \`context()\` finds items RELEVANT to THIS message.**
3323
+ **\`init()\` returns recent items by time. \`context()\` finds items semantically relevant to this message.**
3326
3324
 
3327
3325
  ---
3328
3326
 
@@ -3828,9 +3826,10 @@ See full documentation: https://contextstream.io/docs/mcp/tools
3828
3826
  ## \u{1F6A8} CRITICAL: These Rules Are Non-Negotiable \u{1F6A8}
3829
3827
 
3830
3828
  <contextstream_protocol>
3831
- **EVERY coding response MUST start with:**
3832
- 1. \`init()\` (1st message only) \u2192 then \`context(user_message="<msg>")\`
3833
- 2. \`context(user_message="<msg>")\` (EVERY subsequent message)
3829
+ **MANDATORY STARTUP:**
3830
+ 1. First message in session: \`init()\` \u2192 then \`context(user_message="<msg>")\`
3831
+ 2. Subsequent messages (default): \`context(user_message="<msg>")\` FIRST
3832
+ 3. Narrow bypass: immediate read-only ContextStream calls are allowed only when prior context is fresh and no state-changing tool has run
3834
3833
 
3835
3834
  **BEFORE Glob/Grep/Read/Search/Explore:**
3836
3835
  \u2192 \`search(mode="auto", query="...")\` FIRST \u2014 local tools ONLY if 0 results
@@ -3860,8 +3859,9 @@ Rules Version: ${RULES_VERSION}
3860
3859
 
3861
3860
  | When | Call |
3862
3861
  |------|------|
3863
- | 1st message | \`init()\` \u2192 \`context(user_message="...")\` |
3864
- | Every message after | \`context(user_message="...")\` |
3862
+ | 1st message in session | \`init()\` \u2192 \`context(user_message="...")\` |
3863
+ | Subsequent messages (default) | \`context(user_message="...")\` first |
3864
+ | Narrow bypass | Immediate read-only ContextStream calls when context is fresh and no state-changing tool has run |
3865
3865
  | Before ANY file discovery | \`search(mode="auto", query="...")\` |
3866
3866
  | On \`<system-reminder>\` | **Follow instructions inside** |
3867
3867
  | Save important decisions | \`session(action="capture", event_type="decision", ...)\` |
@@ -3884,12 +3884,10 @@ Rules Version: ${RULES_VERSION}
3884
3884
  - **[RULES_NOTICE]** \u2192 Run \`generate_rules()\`
3885
3885
  - **[VERSION_NOTICE]** \u2192 Tell user to update MCP
3886
3886
 
3887
- ## Fast Path (Simple Utilities Only)
3888
-
3889
- Skip init/context ONLY for: "list workspaces", "show version", "list reminders"
3890
- \u2192 Just call: \`workspace(action="list")\`, \`help(action="version")\`, etc.
3887
+ ## Read-Only Examples
3891
3888
 
3892
- Everything else = full protocol (init \u2192 context \u2192 search \u2192 work)
3889
+ Default behavior is context-first. Narrow bypass applies only for immediate read-only ContextStream calls when context is fresh and state is unchanged.
3890
+ Examples: \`workspace(action="list"|"get")\`, \`help(action="version"|"tools"|"auth")\`, \`project(action="index_status")\`.
3893
3891
 
3894
3892
  ### Lessons (Past Mistakes)
3895
3893
 
@@ -3978,7 +3976,7 @@ You MUST follow these rules manually - there is no automatic enforcement.
3978
3976
  - Check for [RULES_NOTICE] - update rules if needed
3979
3977
  - **save_exchange=true** saves each conversation turn for later retrieval
3980
3978
 
3981
- 4. **NEVER skip init/context** - you will miss critical context
3979
+ 4. **Default behavior:** call \`context(...)\` first on each message. Narrow bypass is allowed only for immediate read-only ContextStream calls when previous context is still fresh and no state-changing tool has run.
3982
3980
 
3983
3981
  ---
3984
3982
 
@@ -3987,7 +3985,7 @@ You MUST follow these rules manually - there is no automatic enforcement.
3987
3985
  **This editor does NOT have hooks to auto-save transcripts.**
3988
3986
  You MUST save each conversation turn manually:
3989
3987
 
3990
- ### On EVERY message (including the first):
3988
+ ### On MOST messages (including the first):
3991
3989
  \`\`\`
3992
3990
  context(user_message="<user's message>", save_exchange=true, session_id="<session-id>")
3993
3991
  \`\`\`
@@ -4062,6 +4060,28 @@ search(mode="auto", query="what you're looking for")
4062
4060
  **IF ContextStream search returns 0 results or errors:**
4063
4061
  \u2192 Use local tools (Glob/Grep/Read) as fallback
4064
4062
 
4063
+ ### Choose Search Mode Intelligently:
4064
+ - \`auto\` (recommended): query-aware mode selection
4065
+ - \`hybrid\`: mixed semantic + keyword retrieval for broad discovery
4066
+ - \`semantic\`: conceptual questions ("how does X work?")
4067
+ - \`keyword\`: exact text / quoted string
4068
+ - \`pattern\`: glob or regex (\`*.ts\`, \`foo\\s+bar\`)
4069
+ - \`refactor\`: symbol usage / rename-safe lookup
4070
+ - \`exhaustive\`: all occurrences / complete match coverage
4071
+ - \`team\`: cross-project team search
4072
+
4073
+ ### Output Format Hints:
4074
+ - Use \`output_format="paths"\` for file listings and rename targets
4075
+ - Use \`output_format="count"\` for "how many" queries
4076
+
4077
+ ### Two-Phase Search Pattern (for precision):
4078
+ - Pass 1 (discovery): \`search(mode="auto", query="<concept + module>", output_format="paths", limit=10)\`
4079
+ - Pass 2 (precision): use one of:
4080
+ - exact text/symbol: \`search(mode="keyword", query="\\"exact_text\\"", include_content=true)\`
4081
+ - symbol usage: \`search(mode="refactor", query="SymbolName", output_format="paths")\`
4082
+ - all occurrences: \`search(mode="exhaustive", query="symbol_or_text")\`
4083
+ - Then use local Read/Grep only on paths returned by ContextStream.
4084
+
4065
4085
  ### When Local Tools Are OK:
4066
4086
  \u2705 Project is not indexed
4067
4087
  \u2705 Index is stale/outdated (>7 days old)
@@ -4559,7 +4579,7 @@ var init_post_write = __esm({
4559
4579
  ".prisma",
4560
4580
  ".proto"
4561
4581
  ]);
4562
- MAX_FILE_SIZE2 = 1024 * 1024;
4582
+ MAX_FILE_SIZE2 = 5 * 1024 * 1024;
4563
4583
  isDirectRun = process.argv[1]?.includes("post-write") || process.argv[2] === "post-write";
4564
4584
  if (isDirectRun) {
4565
4585
  runPostWriteHook().catch(() => process.exit(0));
@@ -4567,14 +4587,181 @@ var init_post_write = __esm({
4567
4587
  }
4568
4588
  });
4569
4589
 
4590
+ // src/hooks/prompt-state.ts
4591
+ import * as fs9 from "node:fs";
4592
+ import * as path10 from "node:path";
4593
+ import { homedir as homedir8 } from "node:os";
4594
+ function defaultState() {
4595
+ return { workspaces: {} };
4596
+ }
4597
+ function nowIso() {
4598
+ return (/* @__PURE__ */ new Date()).toISOString();
4599
+ }
4600
+ function ensureStateDir() {
4601
+ try {
4602
+ fs9.mkdirSync(path10.dirname(STATE_PATH), { recursive: true });
4603
+ } catch {
4604
+ }
4605
+ }
4606
+ function normalizePath(input) {
4607
+ try {
4608
+ return path10.resolve(input);
4609
+ } catch {
4610
+ return input;
4611
+ }
4612
+ }
4613
+ function workspacePathsMatch(a, b) {
4614
+ const left = normalizePath(a);
4615
+ const right = normalizePath(b);
4616
+ return left === right || left.startsWith(`${right}${path10.sep}`) || right.startsWith(`${left}${path10.sep}`);
4617
+ }
4618
+ function readState() {
4619
+ try {
4620
+ const content = fs9.readFileSync(STATE_PATH, "utf8");
4621
+ const parsed = JSON.parse(content);
4622
+ if (!parsed || typeof parsed !== "object" || !parsed.workspaces) {
4623
+ return defaultState();
4624
+ }
4625
+ return parsed;
4626
+ } catch {
4627
+ return defaultState();
4628
+ }
4629
+ }
4630
+ function writeState(state) {
4631
+ try {
4632
+ ensureStateDir();
4633
+ fs9.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), "utf8");
4634
+ } catch {
4635
+ }
4636
+ }
4637
+ function getOrCreateEntry(state, cwd) {
4638
+ if (!cwd.trim()) return null;
4639
+ const exact = state.workspaces[cwd];
4640
+ if (exact) return { key: cwd, entry: exact };
4641
+ for (const [trackedCwd, trackedEntry] of Object.entries(state.workspaces)) {
4642
+ if (workspacePathsMatch(trackedCwd, cwd)) {
4643
+ return { key: trackedCwd, entry: trackedEntry };
4644
+ }
4645
+ }
4646
+ const created = {
4647
+ require_context: false,
4648
+ require_init: false,
4649
+ last_context_at: void 0,
4650
+ last_state_change_at: void 0,
4651
+ updated_at: nowIso()
4652
+ };
4653
+ state.workspaces[cwd] = created;
4654
+ return { key: cwd, entry: created };
4655
+ }
4656
+ function cleanupStale(maxAgeSeconds) {
4657
+ const state = readState();
4658
+ const now = Date.now();
4659
+ let changed = false;
4660
+ for (const [cwd, entry] of Object.entries(state.workspaces)) {
4661
+ const updated = new Date(entry.updated_at);
4662
+ if (Number.isNaN(updated.getTime())) continue;
4663
+ const ageSeconds = (now - updated.getTime()) / 1e3;
4664
+ if (ageSeconds > maxAgeSeconds) {
4665
+ delete state.workspaces[cwd];
4666
+ changed = true;
4667
+ }
4668
+ }
4669
+ if (changed) {
4670
+ writeState(state);
4671
+ }
4672
+ }
4673
+ function markContextRequired(cwd) {
4674
+ if (!cwd.trim()) return;
4675
+ const state = readState();
4676
+ const target = getOrCreateEntry(state, cwd);
4677
+ if (!target) return;
4678
+ target.entry.require_context = true;
4679
+ target.entry.updated_at = nowIso();
4680
+ writeState(state);
4681
+ }
4682
+ function clearContextRequired(cwd) {
4683
+ if (!cwd.trim()) return;
4684
+ const state = readState();
4685
+ const target = getOrCreateEntry(state, cwd);
4686
+ if (!target) return;
4687
+ target.entry.require_context = false;
4688
+ target.entry.last_context_at = nowIso();
4689
+ target.entry.updated_at = nowIso();
4690
+ writeState(state);
4691
+ }
4692
+ function isContextRequired(cwd) {
4693
+ if (!cwd.trim()) return false;
4694
+ const state = readState();
4695
+ const target = getOrCreateEntry(state, cwd);
4696
+ return Boolean(target?.entry.require_context);
4697
+ }
4698
+ function markInitRequired(cwd) {
4699
+ if (!cwd.trim()) return;
4700
+ const state = readState();
4701
+ const target = getOrCreateEntry(state, cwd);
4702
+ if (!target) return;
4703
+ target.entry.require_init = true;
4704
+ target.entry.updated_at = nowIso();
4705
+ writeState(state);
4706
+ }
4707
+ function clearInitRequired(cwd) {
4708
+ if (!cwd.trim()) return;
4709
+ const state = readState();
4710
+ const target = getOrCreateEntry(state, cwd);
4711
+ if (!target) return;
4712
+ target.entry.require_init = false;
4713
+ target.entry.updated_at = nowIso();
4714
+ writeState(state);
4715
+ }
4716
+ function isInitRequired(cwd) {
4717
+ if (!cwd.trim()) return false;
4718
+ const state = readState();
4719
+ const target = getOrCreateEntry(state, cwd);
4720
+ return Boolean(target?.entry.require_init);
4721
+ }
4722
+ function markStateChanged(cwd) {
4723
+ if (!cwd.trim()) return;
4724
+ const state = readState();
4725
+ const target = getOrCreateEntry(state, cwd);
4726
+ if (!target) return;
4727
+ target.entry.last_state_change_at = nowIso();
4728
+ target.entry.updated_at = nowIso();
4729
+ writeState(state);
4730
+ }
4731
+ function isContextFreshAndClean(cwd, maxAgeSeconds) {
4732
+ if (!cwd.trim()) return false;
4733
+ const state = readState();
4734
+ const target = getOrCreateEntry(state, cwd);
4735
+ const entry = target?.entry;
4736
+ if (!entry?.last_context_at) return false;
4737
+ const contextAt = new Date(entry.last_context_at);
4738
+ if (Number.isNaN(contextAt.getTime())) return false;
4739
+ const ageSeconds = (Date.now() - contextAt.getTime()) / 1e3;
4740
+ if (ageSeconds < 0 || ageSeconds > maxAgeSeconds) return false;
4741
+ if (entry.last_state_change_at) {
4742
+ const changedAt = new Date(entry.last_state_change_at);
4743
+ if (!Number.isNaN(changedAt.getTime()) && changedAt.getTime() > contextAt.getTime()) {
4744
+ return false;
4745
+ }
4746
+ }
4747
+ return true;
4748
+ }
4749
+ var STATE_PATH;
4750
+ var init_prompt_state = __esm({
4751
+ "src/hooks/prompt-state.ts"() {
4752
+ "use strict";
4753
+ STATE_PATH = path10.join(homedir8(), ".contextstream", "prompt-state.json");
4754
+ }
4755
+ });
4756
+
4570
4757
  // src/hooks/pre-tool-use.ts
4571
4758
  var pre_tool_use_exports = {};
4572
4759
  __export(pre_tool_use_exports, {
4573
4760
  runPreToolUseHook: () => runPreToolUseHook
4574
4761
  });
4575
- import * as fs9 from "node:fs";
4576
- import * as path10 from "node:path";
4577
- import { homedir as homedir8 } from "node:os";
4762
+ import * as fs10 from "node:fs";
4763
+ import * as path11 from "node:path";
4764
+ import { homedir as homedir9 } from "node:os";
4578
4765
  function isDiscoveryGlob(pattern) {
4579
4766
  const patternLower = pattern.toLowerCase();
4580
4767
  for (const p of DISCOVERY_PATTERNS) {
@@ -4600,22 +4787,22 @@ function isDiscoveryGrep(filePath) {
4600
4787
  return false;
4601
4788
  }
4602
4789
  function isProjectIndexed(cwd) {
4603
- if (!fs9.existsSync(INDEX_STATUS_FILE)) {
4790
+ if (!fs10.existsSync(INDEX_STATUS_FILE)) {
4604
4791
  return { isIndexed: false, isStale: false };
4605
4792
  }
4606
4793
  let data;
4607
4794
  try {
4608
- const content = fs9.readFileSync(INDEX_STATUS_FILE, "utf-8");
4795
+ const content = fs10.readFileSync(INDEX_STATUS_FILE, "utf-8");
4609
4796
  data = JSON.parse(content);
4610
4797
  } catch {
4611
4798
  return { isIndexed: false, isStale: false };
4612
4799
  }
4613
4800
  const projects = data.projects || {};
4614
- const cwdPath = path10.resolve(cwd);
4801
+ const cwdPath = path11.resolve(cwd);
4615
4802
  for (const [projectPath, info] of Object.entries(projects)) {
4616
4803
  try {
4617
- const indexedPath = path10.resolve(projectPath);
4618
- if (cwdPath === indexedPath || cwdPath.startsWith(indexedPath + path10.sep)) {
4804
+ const indexedPath = path11.resolve(projectPath);
4805
+ if (cwdPath === indexedPath || cwdPath.startsWith(indexedPath + path11.sep)) {
4619
4806
  const indexedAt = info.indexed_at;
4620
4807
  if (indexedAt) {
4621
4808
  try {
@@ -4648,6 +4835,97 @@ function extractToolName(input) {
4648
4835
  function extractToolInput(input) {
4649
4836
  return input.tool_input || input.parameters || input.toolParameters || {};
4650
4837
  }
4838
+ function normalizeContextstreamToolName(toolName) {
4839
+ const trimmed = toolName.trim();
4840
+ if (!trimmed) return null;
4841
+ const lower = trimmed.toLowerCase();
4842
+ const prefixed = "mcp__contextstream__";
4843
+ if (lower.startsWith(prefixed)) {
4844
+ return lower.slice(prefixed.length);
4845
+ }
4846
+ if (lower.startsWith("contextstream__")) {
4847
+ return lower.slice("contextstream__".length);
4848
+ }
4849
+ if (lower === "init" || lower === "context") {
4850
+ return lower;
4851
+ }
4852
+ return null;
4853
+ }
4854
+ function actionFromToolInput(toolInput) {
4855
+ const maybeAction = toolInput?.action;
4856
+ return typeof maybeAction === "string" ? maybeAction.trim().toLowerCase() : "";
4857
+ }
4858
+ function isContextstreamReadOnlyOperation(toolName, toolInput) {
4859
+ const action = actionFromToolInput(toolInput);
4860
+ switch (toolName) {
4861
+ case "workspace":
4862
+ return action === "list" || action === "get";
4863
+ case "memory":
4864
+ return action === "list_docs" || action === "list_events" || action === "list_todos" || action === "list_tasks" || action === "list_transcripts" || action === "list_nodes" || action === "decisions" || action === "get_doc" || action === "get_event" || action === "get_task" || action === "get_todo" || action === "get_transcript";
4865
+ case "session":
4866
+ return action === "get_lessons" || action === "get_plan" || action === "list_plans" || action === "recall";
4867
+ case "help":
4868
+ return action === "version" || action === "tools" || action === "auth";
4869
+ case "project":
4870
+ return action === "list" || action === "get" || action === "index_status";
4871
+ case "reminder":
4872
+ return action === "list" || action === "active";
4873
+ case "context":
4874
+ case "init":
4875
+ return true;
4876
+ default:
4877
+ return false;
4878
+ }
4879
+ }
4880
+ function isLikelyStateChangingTool(toolLower, toolInput, isContextstreamCall, normalizedContextstreamTool) {
4881
+ if (isContextstreamCall && normalizedContextstreamTool) {
4882
+ return !isContextstreamReadOnlyOperation(normalizedContextstreamTool, toolInput);
4883
+ }
4884
+ if ([
4885
+ "read",
4886
+ "read_file",
4887
+ "grep",
4888
+ "glob",
4889
+ "search",
4890
+ "grep_search",
4891
+ "code_search",
4892
+ "semanticsearch",
4893
+ "codebase_search",
4894
+ "list_files",
4895
+ "search_files",
4896
+ "search_files_content",
4897
+ "find_files",
4898
+ "find_by_name",
4899
+ "ls",
4900
+ "cat",
4901
+ "view"
4902
+ ].includes(toolLower)) {
4903
+ return false;
4904
+ }
4905
+ const writeMarkers = [
4906
+ "write",
4907
+ "edit",
4908
+ "create",
4909
+ "delete",
4910
+ "remove",
4911
+ "rename",
4912
+ "move",
4913
+ "patch",
4914
+ "apply",
4915
+ "insert",
4916
+ "append",
4917
+ "replace",
4918
+ "update",
4919
+ "commit",
4920
+ "push",
4921
+ "install",
4922
+ "exec",
4923
+ "run",
4924
+ "bash",
4925
+ "shell"
4926
+ ];
4927
+ return writeMarkers.some((marker) => toolLower.includes(marker));
4928
+ }
4651
4929
  function blockClaudeCode(message) {
4652
4930
  const response = {
4653
4931
  hookSpecificOutput: {
@@ -4656,7 +4934,7 @@ function blockClaudeCode(message) {
4656
4934
  additionalContext: `[CONTEXTSTREAM] ${message}`
4657
4935
  }
4658
4936
  };
4659
- fs9.appendFileSync(DEBUG_FILE, `[PreToolUse] REDIRECT (additionalContext): ${JSON.stringify(response)}
4937
+ fs10.appendFileSync(DEBUG_FILE, `[PreToolUse] REDIRECT (additionalContext): ${JSON.stringify(response)}
4660
4938
  `);
4661
4939
  console.log(JSON.stringify(response));
4662
4940
  process.exit(0);
@@ -4684,6 +4962,25 @@ function outputCursorAllow() {
4684
4962
  console.log(JSON.stringify({ decision: "allow" }));
4685
4963
  process.exit(0);
4686
4964
  }
4965
+ function blockWithMessage(editorFormat, message) {
4966
+ if (editorFormat === "cline") {
4967
+ outputClineBlock(message, "[CONTEXTSTREAM] Follow ContextStream startup requirements.");
4968
+ } else if (editorFormat === "cursor") {
4969
+ outputCursorBlock(message);
4970
+ }
4971
+ blockClaudeCode(message);
4972
+ }
4973
+ function allowTool(editorFormat, cwd, recordStateChange) {
4974
+ if (recordStateChange) {
4975
+ markStateChanged(cwd);
4976
+ }
4977
+ if (editorFormat === "cline") {
4978
+ outputClineAllow();
4979
+ } else if (editorFormat === "cursor") {
4980
+ outputCursorAllow();
4981
+ }
4982
+ process.exit(0);
4983
+ }
4687
4984
  function detectEditorFormat(input) {
4688
4985
  if (input.hookName !== void 0 || input.toolName !== void 0) {
4689
4986
  return "cline";
@@ -4694,11 +4991,11 @@ function detectEditorFormat(input) {
4694
4991
  return "claude";
4695
4992
  }
4696
4993
  async function runPreToolUseHook() {
4697
- fs9.appendFileSync(DEBUG_FILE, `[PreToolUse] Hook invoked at ${(/* @__PURE__ */ new Date()).toISOString()}
4994
+ fs10.appendFileSync(DEBUG_FILE, `[PreToolUse] Hook invoked at ${(/* @__PURE__ */ new Date()).toISOString()}
4698
4995
  `);
4699
4996
  console.error("[PreToolUse] Hook invoked at", (/* @__PURE__ */ new Date()).toISOString());
4700
4997
  if (!ENABLED2) {
4701
- fs9.appendFileSync(DEBUG_FILE, "[PreToolUse] Hook disabled, exiting\n");
4998
+ fs10.appendFileSync(DEBUG_FILE, "[PreToolUse] Hook disabled, exiting\n");
4702
4999
  console.error("[PreToolUse] Hook disabled, exiting");
4703
5000
  process.exit(0);
4704
5001
  }
@@ -4719,28 +5016,52 @@ async function runPreToolUseHook() {
4719
5016
  const cwd = extractCwd2(input);
4720
5017
  const tool = extractToolName(input);
4721
5018
  const toolInput = extractToolInput(input);
4722
- fs9.appendFileSync(DEBUG_FILE, `[PreToolUse] tool=${tool}, cwd=${cwd}, editorFormat=${editorFormat}
5019
+ const toolLower = tool.toLowerCase();
5020
+ const normalizedContextstreamTool = normalizeContextstreamToolName(tool);
5021
+ const isContextstreamCall = normalizedContextstreamTool !== null;
5022
+ const recordStateChange = isLikelyStateChangingTool(
5023
+ toolLower,
5024
+ toolInput,
5025
+ isContextstreamCall,
5026
+ normalizedContextstreamTool
5027
+ );
5028
+ fs10.appendFileSync(DEBUG_FILE, `[PreToolUse] tool=${tool}, cwd=${cwd}, editorFormat=${editorFormat}
4723
5029
  `);
5030
+ cleanupStale(180);
5031
+ if (isInitRequired(cwd)) {
5032
+ if (isContextstreamCall && normalizedContextstreamTool === "init") {
5033
+ clearInitRequired(cwd);
5034
+ } else {
5035
+ const required = "mcp__contextstream__init(...)";
5036
+ const msg = `First call required for this session: ${required}. Run it before any other MCP tool. Then call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>").`;
5037
+ blockWithMessage(editorFormat, msg);
5038
+ }
5039
+ }
5040
+ if (isContextRequired(cwd)) {
5041
+ if (isContextstreamCall && normalizedContextstreamTool === "context") {
5042
+ clearContextRequired(cwd);
5043
+ } else if (isContextstreamCall && normalizedContextstreamTool === "init") {
5044
+ } else if (isContextstreamCall && normalizedContextstreamTool && isContextstreamReadOnlyOperation(normalizedContextstreamTool, toolInput) && isContextFreshAndClean(cwd, CONTEXT_FRESHNESS_SECONDS)) {
5045
+ } else {
5046
+ const msg = 'First call required for this prompt: mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>"). Run it before any other MCP tool.';
5047
+ blockWithMessage(editorFormat, msg);
5048
+ }
5049
+ }
4724
5050
  const { isIndexed } = isProjectIndexed(cwd);
4725
- fs9.appendFileSync(DEBUG_FILE, `[PreToolUse] isIndexed=${isIndexed}
5051
+ fs10.appendFileSync(DEBUG_FILE, `[PreToolUse] isIndexed=${isIndexed}
4726
5052
  `);
4727
5053
  if (!isIndexed) {
4728
- fs9.appendFileSync(DEBUG_FILE, `[PreToolUse] Project not indexed, allowing
5054
+ fs10.appendFileSync(DEBUG_FILE, `[PreToolUse] Project not indexed, allowing
4729
5055
  `);
4730
- if (editorFormat === "cline") {
4731
- outputClineAllow();
4732
- } else if (editorFormat === "cursor") {
4733
- outputCursorAllow();
4734
- }
4735
- process.exit(0);
5056
+ allowTool(editorFormat, cwd, recordStateChange);
4736
5057
  }
4737
5058
  if (tool === "Glob") {
4738
5059
  const pattern = toolInput?.pattern || "";
4739
- fs9.appendFileSync(DEBUG_FILE, `[PreToolUse] Glob pattern=${pattern}, isDiscovery=${isDiscoveryGlob(pattern)}
5060
+ fs10.appendFileSync(DEBUG_FILE, `[PreToolUse] Glob pattern=${pattern}, isDiscovery=${isDiscoveryGlob(pattern)}
4740
5061
  `);
4741
5062
  if (isDiscoveryGlob(pattern)) {
4742
- const msg = `STOP: Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of Glob.`;
4743
- fs9.appendFileSync(DEBUG_FILE, `[PreToolUse] Intercepting discovery glob: ${msg}
5063
+ const msg = `This project index is current. Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of Glob for faster, richer code results.`;
5064
+ fs10.appendFileSync(DEBUG_FILE, `[PreToolUse] Intercepting discovery glob: ${msg}
4744
5065
  `);
4745
5066
  if (editorFormat === "cline") {
4746
5067
  outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
@@ -4762,7 +5083,7 @@ async function runPreToolUseHook() {
4762
5083
  }
4763
5084
  blockClaudeCode(msg);
4764
5085
  } else {
4765
- const msg = `STOP: Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool}.`;
5086
+ const msg = `This project index is current. Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool} for faster, richer code results.`;
4766
5087
  if (editorFormat === "cline") {
4767
5088
  outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
4768
5089
  } else if (editorFormat === "cursor") {
@@ -4774,7 +5095,7 @@ async function runPreToolUseHook() {
4774
5095
  } else if (tool === "Task") {
4775
5096
  const subagentType = toolInput?.subagent_type?.toLowerCase() || "";
4776
5097
  if (subagentType === "explore") {
4777
- const msg = 'STOP: Use mcp__contextstream__search(mode="auto") instead of Task(Explore).';
5098
+ const msg = 'Project index is current. Use mcp__contextstream__search(mode="auto") instead of Task(Explore) for broad discovery.';
4778
5099
  if (editorFormat === "cline") {
4779
5100
  outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
4780
5101
  } else if (editorFormat === "cursor") {
@@ -4783,7 +5104,7 @@ async function runPreToolUseHook() {
4783
5104
  blockClaudeCode(msg);
4784
5105
  }
4785
5106
  if (subagentType === "plan") {
4786
- const msg = 'STOP: Use mcp__contextstream__session(action="capture_plan") for planning. ContextStream plans persist across sessions.';
5107
+ const msg = 'After your plan is ready, save it with mcp__contextstream__session(action="capture_plan"). Then create tasks with mcp__contextstream__memory(action="create_task", title="...", plan_id="...").';
4787
5108
  if (editorFormat === "cline") {
4788
5109
  outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream plans for persistence.");
4789
5110
  } else if (editorFormat === "cursor") {
@@ -4792,7 +5113,7 @@ async function runPreToolUseHook() {
4792
5113
  blockClaudeCode(msg);
4793
5114
  }
4794
5115
  } else if (tool === "EnterPlanMode") {
4795
- const msg = 'STOP: Use mcp__contextstream__session(action="capture_plan", title="...", steps=[...]) instead of EnterPlanMode. ContextStream plans persist across sessions and are searchable.';
5116
+ const msg = 'After finalizing your plan, save it to ContextStream (not a local markdown file): mcp__contextstream__session(action="capture_plan", title="...", steps=[...]). Then create tasks with mcp__contextstream__memory(action="create_task", title="...", plan_id="...").';
4796
5117
  if (editorFormat === "cline") {
4797
5118
  outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream plans for persistence.");
4798
5119
  } else if (editorFormat === "cursor") {
@@ -4803,29 +5124,27 @@ async function runPreToolUseHook() {
4803
5124
  if (tool === "list_files" || tool === "search_files") {
4804
5125
  const pattern = toolInput?.path || toolInput?.regex || "";
4805
5126
  if (isDiscoveryGlob(pattern) || isDiscoveryGrep(pattern)) {
4806
- const msg = `Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool}. ContextStream search is indexed and faster.`;
5127
+ const msg = `Project index is current. Use mcp__contextstream__search(mode="auto", query="${pattern}") instead of ${tool} for faster, richer code results.`;
4807
5128
  if (editorFormat === "cline") {
4808
5129
  outputClineBlock(msg, "[CONTEXTSTREAM] Use ContextStream search for code discovery.");
4809
5130
  } else if (editorFormat === "cursor") {
4810
5131
  outputCursorBlock(msg);
4811
5132
  }
5133
+ blockClaudeCode(msg);
4812
5134
  }
4813
5135
  }
4814
- if (editorFormat === "cline") {
4815
- outputClineAllow();
4816
- } else if (editorFormat === "cursor") {
4817
- outputCursorAllow();
4818
- }
4819
- process.exit(0);
5136
+ allowTool(editorFormat, cwd, recordStateChange);
4820
5137
  }
4821
- var ENABLED2, INDEX_STATUS_FILE, DEBUG_FILE, STALE_THRESHOLD_DAYS, DISCOVERY_PATTERNS, isDirectRun2;
5138
+ var ENABLED2, INDEX_STATUS_FILE, DEBUG_FILE, STALE_THRESHOLD_DAYS, CONTEXT_FRESHNESS_SECONDS, DISCOVERY_PATTERNS, isDirectRun2;
4822
5139
  var init_pre_tool_use = __esm({
4823
5140
  "src/hooks/pre-tool-use.ts"() {
4824
5141
  "use strict";
5142
+ init_prompt_state();
4825
5143
  ENABLED2 = process.env.CONTEXTSTREAM_HOOK_ENABLED !== "false";
4826
- INDEX_STATUS_FILE = path10.join(homedir8(), ".contextstream", "indexed-projects.json");
5144
+ INDEX_STATUS_FILE = path11.join(homedir9(), ".contextstream", "indexed-projects.json");
4827
5145
  DEBUG_FILE = "/tmp/pretooluse-hook-debug.log";
4828
5146
  STALE_THRESHOLD_DAYS = 7;
5147
+ CONTEXT_FRESHNESS_SECONDS = 120;
4829
5148
  DISCOVERY_PATTERNS = ["**/*", "**/", "src/**", "lib/**", "app/**", "components/**"];
4830
5149
  isDirectRun2 = process.argv[1]?.includes("pre-tool-use") || process.argv[2] === "pre-tool-use";
4831
5150
  if (isDirectRun2) {
@@ -4839,17 +5158,17 @@ var user_prompt_submit_exports = {};
4839
5158
  __export(user_prompt_submit_exports, {
4840
5159
  runUserPromptSubmitHook: () => runUserPromptSubmitHook
4841
5160
  });
4842
- import * as fs10 from "node:fs";
4843
- import * as path11 from "node:path";
4844
- import { homedir as homedir9 } from "node:os";
5161
+ import * as fs11 from "node:fs";
5162
+ import * as path12 from "node:path";
5163
+ import { homedir as homedir10 } from "node:os";
4845
5164
  function loadConfigFromMcpJson(cwd) {
4846
- let searchDir = path11.resolve(cwd);
5165
+ let searchDir = path12.resolve(cwd);
4847
5166
  for (let i = 0; i < 5; i++) {
4848
5167
  if (!API_KEY2) {
4849
- const mcpPath = path11.join(searchDir, ".mcp.json");
4850
- if (fs10.existsSync(mcpPath)) {
5168
+ const mcpPath = path12.join(searchDir, ".mcp.json");
5169
+ if (fs11.existsSync(mcpPath)) {
4851
5170
  try {
4852
- const content = fs10.readFileSync(mcpPath, "utf-8");
5171
+ const content = fs11.readFileSync(mcpPath, "utf-8");
4853
5172
  const config = JSON.parse(content);
4854
5173
  const csEnv = config.mcpServers?.contextstream?.env;
4855
5174
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -4866,10 +5185,10 @@ function loadConfigFromMcpJson(cwd) {
4866
5185
  }
4867
5186
  }
4868
5187
  if (!WORKSPACE_ID || !PROJECT_ID) {
4869
- const csConfigPath = path11.join(searchDir, ".contextstream", "config.json");
4870
- if (fs10.existsSync(csConfigPath)) {
5188
+ const csConfigPath = path12.join(searchDir, ".contextstream", "config.json");
5189
+ if (fs11.existsSync(csConfigPath)) {
4871
5190
  try {
4872
- const content = fs10.readFileSync(csConfigPath, "utf-8");
5191
+ const content = fs11.readFileSync(csConfigPath, "utf-8");
4873
5192
  const csConfig = JSON.parse(content);
4874
5193
  if (csConfig.workspace_id && !WORKSPACE_ID) {
4875
5194
  WORKSPACE_ID = csConfig.workspace_id;
@@ -4881,15 +5200,15 @@ function loadConfigFromMcpJson(cwd) {
4881
5200
  }
4882
5201
  }
4883
5202
  }
4884
- const parentDir = path11.dirname(searchDir);
5203
+ const parentDir = path12.dirname(searchDir);
4885
5204
  if (parentDir === searchDir) break;
4886
5205
  searchDir = parentDir;
4887
5206
  }
4888
5207
  if (!API_KEY2) {
4889
- const homeMcpPath = path11.join(homedir9(), ".mcp.json");
4890
- if (fs10.existsSync(homeMcpPath)) {
5208
+ const homeMcpPath = path12.join(homedir10(), ".mcp.json");
5209
+ if (fs11.existsSync(homeMcpPath)) {
4891
5210
  try {
4892
- const content = fs10.readFileSync(homeMcpPath, "utf-8");
5211
+ const content = fs11.readFileSync(homeMcpPath, "utf-8");
4893
5212
  const config = JSON.parse(content);
4894
5213
  const csEnv = config.mcpServers?.contextstream?.env;
4895
5214
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -4905,7 +5224,7 @@ function loadConfigFromMcpJson(cwd) {
4905
5224
  }
4906
5225
  function readTranscriptFile(transcriptPath) {
4907
5226
  try {
4908
- const content = fs10.readFileSync(transcriptPath, "utf-8");
5227
+ const content = fs11.readFileSync(transcriptPath, "utf-8");
4909
5228
  const lines = content.trim().split("\n");
4910
5229
  const messages = [];
4911
5230
  for (const line of lines) {
@@ -5313,6 +5632,11 @@ async function runUserPromptSubmitHook() {
5313
5632
  }
5314
5633
  const editorFormat = detectEditorFormat2(input);
5315
5634
  const cwd = input.cwd || process.cwd();
5635
+ cleanupStale(180);
5636
+ markContextRequired(cwd);
5637
+ if (isNewSession(input, editorFormat)) {
5638
+ markInitRequired(cwd);
5639
+ }
5316
5640
  if (editorFormat === "claude") {
5317
5641
  loadConfigFromMcpJson(cwd);
5318
5642
  let context = REMINDER;
@@ -5369,20 +5693,23 @@ var init_user_prompt_submit = __esm({
5369
5693
  "src/hooks/user-prompt-submit.ts"() {
5370
5694
  "use strict";
5371
5695
  init_version();
5696
+ init_prompt_state();
5372
5697
  ENABLED3 = process.env.CONTEXTSTREAM_REMINDER_ENABLED !== "false";
5373
5698
  API_URL2 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
5374
5699
  API_KEY2 = process.env.CONTEXTSTREAM_API_KEY || "";
5375
5700
  WORKSPACE_ID = null;
5376
5701
  PROJECT_ID = null;
5377
- REMINDER = `[CONTEXTSTREAM] Call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>") FIRST before any other tool. Response contains dynamic rules, lessons, preferences. For search: use search(mode="auto") if indexed, else local tools.
5702
+ REMINDER = `[CONTEXTSTREAM] On the first message in every session call mcp__contextstream__init(...), then call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>") FIRST before any other tool. On subsequent messages, default to context first. Narrow bypass is allowed only for immediate read-only ContextStream calls when prior context is fresh and no state-changing tool has run. Response contains dynamic rules, lessons, preferences.
5703
+ COMMON MEMORY CALLS: list docs via memory(action="list_docs"), list lessons via session(action="get_lessons"), list plans via session(action="list_plans"), list tasks/todos via memory(action="list_tasks"|"list_todos").
5378
5704
  [END]`;
5379
5705
  FULL_REMINDER = `[CONTEXTSTREAM RULES - MANDATORY]
5380
5706
 
5381
- 1. FIRST: Call mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>") before ANY other tool
5707
+ 1. FIRST MESSAGE IN SESSION: Call mcp__contextstream__init(...) then mcp__contextstream__context(user_message="...", save_exchange=true, session_id="<session-id>")
5382
5708
  - Returns: dynamic rules, lessons from past mistakes, relevant context
5383
5709
  - Check response for: [LESSONS_WARNING], [RULES_NOTICE], preferences
5384
5710
  - save_exchange=true saves each conversation turn for later retrieval
5385
5711
  - Use a consistent session_id for the entire conversation (generate once on first message)
5712
+ - On subsequent messages, default to context() first. Narrow bypass: immediate read-only ContextStream calls when context is fresh and no state-changing tool has run.
5386
5713
 
5387
5714
  2. FOR CODE SEARCH: Check index status, then search appropriately
5388
5715
  \u26A0\uFE0F BEFORE searching: mcp__contextstream__project(action="index_status")
@@ -5533,17 +5860,17 @@ var pre_compact_exports = {};
5533
5860
  __export(pre_compact_exports, {
5534
5861
  runPreCompactHook: () => runPreCompactHook
5535
5862
  });
5536
- import * as fs11 from "node:fs";
5537
- import * as path12 from "node:path";
5538
- import { homedir as homedir10 } from "node:os";
5863
+ import * as fs12 from "node:fs";
5864
+ import * as path13 from "node:path";
5865
+ import { homedir as homedir11 } from "node:os";
5539
5866
  function loadConfigFromMcpJson2(cwd) {
5540
- let searchDir = path12.resolve(cwd);
5867
+ let searchDir = path13.resolve(cwd);
5541
5868
  for (let i = 0; i < 5; i++) {
5542
5869
  if (!API_KEY3) {
5543
- const mcpPath = path12.join(searchDir, ".mcp.json");
5544
- if (fs11.existsSync(mcpPath)) {
5870
+ const mcpPath = path13.join(searchDir, ".mcp.json");
5871
+ if (fs12.existsSync(mcpPath)) {
5545
5872
  try {
5546
- const content = fs11.readFileSync(mcpPath, "utf-8");
5873
+ const content = fs12.readFileSync(mcpPath, "utf-8");
5547
5874
  const config = JSON.parse(content);
5548
5875
  const csEnv = config.mcpServers?.contextstream?.env;
5549
5876
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -5557,10 +5884,10 @@ function loadConfigFromMcpJson2(cwd) {
5557
5884
  }
5558
5885
  }
5559
5886
  if (!WORKSPACE_ID2) {
5560
- const csConfigPath = path12.join(searchDir, ".contextstream", "config.json");
5561
- if (fs11.existsSync(csConfigPath)) {
5887
+ const csConfigPath = path13.join(searchDir, ".contextstream", "config.json");
5888
+ if (fs12.existsSync(csConfigPath)) {
5562
5889
  try {
5563
- const content = fs11.readFileSync(csConfigPath, "utf-8");
5890
+ const content = fs12.readFileSync(csConfigPath, "utf-8");
5564
5891
  const csConfig = JSON.parse(content);
5565
5892
  if (csConfig.workspace_id) {
5566
5893
  WORKSPACE_ID2 = csConfig.workspace_id;
@@ -5569,15 +5896,15 @@ function loadConfigFromMcpJson2(cwd) {
5569
5896
  }
5570
5897
  }
5571
5898
  }
5572
- const parentDir = path12.dirname(searchDir);
5899
+ const parentDir = path13.dirname(searchDir);
5573
5900
  if (parentDir === searchDir) break;
5574
5901
  searchDir = parentDir;
5575
5902
  }
5576
5903
  if (!API_KEY3) {
5577
- const homeMcpPath = path12.join(homedir10(), ".mcp.json");
5578
- if (fs11.existsSync(homeMcpPath)) {
5904
+ const homeMcpPath = path13.join(homedir11(), ".mcp.json");
5905
+ if (fs12.existsSync(homeMcpPath)) {
5579
5906
  try {
5580
- const content = fs11.readFileSync(homeMcpPath, "utf-8");
5907
+ const content = fs12.readFileSync(homeMcpPath, "utf-8");
5581
5908
  const config = JSON.parse(content);
5582
5909
  const csEnv = config.mcpServers?.contextstream?.env;
5583
5910
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -5599,7 +5926,7 @@ function parseTranscript(transcriptPath) {
5599
5926
  let startedAt = (/* @__PURE__ */ new Date()).toISOString();
5600
5927
  let firstTimestamp = true;
5601
5928
  try {
5602
- const content = fs11.readFileSync(transcriptPath, "utf-8");
5929
+ const content = fs12.readFileSync(transcriptPath, "utf-8");
5603
5930
  const lines = content.split("\n");
5604
5931
  for (const line of lines) {
5605
5932
  if (!line.trim()) continue;
@@ -5800,7 +6127,7 @@ async function runPreCompactHook() {
5800
6127
  messages: [],
5801
6128
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
5802
6129
  };
5803
- if (transcriptPath && fs11.existsSync(transcriptPath)) {
6130
+ if (transcriptPath && fs12.existsSync(transcriptPath)) {
5804
6131
  transcriptData = parseTranscript(transcriptPath);
5805
6132
  }
5806
6133
  let autoSaveStatus = "";
@@ -5859,13 +6186,13 @@ var auto_rules_exports = {};
5859
6186
  __export(auto_rules_exports, {
5860
6187
  runAutoRulesHook: () => runAutoRulesHook
5861
6188
  });
5862
- import * as fs12 from "node:fs";
5863
- import * as path13 from "node:path";
5864
- import { homedir as homedir11 } from "node:os";
6189
+ import * as fs13 from "node:fs";
6190
+ import * as path14 from "node:path";
6191
+ import { homedir as homedir12 } from "node:os";
5865
6192
  function hasRunRecently() {
5866
6193
  try {
5867
- if (!fs12.existsSync(MARKER_FILE)) return false;
5868
- const stat2 = fs12.statSync(MARKER_FILE);
6194
+ if (!fs13.existsSync(MARKER_FILE)) return false;
6195
+ const stat2 = fs13.statSync(MARKER_FILE);
5869
6196
  const age = Date.now() - stat2.mtimeMs;
5870
6197
  return age < COOLDOWN_MS;
5871
6198
  } catch {
@@ -5874,11 +6201,11 @@ function hasRunRecently() {
5874
6201
  }
5875
6202
  function markAsRan() {
5876
6203
  try {
5877
- const dir = path13.dirname(MARKER_FILE);
5878
- if (!fs12.existsSync(dir)) {
5879
- fs12.mkdirSync(dir, { recursive: true });
6204
+ const dir = path14.dirname(MARKER_FILE);
6205
+ if (!fs13.existsSync(dir)) {
6206
+ fs13.mkdirSync(dir, { recursive: true });
5880
6207
  }
5881
- fs12.writeFileSync(MARKER_FILE, (/* @__PURE__ */ new Date()).toISOString());
6208
+ fs13.writeFileSync(MARKER_FILE, (/* @__PURE__ */ new Date()).toISOString());
5882
6209
  } catch {
5883
6210
  }
5884
6211
  }
@@ -5907,8 +6234,8 @@ function extractCwd3(input) {
5907
6234
  }
5908
6235
  function hasPythonHooks(settingsPath) {
5909
6236
  try {
5910
- if (!fs12.existsSync(settingsPath)) return false;
5911
- const content = fs12.readFileSync(settingsPath, "utf-8");
6237
+ if (!fs13.existsSync(settingsPath)) return false;
6238
+ const content = fs13.readFileSync(settingsPath, "utf-8");
5912
6239
  const settings = JSON.parse(content);
5913
6240
  const hooks = settings.hooks;
5914
6241
  if (!hooks) return false;
@@ -5932,8 +6259,8 @@ function hasPythonHooks(settingsPath) {
5932
6259
  }
5933
6260
  }
5934
6261
  function detectPythonHooks(cwd) {
5935
- const globalSettingsPath = path13.join(homedir11(), ".claude", "settings.json");
5936
- const projectSettingsPath = path13.join(cwd, ".claude", "settings.json");
6262
+ const globalSettingsPath = path14.join(homedir12(), ".claude", "settings.json");
6263
+ const projectSettingsPath = path14.join(cwd, ".claude", "settings.json");
5937
6264
  return {
5938
6265
  global: hasPythonHooks(globalSettingsPath),
5939
6266
  project: hasPythonHooks(projectSettingsPath)
@@ -5998,7 +6325,7 @@ var init_auto_rules = __esm({
5998
6325
  API_URL4 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
5999
6326
  API_KEY4 = process.env.CONTEXTSTREAM_API_KEY || "";
6000
6327
  ENABLED6 = process.env.CONTEXTSTREAM_AUTO_RULES !== "false";
6001
- MARKER_FILE = path13.join(homedir11(), ".contextstream", ".auto-rules-ran");
6328
+ MARKER_FILE = path14.join(homedir12(), ".contextstream", ".auto-rules-ran");
6002
6329
  COOLDOWN_MS = 4 * 60 * 60 * 1e3;
6003
6330
  isDirectRun6 = process.argv[1]?.includes("auto-rules") || process.argv[2] === "auto-rules";
6004
6331
  if (isDirectRun6) {
@@ -6012,17 +6339,17 @@ var post_compact_exports = {};
6012
6339
  __export(post_compact_exports, {
6013
6340
  runPostCompactHook: () => runPostCompactHook
6014
6341
  });
6015
- import * as fs13 from "node:fs";
6016
- import * as path14 from "node:path";
6017
- import { homedir as homedir12 } from "node:os";
6342
+ import * as fs14 from "node:fs";
6343
+ import * as path15 from "node:path";
6344
+ import { homedir as homedir13 } from "node:os";
6018
6345
  function loadConfigFromMcpJson3(cwd) {
6019
- let searchDir = path14.resolve(cwd);
6346
+ let searchDir = path15.resolve(cwd);
6020
6347
  for (let i = 0; i < 5; i++) {
6021
6348
  if (!API_KEY5) {
6022
- const mcpPath = path14.join(searchDir, ".mcp.json");
6023
- if (fs13.existsSync(mcpPath)) {
6349
+ const mcpPath = path15.join(searchDir, ".mcp.json");
6350
+ if (fs14.existsSync(mcpPath)) {
6024
6351
  try {
6025
- const content = fs13.readFileSync(mcpPath, "utf-8");
6352
+ const content = fs14.readFileSync(mcpPath, "utf-8");
6026
6353
  const config = JSON.parse(content);
6027
6354
  const csEnv = config.mcpServers?.contextstream?.env;
6028
6355
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6036,10 +6363,10 @@ function loadConfigFromMcpJson3(cwd) {
6036
6363
  }
6037
6364
  }
6038
6365
  if (!WORKSPACE_ID3) {
6039
- const csConfigPath = path14.join(searchDir, ".contextstream", "config.json");
6040
- if (fs13.existsSync(csConfigPath)) {
6366
+ const csConfigPath = path15.join(searchDir, ".contextstream", "config.json");
6367
+ if (fs14.existsSync(csConfigPath)) {
6041
6368
  try {
6042
- const content = fs13.readFileSync(csConfigPath, "utf-8");
6369
+ const content = fs14.readFileSync(csConfigPath, "utf-8");
6043
6370
  const csConfig = JSON.parse(content);
6044
6371
  if (csConfig.workspace_id) {
6045
6372
  WORKSPACE_ID3 = csConfig.workspace_id;
@@ -6048,15 +6375,15 @@ function loadConfigFromMcpJson3(cwd) {
6048
6375
  }
6049
6376
  }
6050
6377
  }
6051
- const parentDir = path14.dirname(searchDir);
6378
+ const parentDir = path15.dirname(searchDir);
6052
6379
  if (parentDir === searchDir) break;
6053
6380
  searchDir = parentDir;
6054
6381
  }
6055
6382
  if (!API_KEY5) {
6056
- const homeMcpPath = path14.join(homedir12(), ".mcp.json");
6057
- if (fs13.existsSync(homeMcpPath)) {
6383
+ const homeMcpPath = path15.join(homedir13(), ".mcp.json");
6384
+ if (fs14.existsSync(homeMcpPath)) {
6058
6385
  try {
6059
- const content = fs13.readFileSync(homeMcpPath, "utf-8");
6386
+ const content = fs14.readFileSync(homeMcpPath, "utf-8");
6060
6387
  const config = JSON.parse(content);
6061
6388
  const csEnv = config.mcpServers?.contextstream?.env;
6062
6389
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6190,17 +6517,17 @@ var on_bash_exports = {};
6190
6517
  __export(on_bash_exports, {
6191
6518
  runOnBashHook: () => runOnBashHook
6192
6519
  });
6193
- import * as fs14 from "node:fs";
6194
- import * as path15 from "node:path";
6195
- import { homedir as homedir13 } from "node:os";
6520
+ import * as fs15 from "node:fs";
6521
+ import * as path16 from "node:path";
6522
+ import { homedir as homedir14 } from "node:os";
6196
6523
  function loadConfigFromMcpJson4(cwd) {
6197
- let searchDir = path15.resolve(cwd);
6524
+ let searchDir = path16.resolve(cwd);
6198
6525
  for (let i = 0; i < 5; i++) {
6199
6526
  if (!API_KEY6) {
6200
- const mcpPath = path15.join(searchDir, ".mcp.json");
6201
- if (fs14.existsSync(mcpPath)) {
6527
+ const mcpPath = path16.join(searchDir, ".mcp.json");
6528
+ if (fs15.existsSync(mcpPath)) {
6202
6529
  try {
6203
- const content = fs14.readFileSync(mcpPath, "utf-8");
6530
+ const content = fs15.readFileSync(mcpPath, "utf-8");
6204
6531
  const config = JSON.parse(content);
6205
6532
  const csEnv = config.mcpServers?.contextstream?.env;
6206
6533
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6214,10 +6541,10 @@ function loadConfigFromMcpJson4(cwd) {
6214
6541
  }
6215
6542
  }
6216
6543
  if (!WORKSPACE_ID4) {
6217
- const csConfigPath = path15.join(searchDir, ".contextstream", "config.json");
6218
- if (fs14.existsSync(csConfigPath)) {
6544
+ const csConfigPath = path16.join(searchDir, ".contextstream", "config.json");
6545
+ if (fs15.existsSync(csConfigPath)) {
6219
6546
  try {
6220
- const content = fs14.readFileSync(csConfigPath, "utf-8");
6547
+ const content = fs15.readFileSync(csConfigPath, "utf-8");
6221
6548
  const csConfig = JSON.parse(content);
6222
6549
  if (csConfig.workspace_id) {
6223
6550
  WORKSPACE_ID4 = csConfig.workspace_id;
@@ -6226,15 +6553,15 @@ function loadConfigFromMcpJson4(cwd) {
6226
6553
  }
6227
6554
  }
6228
6555
  }
6229
- const parentDir = path15.dirname(searchDir);
6556
+ const parentDir = path16.dirname(searchDir);
6230
6557
  if (parentDir === searchDir) break;
6231
6558
  searchDir = parentDir;
6232
6559
  }
6233
6560
  if (!API_KEY6) {
6234
- const homeMcpPath = path15.join(homedir13(), ".mcp.json");
6235
- if (fs14.existsSync(homeMcpPath)) {
6561
+ const homeMcpPath = path16.join(homedir14(), ".mcp.json");
6562
+ if (fs15.existsSync(homeMcpPath)) {
6236
6563
  try {
6237
- const content = fs14.readFileSync(homeMcpPath, "utf-8");
6564
+ const content = fs15.readFileSync(homeMcpPath, "utf-8");
6238
6565
  const config = JSON.parse(content);
6239
6566
  const csEnv = config.mcpServers?.contextstream?.env;
6240
6567
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6386,17 +6713,17 @@ var on_task_exports = {};
6386
6713
  __export(on_task_exports, {
6387
6714
  runOnTaskHook: () => runOnTaskHook
6388
6715
  });
6389
- import * as fs15 from "node:fs";
6390
- import * as path16 from "node:path";
6391
- import { homedir as homedir14 } from "node:os";
6716
+ import * as fs16 from "node:fs";
6717
+ import * as path17 from "node:path";
6718
+ import { homedir as homedir15 } from "node:os";
6392
6719
  function loadConfigFromMcpJson5(cwd) {
6393
- let searchDir = path16.resolve(cwd);
6720
+ let searchDir = path17.resolve(cwd);
6394
6721
  for (let i = 0; i < 5; i++) {
6395
6722
  if (!API_KEY7) {
6396
- const mcpPath = path16.join(searchDir, ".mcp.json");
6397
- if (fs15.existsSync(mcpPath)) {
6723
+ const mcpPath = path17.join(searchDir, ".mcp.json");
6724
+ if (fs16.existsSync(mcpPath)) {
6398
6725
  try {
6399
- const content = fs15.readFileSync(mcpPath, "utf-8");
6726
+ const content = fs16.readFileSync(mcpPath, "utf-8");
6400
6727
  const config = JSON.parse(content);
6401
6728
  const csEnv = config.mcpServers?.contextstream?.env;
6402
6729
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6410,10 +6737,10 @@ function loadConfigFromMcpJson5(cwd) {
6410
6737
  }
6411
6738
  }
6412
6739
  if (!WORKSPACE_ID5) {
6413
- const csConfigPath = path16.join(searchDir, ".contextstream", "config.json");
6414
- if (fs15.existsSync(csConfigPath)) {
6740
+ const csConfigPath = path17.join(searchDir, ".contextstream", "config.json");
6741
+ if (fs16.existsSync(csConfigPath)) {
6415
6742
  try {
6416
- const content = fs15.readFileSync(csConfigPath, "utf-8");
6743
+ const content = fs16.readFileSync(csConfigPath, "utf-8");
6417
6744
  const csConfig = JSON.parse(content);
6418
6745
  if (csConfig.workspace_id) {
6419
6746
  WORKSPACE_ID5 = csConfig.workspace_id;
@@ -6422,15 +6749,15 @@ function loadConfigFromMcpJson5(cwd) {
6422
6749
  }
6423
6750
  }
6424
6751
  }
6425
- const parentDir = path16.dirname(searchDir);
6752
+ const parentDir = path17.dirname(searchDir);
6426
6753
  if (parentDir === searchDir) break;
6427
6754
  searchDir = parentDir;
6428
6755
  }
6429
6756
  if (!API_KEY7) {
6430
- const homeMcpPath = path16.join(homedir14(), ".mcp.json");
6431
- if (fs15.existsSync(homeMcpPath)) {
6757
+ const homeMcpPath = path17.join(homedir15(), ".mcp.json");
6758
+ if (fs16.existsSync(homeMcpPath)) {
6432
6759
  try {
6433
- const content = fs15.readFileSync(homeMcpPath, "utf-8");
6760
+ const content = fs16.readFileSync(homeMcpPath, "utf-8");
6434
6761
  const config = JSON.parse(content);
6435
6762
  const csEnv = config.mcpServers?.contextstream?.env;
6436
6763
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6531,17 +6858,17 @@ var on_read_exports = {};
6531
6858
  __export(on_read_exports, {
6532
6859
  runOnReadHook: () => runOnReadHook
6533
6860
  });
6534
- import * as fs16 from "node:fs";
6535
- import * as path17 from "node:path";
6536
- import { homedir as homedir15 } from "node:os";
6861
+ import * as fs17 from "node:fs";
6862
+ import * as path18 from "node:path";
6863
+ import { homedir as homedir16 } from "node:os";
6537
6864
  function loadConfigFromMcpJson6(cwd) {
6538
- let searchDir = path17.resolve(cwd);
6865
+ let searchDir = path18.resolve(cwd);
6539
6866
  for (let i = 0; i < 5; i++) {
6540
6867
  if (!API_KEY8) {
6541
- const mcpPath = path17.join(searchDir, ".mcp.json");
6542
- if (fs16.existsSync(mcpPath)) {
6868
+ const mcpPath = path18.join(searchDir, ".mcp.json");
6869
+ if (fs17.existsSync(mcpPath)) {
6543
6870
  try {
6544
- const content = fs16.readFileSync(mcpPath, "utf-8");
6871
+ const content = fs17.readFileSync(mcpPath, "utf-8");
6545
6872
  const config = JSON.parse(content);
6546
6873
  const csEnv = config.mcpServers?.contextstream?.env;
6547
6874
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6555,10 +6882,10 @@ function loadConfigFromMcpJson6(cwd) {
6555
6882
  }
6556
6883
  }
6557
6884
  if (!WORKSPACE_ID6) {
6558
- const csConfigPath = path17.join(searchDir, ".contextstream", "config.json");
6559
- if (fs16.existsSync(csConfigPath)) {
6885
+ const csConfigPath = path18.join(searchDir, ".contextstream", "config.json");
6886
+ if (fs17.existsSync(csConfigPath)) {
6560
6887
  try {
6561
- const content = fs16.readFileSync(csConfigPath, "utf-8");
6888
+ const content = fs17.readFileSync(csConfigPath, "utf-8");
6562
6889
  const csConfig = JSON.parse(content);
6563
6890
  if (csConfig.workspace_id) {
6564
6891
  WORKSPACE_ID6 = csConfig.workspace_id;
@@ -6567,15 +6894,15 @@ function loadConfigFromMcpJson6(cwd) {
6567
6894
  }
6568
6895
  }
6569
6896
  }
6570
- const parentDir = path17.dirname(searchDir);
6897
+ const parentDir = path18.dirname(searchDir);
6571
6898
  if (parentDir === searchDir) break;
6572
6899
  searchDir = parentDir;
6573
6900
  }
6574
6901
  if (!API_KEY8) {
6575
- const homeMcpPath = path17.join(homedir15(), ".mcp.json");
6576
- if (fs16.existsSync(homeMcpPath)) {
6902
+ const homeMcpPath = path18.join(homedir16(), ".mcp.json");
6903
+ if (fs17.existsSync(homeMcpPath)) {
6577
6904
  try {
6578
- const content = fs16.readFileSync(homeMcpPath, "utf-8");
6905
+ const content = fs17.readFileSync(homeMcpPath, "utf-8");
6579
6906
  const config = JSON.parse(content);
6580
6907
  const csEnv = config.mcpServers?.contextstream?.env;
6581
6908
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6700,17 +7027,17 @@ var on_web_exports = {};
6700
7027
  __export(on_web_exports, {
6701
7028
  runOnWebHook: () => runOnWebHook
6702
7029
  });
6703
- import * as fs17 from "node:fs";
6704
- import * as path18 from "node:path";
6705
- import { homedir as homedir16 } from "node:os";
7030
+ import * as fs18 from "node:fs";
7031
+ import * as path19 from "node:path";
7032
+ import { homedir as homedir17 } from "node:os";
6706
7033
  function loadConfigFromMcpJson7(cwd) {
6707
- let searchDir = path18.resolve(cwd);
7034
+ let searchDir = path19.resolve(cwd);
6708
7035
  for (let i = 0; i < 5; i++) {
6709
7036
  if (!API_KEY9) {
6710
- const mcpPath = path18.join(searchDir, ".mcp.json");
6711
- if (fs17.existsSync(mcpPath)) {
7037
+ const mcpPath = path19.join(searchDir, ".mcp.json");
7038
+ if (fs18.existsSync(mcpPath)) {
6712
7039
  try {
6713
- const content = fs17.readFileSync(mcpPath, "utf-8");
7040
+ const content = fs18.readFileSync(mcpPath, "utf-8");
6714
7041
  const config = JSON.parse(content);
6715
7042
  const csEnv = config.mcpServers?.contextstream?.env;
6716
7043
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6724,10 +7051,10 @@ function loadConfigFromMcpJson7(cwd) {
6724
7051
  }
6725
7052
  }
6726
7053
  if (!WORKSPACE_ID7) {
6727
- const csConfigPath = path18.join(searchDir, ".contextstream", "config.json");
6728
- if (fs17.existsSync(csConfigPath)) {
7054
+ const csConfigPath = path19.join(searchDir, ".contextstream", "config.json");
7055
+ if (fs18.existsSync(csConfigPath)) {
6729
7056
  try {
6730
- const content = fs17.readFileSync(csConfigPath, "utf-8");
7057
+ const content = fs18.readFileSync(csConfigPath, "utf-8");
6731
7058
  const csConfig = JSON.parse(content);
6732
7059
  if (csConfig.workspace_id) {
6733
7060
  WORKSPACE_ID7 = csConfig.workspace_id;
@@ -6736,15 +7063,15 @@ function loadConfigFromMcpJson7(cwd) {
6736
7063
  }
6737
7064
  }
6738
7065
  }
6739
- const parentDir = path18.dirname(searchDir);
7066
+ const parentDir = path19.dirname(searchDir);
6740
7067
  if (parentDir === searchDir) break;
6741
7068
  searchDir = parentDir;
6742
7069
  }
6743
7070
  if (!API_KEY9) {
6744
- const homeMcpPath = path18.join(homedir16(), ".mcp.json");
6745
- if (fs17.existsSync(homeMcpPath)) {
7071
+ const homeMcpPath = path19.join(homedir17(), ".mcp.json");
7072
+ if (fs18.existsSync(homeMcpPath)) {
6746
7073
  try {
6747
- const content = fs17.readFileSync(homeMcpPath, "utf-8");
7074
+ const content = fs18.readFileSync(homeMcpPath, "utf-8");
6748
7075
  const config = JSON.parse(content);
6749
7076
  const csEnv = config.mcpServers?.contextstream?.env;
6750
7077
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6861,17 +7188,17 @@ var session_init_exports = {};
6861
7188
  __export(session_init_exports, {
6862
7189
  runSessionInitHook: () => runSessionInitHook
6863
7190
  });
6864
- import * as fs18 from "node:fs";
6865
- import * as path19 from "node:path";
6866
- import { homedir as homedir17 } from "node:os";
7191
+ import * as fs19 from "node:fs";
7192
+ import * as path20 from "node:path";
7193
+ import { homedir as homedir18 } from "node:os";
6867
7194
  function loadConfigFromMcpJson8(cwd) {
6868
- let searchDir = path19.resolve(cwd);
7195
+ let searchDir = path20.resolve(cwd);
6869
7196
  for (let i = 0; i < 5; i++) {
6870
7197
  if (!API_KEY10) {
6871
- const mcpPath = path19.join(searchDir, ".mcp.json");
6872
- if (fs18.existsSync(mcpPath)) {
7198
+ const mcpPath = path20.join(searchDir, ".mcp.json");
7199
+ if (fs19.existsSync(mcpPath)) {
6873
7200
  try {
6874
- const content = fs18.readFileSync(mcpPath, "utf-8");
7201
+ const content = fs19.readFileSync(mcpPath, "utf-8");
6875
7202
  const config = JSON.parse(content);
6876
7203
  const csEnv = config.mcpServers?.contextstream?.env;
6877
7204
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6888,10 +7215,10 @@ function loadConfigFromMcpJson8(cwd) {
6888
7215
  }
6889
7216
  }
6890
7217
  if (!WORKSPACE_ID8 || !PROJECT_ID2) {
6891
- const csConfigPath = path19.join(searchDir, ".contextstream", "config.json");
6892
- if (fs18.existsSync(csConfigPath)) {
7218
+ const csConfigPath = path20.join(searchDir, ".contextstream", "config.json");
7219
+ if (fs19.existsSync(csConfigPath)) {
6893
7220
  try {
6894
- const content = fs18.readFileSync(csConfigPath, "utf-8");
7221
+ const content = fs19.readFileSync(csConfigPath, "utf-8");
6895
7222
  const csConfig = JSON.parse(content);
6896
7223
  if (csConfig.workspace_id && !WORKSPACE_ID8) {
6897
7224
  WORKSPACE_ID8 = csConfig.workspace_id;
@@ -6903,15 +7230,15 @@ function loadConfigFromMcpJson8(cwd) {
6903
7230
  }
6904
7231
  }
6905
7232
  }
6906
- const parentDir = path19.dirname(searchDir);
7233
+ const parentDir = path20.dirname(searchDir);
6907
7234
  if (parentDir === searchDir) break;
6908
7235
  searchDir = parentDir;
6909
7236
  }
6910
7237
  if (!API_KEY10) {
6911
- const homeMcpPath = path19.join(homedir17(), ".mcp.json");
6912
- if (fs18.existsSync(homeMcpPath)) {
7238
+ const homeMcpPath = path20.join(homedir18(), ".mcp.json");
7239
+ if (fs19.existsSync(homeMcpPath)) {
6913
7240
  try {
6914
- const content = fs18.readFileSync(homeMcpPath, "utf-8");
7241
+ const content = fs19.readFileSync(homeMcpPath, "utf-8");
6915
7242
  const config = JSON.parse(content);
6916
7243
  const csEnv = config.mcpServers?.contextstream?.env;
6917
7244
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -6989,7 +7316,9 @@ function formatContext(ctx, options = {}) {
6989
7316
  }
6990
7317
  }
6991
7318
  if (!ctx) {
6992
- parts.push('\nNo stored context found. Call `mcp__contextstream__context(user_message="starting new session")` to initialize.');
7319
+ parts.push(
7320
+ '\nNo saved context found yet. On the first message in this session call `mcp__contextstream__init(...)` then `mcp__contextstream__context(user_message="starting new session")`.'
7321
+ );
6993
7322
  return parts.join("\n");
6994
7323
  }
6995
7324
  if (ctx.lessons && ctx.lessons.length > 0) {
@@ -7017,7 +7346,9 @@ function formatContext(ctx, options = {}) {
7017
7346
  }
7018
7347
  }
7019
7348
  parts.push("\n---");
7020
- parts.push('Call `mcp__contextstream__context(user_message="...")` for task-specific context.');
7349
+ parts.push(
7350
+ 'On the first message in a new session call `mcp__contextstream__init(...)` then `mcp__contextstream__context(user_message="...")`. After that, call `mcp__contextstream__context(user_message="...")` on every message.'
7351
+ );
7021
7352
  return parts.join("\n");
7022
7353
  }
7023
7354
  function regenerateRuleFiles(folderPath) {
@@ -7026,10 +7357,10 @@ function regenerateRuleFiles(folderPath) {
7026
7357
  for (const editor of editors) {
7027
7358
  const rule = generateRuleContent(editor, { mode: "bootstrap" });
7028
7359
  if (!rule) continue;
7029
- const filePath = path19.join(folderPath, rule.filename);
7030
- if (!fs18.existsSync(filePath)) continue;
7360
+ const filePath = path20.join(folderPath, rule.filename);
7361
+ if (!fs19.existsSync(filePath)) continue;
7031
7362
  try {
7032
- const existing = fs18.readFileSync(filePath, "utf8");
7363
+ const existing = fs19.readFileSync(filePath, "utf8");
7033
7364
  const startIdx = existing.indexOf(CONTEXTSTREAM_START_MARKER3);
7034
7365
  const endIdx = existing.indexOf(CONTEXTSTREAM_END_MARKER3);
7035
7366
  if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) continue;
@@ -7039,7 +7370,7 @@ function regenerateRuleFiles(folderPath) {
7039
7370
  ${rule.content.trim()}
7040
7371
  ${CONTEXTSTREAM_END_MARKER3}`;
7041
7372
  const merged = [before, newBlock, after].filter((p) => p.length > 0).join("\n\n");
7042
- fs18.writeFileSync(filePath, merged.trim() + "\n", "utf8");
7373
+ fs19.writeFileSync(filePath, merged.trim() + "\n", "utf8");
7043
7374
  updated++;
7044
7375
  } catch {
7045
7376
  }
@@ -7064,6 +7395,8 @@ async function runSessionInitHook() {
7064
7395
  process.exit(0);
7065
7396
  }
7066
7397
  const cwd = input.cwd || process.cwd();
7398
+ cleanupStale(360);
7399
+ markInitRequired(cwd);
7067
7400
  loadConfigFromMcpJson8(cwd);
7068
7401
  const updateMarker = checkUpdateMarker();
7069
7402
  if (updateMarker) {
@@ -7097,6 +7430,7 @@ var init_session_init = __esm({
7097
7430
  "use strict";
7098
7431
  init_version();
7099
7432
  init_rules_templates();
7433
+ init_prompt_state();
7100
7434
  ENABLED12 = process.env.CONTEXTSTREAM_SESSION_INIT_ENABLED !== "false";
7101
7435
  API_URL10 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
7102
7436
  API_KEY10 = process.env.CONTEXTSTREAM_API_KEY || "";
@@ -7116,17 +7450,17 @@ var session_end_exports = {};
7116
7450
  __export(session_end_exports, {
7117
7451
  runSessionEndHook: () => runSessionEndHook
7118
7452
  });
7119
- import * as fs19 from "node:fs";
7120
- import * as path20 from "node:path";
7121
- import { homedir as homedir18 } from "node:os";
7453
+ import * as fs20 from "node:fs";
7454
+ import * as path21 from "node:path";
7455
+ import { homedir as homedir19 } from "node:os";
7122
7456
  function loadConfigFromMcpJson9(cwd) {
7123
- let searchDir = path20.resolve(cwd);
7457
+ let searchDir = path21.resolve(cwd);
7124
7458
  for (let i = 0; i < 5; i++) {
7125
7459
  if (!API_KEY11) {
7126
- const mcpPath = path20.join(searchDir, ".mcp.json");
7127
- if (fs19.existsSync(mcpPath)) {
7460
+ const mcpPath = path21.join(searchDir, ".mcp.json");
7461
+ if (fs20.existsSync(mcpPath)) {
7128
7462
  try {
7129
- const content = fs19.readFileSync(mcpPath, "utf-8");
7463
+ const content = fs20.readFileSync(mcpPath, "utf-8");
7130
7464
  const config = JSON.parse(content);
7131
7465
  const csEnv = config.mcpServers?.contextstream?.env;
7132
7466
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -7140,10 +7474,10 @@ function loadConfigFromMcpJson9(cwd) {
7140
7474
  }
7141
7475
  }
7142
7476
  if (!WORKSPACE_ID9 || !PROJECT_ID3) {
7143
- const csConfigPath = path20.join(searchDir, ".contextstream", "config.json");
7144
- if (fs19.existsSync(csConfigPath)) {
7477
+ const csConfigPath = path21.join(searchDir, ".contextstream", "config.json");
7478
+ if (fs20.existsSync(csConfigPath)) {
7145
7479
  try {
7146
- const content = fs19.readFileSync(csConfigPath, "utf-8");
7480
+ const content = fs20.readFileSync(csConfigPath, "utf-8");
7147
7481
  const csConfig = JSON.parse(content);
7148
7482
  if (csConfig.workspace_id && !WORKSPACE_ID9) {
7149
7483
  WORKSPACE_ID9 = csConfig.workspace_id;
@@ -7155,15 +7489,15 @@ function loadConfigFromMcpJson9(cwd) {
7155
7489
  }
7156
7490
  }
7157
7491
  }
7158
- const parentDir = path20.dirname(searchDir);
7492
+ const parentDir = path21.dirname(searchDir);
7159
7493
  if (parentDir === searchDir) break;
7160
7494
  searchDir = parentDir;
7161
7495
  }
7162
7496
  if (!API_KEY11) {
7163
- const homeMcpPath = path20.join(homedir18(), ".mcp.json");
7164
- if (fs19.existsSync(homeMcpPath)) {
7497
+ const homeMcpPath = path21.join(homedir19(), ".mcp.json");
7498
+ if (fs20.existsSync(homeMcpPath)) {
7165
7499
  try {
7166
- const content = fs19.readFileSync(homeMcpPath, "utf-8");
7500
+ const content = fs20.readFileSync(homeMcpPath, "utf-8");
7167
7501
  const config = JSON.parse(content);
7168
7502
  const csEnv = config.mcpServers?.contextstream?.env;
7169
7503
  if (csEnv?.CONTEXTSTREAM_API_KEY) {
@@ -7186,11 +7520,11 @@ function parseTranscriptStats(transcriptPath) {
7186
7520
  messages: [],
7187
7521
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
7188
7522
  };
7189
- if (!transcriptPath || !fs19.existsSync(transcriptPath)) {
7523
+ if (!transcriptPath || !fs20.existsSync(transcriptPath)) {
7190
7524
  return stats;
7191
7525
  }
7192
7526
  try {
7193
- const content = fs19.readFileSync(transcriptPath, "utf-8");
7527
+ const content = fs20.readFileSync(transcriptPath, "utf-8");
7194
7528
  const lines = content.split("\n");
7195
7529
  let firstTimestamp = null;
7196
7530
  let lastTimestamp = null;
@@ -7548,9 +7882,9 @@ __export(verify_key_exports, {
7548
7882
  runVerifyKey: () => runVerifyKey,
7549
7883
  validateApiKey: () => validateApiKey
7550
7884
  });
7551
- import * as fs20 from "node:fs";
7552
- import * as path21 from "node:path";
7553
- import { homedir as homedir19 } from "node:os";
7885
+ import * as fs21 from "node:fs";
7886
+ import * as path22 from "node:path";
7887
+ import { homedir as homedir20 } from "node:os";
7554
7888
  function maskApiKey2(key) {
7555
7889
  if (!key || key.length < 8) return "***";
7556
7890
  const prefixMatch = key.match(/^([a-z]{2,3}_)/i);
@@ -7583,11 +7917,11 @@ function extractFromMcpConfig(config) {
7583
7917
  function getClaudeDesktopConfigPath() {
7584
7918
  const platform2 = process.platform;
7585
7919
  if (platform2 === "darwin") {
7586
- return path21.join(homedir19(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
7920
+ return path22.join(homedir20(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
7587
7921
  } else if (platform2 === "win32") {
7588
- return path21.join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
7922
+ return path22.join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
7589
7923
  } else {
7590
- return path21.join(homedir19(), ".config", "Claude", "claude_desktop_config.json");
7924
+ return path22.join(homedir20(), ".config", "Claude", "claude_desktop_config.json");
7591
7925
  }
7592
7926
  }
7593
7927
  function loadApiKey() {
@@ -7604,10 +7938,10 @@ function loadApiKey() {
7604
7938
  }
7605
7939
  let searchDir = process.cwd();
7606
7940
  for (let i = 0; i < 5; i++) {
7607
- const projectMcpPath = path21.join(searchDir, ".mcp.json");
7608
- if (fs20.existsSync(projectMcpPath)) {
7941
+ const projectMcpPath = path22.join(searchDir, ".mcp.json");
7942
+ if (fs21.existsSync(projectMcpPath)) {
7609
7943
  try {
7610
- const content = fs20.readFileSync(projectMcpPath, "utf-8");
7944
+ const content = fs21.readFileSync(projectMcpPath, "utf-8");
7611
7945
  const config = JSON.parse(content);
7612
7946
  const extracted = extractFromMcpConfig(config);
7613
7947
  if (extracted.apiKey) {
@@ -7621,14 +7955,14 @@ function loadApiKey() {
7621
7955
  } catch {
7622
7956
  }
7623
7957
  }
7624
- const parentDir = path21.dirname(searchDir);
7958
+ const parentDir = path22.dirname(searchDir);
7625
7959
  if (parentDir === searchDir) break;
7626
7960
  searchDir = parentDir;
7627
7961
  }
7628
- const globalMcpPath = path21.join(homedir19(), ".mcp.json");
7629
- if (fs20.existsSync(globalMcpPath)) {
7962
+ const globalMcpPath = path22.join(homedir20(), ".mcp.json");
7963
+ if (fs21.existsSync(globalMcpPath)) {
7630
7964
  try {
7631
- const content = fs20.readFileSync(globalMcpPath, "utf-8");
7965
+ const content = fs21.readFileSync(globalMcpPath, "utf-8");
7632
7966
  const config = JSON.parse(content);
7633
7967
  const extracted = extractFromMcpConfig(config);
7634
7968
  if (extracted.apiKey) {
@@ -7643,13 +7977,13 @@ function loadApiKey() {
7643
7977
  }
7644
7978
  }
7645
7979
  const cursorPaths = [
7646
- path21.join(process.cwd(), ".cursor", "mcp.json"),
7647
- path21.join(homedir19(), ".cursor", "mcp.json")
7980
+ path22.join(process.cwd(), ".cursor", "mcp.json"),
7981
+ path22.join(homedir20(), ".cursor", "mcp.json")
7648
7982
  ];
7649
7983
  for (const cursorPath of cursorPaths) {
7650
- if (fs20.existsSync(cursorPath)) {
7984
+ if (fs21.existsSync(cursorPath)) {
7651
7985
  try {
7652
- const content = fs20.readFileSync(cursorPath, "utf-8");
7986
+ const content = fs21.readFileSync(cursorPath, "utf-8");
7653
7987
  const config = JSON.parse(content);
7654
7988
  const extracted = extractFromMcpConfig(config);
7655
7989
  if (extracted.apiKey) {
@@ -7665,9 +7999,9 @@ function loadApiKey() {
7665
7999
  }
7666
8000
  }
7667
8001
  const claudeDesktopPath = getClaudeDesktopConfigPath();
7668
- if (fs20.existsSync(claudeDesktopPath)) {
8002
+ if (fs21.existsSync(claudeDesktopPath)) {
7669
8003
  try {
7670
- const content = fs20.readFileSync(claudeDesktopPath, "utf-8");
8004
+ const content = fs21.readFileSync(claudeDesktopPath, "utf-8");
7671
8005
  const config = JSON.parse(content);
7672
8006
  const extracted = extractFromMcpConfig(config);
7673
8007
  if (extracted.apiKey) {
@@ -7682,14 +8016,14 @@ function loadApiKey() {
7682
8016
  }
7683
8017
  }
7684
8018
  const vscodePaths = [
7685
- path21.join(homedir19(), ".vscode", "mcp.json"),
7686
- path21.join(homedir19(), ".codeium", "windsurf", "mcp_config.json"),
7687
- path21.join(homedir19(), ".continue", "config.json")
8019
+ path22.join(homedir20(), ".vscode", "mcp.json"),
8020
+ path22.join(homedir20(), ".codeium", "windsurf", "mcp_config.json"),
8021
+ path22.join(homedir20(), ".continue", "config.json")
7688
8022
  ];
7689
8023
  for (const vsPath of vscodePaths) {
7690
- if (fs20.existsSync(vsPath)) {
8024
+ if (fs21.existsSync(vsPath)) {
7691
8025
  try {
7692
- const content = fs20.readFileSync(vsPath, "utf-8");
8026
+ const content = fs21.readFileSync(vsPath, "utf-8");
7693
8027
  const config = JSON.parse(content);
7694
8028
  const extracted = extractFromMcpConfig(config);
7695
8029
  if (extracted.apiKey) {
@@ -7704,10 +8038,10 @@ function loadApiKey() {
7704
8038
  }
7705
8039
  }
7706
8040
  }
7707
- const credentialsPath = path21.join(homedir19(), ".contextstream", "credentials.json");
7708
- if (fs20.existsSync(credentialsPath)) {
8041
+ const credentialsPath = path22.join(homedir20(), ".contextstream", "credentials.json");
8042
+ if (fs21.existsSync(credentialsPath)) {
7709
8043
  try {
7710
- const content = fs20.readFileSync(credentialsPath, "utf-8");
8044
+ const content = fs21.readFileSync(credentialsPath, "utf-8");
7711
8045
  const creds = JSON.parse(content);
7712
8046
  if (creds.api_key) {
7713
8047
  apiKey = creds.api_key;
@@ -8305,8 +8639,8 @@ function getErrorMap() {
8305
8639
 
8306
8640
  // node_modules/zod/v3/helpers/parseUtil.js
8307
8641
  var makeIssue = (params) => {
8308
- const { data, path: path22, errorMaps, issueData } = params;
8309
- const fullPath = [...path22, ...issueData.path || []];
8642
+ const { data, path: path23, errorMaps, issueData } = params;
8643
+ const fullPath = [...path23, ...issueData.path || []];
8310
8644
  const fullIssue = {
8311
8645
  ...issueData,
8312
8646
  path: fullPath
@@ -8422,11 +8756,11 @@ var errorUtil;
8422
8756
 
8423
8757
  // node_modules/zod/v3/types.js
8424
8758
  var ParseInputLazyPath = class {
8425
- constructor(parent, value, path22, key) {
8759
+ constructor(parent, value, path23, key) {
8426
8760
  this._cachedPath = [];
8427
8761
  this.parent = parent;
8428
8762
  this.data = value;
8429
- this._path = path22;
8763
+ this._path = path23;
8430
8764
  this._key = key;
8431
8765
  }
8432
8766
  get path() {
@@ -11991,14 +12325,14 @@ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504])
11991
12325
  var MAX_RETRIES = 3;
11992
12326
  var BASE_DELAY = 1e3;
11993
12327
  async function sleep(ms) {
11994
- return new Promise((resolve16) => setTimeout(resolve16, ms));
12328
+ return new Promise((resolve18) => setTimeout(resolve18, ms));
11995
12329
  }
11996
- async function request(config, path22, options = {}) {
12330
+ async function request(config, path23, options = {}) {
11997
12331
  const { apiUrl, userAgent } = config;
11998
12332
  const authOverride = getAuthOverride();
11999
12333
  const apiKey = authOverride?.apiKey ?? config.apiKey;
12000
12334
  const jwt = authOverride?.jwt ?? config.jwt;
12001
- const rawPath = path22.startsWith("/") ? path22 : `/${path22}`;
12335
+ const rawPath = path23.startsWith("/") ? path23 : `/${path23}`;
12002
12336
  const apiPath = rawPath.startsWith("/api/") ? rawPath : `/api/v1${rawPath}`;
12003
12337
  const unauthenticatedEndpoints = ["/api/v1/auth/device/start", "/api/v1/auth/device/token"];
12004
12338
  const isUnauthenticatedEndpoint = unauthenticatedEndpoints.some(
@@ -12153,9 +12487,9 @@ function extractErrorCode(payload) {
12153
12487
  if (typeof payload.code === "string" && payload.code.trim()) return payload.code.trim();
12154
12488
  return null;
12155
12489
  }
12156
- function detectIntegrationProvider(path22) {
12157
- if (/\/github(\/|$)/i.test(path22)) return "github";
12158
- if (/\/slack(\/|$)/i.test(path22)) return "slack";
12490
+ function detectIntegrationProvider(path23) {
12491
+ if (/\/github(\/|$)/i.test(path23)) return "github";
12492
+ if (/\/slack(\/|$)/i.test(path23)) return "slack";
12159
12493
  return null;
12160
12494
  }
12161
12495
  function rewriteNotFoundMessage(input) {
@@ -12431,7 +12765,7 @@ var INGEST_BENEFITS = [
12431
12765
  "Allow the AI assistant to find relevant code without manual file navigation",
12432
12766
  "Build a searchable knowledge base of your codebase structure"
12433
12767
  ];
12434
- var AUTO_INDEX_FILE_CAP = 2e3;
12768
+ var AUTO_INDEX_FILE_CAP = 1e4;
12435
12769
  var PROJECT_MARKERS = [
12436
12770
  ".git",
12437
12771
  "package.json",
@@ -12446,10 +12780,10 @@ var PROJECT_MARKERS = [
12446
12780
  ];
12447
12781
  function isMultiProjectFolder(folderPath) {
12448
12782
  try {
12449
- const fs21 = __require("fs");
12783
+ const fs22 = __require("fs");
12450
12784
  const pathModule = __require("path");
12451
- const rootHasGit = fs21.existsSync(pathModule.join(folderPath, ".git"));
12452
- const entries = fs21.readdirSync(folderPath, { withFileTypes: true });
12785
+ const rootHasGit = fs22.existsSync(pathModule.join(folderPath, ".git"));
12786
+ const entries = fs22.readdirSync(folderPath, { withFileTypes: true });
12453
12787
  const subdirs = entries.filter(
12454
12788
  (e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules"
12455
12789
  );
@@ -12457,7 +12791,7 @@ function isMultiProjectFolder(folderPath) {
12457
12791
  for (const subdir of subdirs) {
12458
12792
  const subdirPath = pathModule.join(folderPath, subdir.name);
12459
12793
  for (const marker of PROJECT_MARKERS) {
12460
- if (fs21.existsSync(pathModule.join(subdirPath, marker))) {
12794
+ if (fs22.existsSync(pathModule.join(subdirPath, marker))) {
12461
12795
  projectSubdirs.push(subdir.name);
12462
12796
  break;
12463
12797
  }
@@ -12473,7 +12807,7 @@ function isMultiProjectFolder(folderPath) {
12473
12807
  return { isMultiProject: false, projectCount: 0, projectNames: [] };
12474
12808
  }
12475
12809
  }
12476
- var ContextStreamClient = class {
12810
+ var ContextStreamClient = class _ContextStreamClient {
12477
12811
  constructor(config) {
12478
12812
  this.config = config;
12479
12813
  this.indexRefreshInProgress = false;
@@ -13391,7 +13725,7 @@ var ContextStreamClient = class {
13391
13725
  * Check for changed files since last index and queue them for indexing.
13392
13726
  * This is used as a fallback for editors that don't support PostToolUse hooks.
13393
13727
  *
13394
- * Throttled to run at most every 10 seconds and caps at 20 files per check.
13728
+ * Throttled to run at most every 10 seconds and caps at 100 files per check.
13395
13729
  * Runs in fire-and-forget mode to avoid blocking tool responses.
13396
13730
  *
13397
13731
  * Call this from tool handlers like contextSmart() and search() to enable
@@ -13401,6 +13735,9 @@ var ContextStreamClient = class {
13401
13735
  if (!this.sessionProjectId || !this.sessionRootPath) {
13402
13736
  return;
13403
13737
  }
13738
+ if (!await this.hasBaselineIndex(this.sessionRootPath)) {
13739
+ return;
13740
+ }
13404
13741
  const now = Date.now();
13405
13742
  if (this.lastIndexCheckTime && now - this.lastIndexCheckTime < 1e4) {
13406
13743
  return;
@@ -13430,7 +13767,7 @@ var ContextStreamClient = class {
13430
13767
  if (!this.sessionProjectId || !this.sessionRootPath || !this.lastIndexedTime) {
13431
13768
  return;
13432
13769
  }
13433
- const MAX_FILES = 20;
13770
+ const MAX_FILES = 100;
13434
13771
  const filesToIndex = [];
13435
13772
  try {
13436
13773
  for await (const batch of readChangedFilesInBatches(
@@ -13452,6 +13789,62 @@ var ContextStreamClient = class {
13452
13789
  } catch {
13453
13790
  }
13454
13791
  }
13792
+ /**
13793
+ * Check local index-status tracking to confirm a baseline index exists.
13794
+ */
13795
+ async hasBaselineIndex(rootPath) {
13796
+ try {
13797
+ const localProjectId = await _ContextStreamClient.indexedProjectIdForFolder(rootPath);
13798
+ if (localProjectId) {
13799
+ return true;
13800
+ }
13801
+ const status = await readIndexStatus();
13802
+ const resolvedRoot = path5.resolve(rootPath);
13803
+ for (const indexedPath of Object.keys(status.projects ?? {})) {
13804
+ const resolvedIndexed = path5.resolve(indexedPath);
13805
+ if (resolvedRoot === resolvedIndexed || resolvedRoot.startsWith(`${resolvedIndexed}${path5.sep}`) || resolvedIndexed.startsWith(`${resolvedRoot}${path5.sep}`)) {
13806
+ return true;
13807
+ }
13808
+ }
13809
+ } catch {
13810
+ }
13811
+ return false;
13812
+ }
13813
+ /**
13814
+ * Return the locally tracked project_id for a folder from indexed-projects.json.
13815
+ * Uses longest-prefix path matching and ignores stale entries (>7 days old).
13816
+ */
13817
+ static async indexedProjectIdForFolder(folderPath) {
13818
+ const status = await readIndexStatus();
13819
+ const projects = status.projects ?? {};
13820
+ const resolvedFolder = path5.resolve(folderPath);
13821
+ let bestMatch = null;
13822
+ for (const [projectPath, info] of Object.entries(projects)) {
13823
+ const resolvedProjectPath = path5.resolve(projectPath);
13824
+ if (!(resolvedFolder === resolvedProjectPath || resolvedFolder.startsWith(`${resolvedProjectPath}${path5.sep}`) || resolvedProjectPath.startsWith(`${resolvedFolder}${path5.sep}`))) {
13825
+ continue;
13826
+ }
13827
+ if (_ContextStreamClient.isStaleIndexEntry(info?.indexed_at)) {
13828
+ continue;
13829
+ }
13830
+ const projectId = typeof info?.project_id === "string" && info.project_id.trim() ? info.project_id.trim() : void 0;
13831
+ if (!projectId) {
13832
+ continue;
13833
+ }
13834
+ const matchLen = resolvedProjectPath.length;
13835
+ if (!bestMatch || matchLen > bestMatch.matchLen) {
13836
+ bestMatch = { projectId, matchLen };
13837
+ }
13838
+ }
13839
+ return bestMatch?.projectId;
13840
+ }
13841
+ static isStaleIndexEntry(indexedAt) {
13842
+ if (!indexedAt) return false;
13843
+ const parsed = new Date(indexedAt);
13844
+ if (Number.isNaN(parsed.getTime())) return false;
13845
+ const diffDays = (Date.now() - parsed.getTime()) / (1e3 * 60 * 60 * 24);
13846
+ return diffDays > 7;
13847
+ }
13455
13848
  // Workspace extended operations (with caching)
13456
13849
  async getWorkspace(workspaceId) {
13457
13850
  uuidSchema.parse(workspaceId);
@@ -14112,15 +14505,30 @@ var ContextStreamClient = class {
14112
14505
  * Persists the selection to .contextstream/config.json for future sessions.
14113
14506
  */
14114
14507
  async associateWorkspace(params) {
14115
- const { folder_path, workspace_id, workspace_name, create_parent_mapping, version, configured_editors, context_pack, api_url } = params;
14508
+ const {
14509
+ folder_path,
14510
+ workspace_id,
14511
+ workspace_name,
14512
+ create_parent_mapping,
14513
+ project_id,
14514
+ project_name,
14515
+ version,
14516
+ configured_editors,
14517
+ context_pack,
14518
+ api_url
14519
+ } = params;
14520
+ const existing = readLocalConfig(folder_path);
14116
14521
  const saved = writeLocalConfig(folder_path, {
14117
14522
  workspace_id,
14118
14523
  workspace_name,
14524
+ project_id: project_id ?? existing?.project_id,
14525
+ project_name: project_name ?? existing?.project_name,
14119
14526
  associated_at: (/* @__PURE__ */ new Date()).toISOString(),
14120
14527
  version,
14121
14528
  configured_editors,
14122
14529
  context_pack,
14123
14530
  api_url,
14531
+ indexing_enabled: existing?.indexing_enabled,
14124
14532
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
14125
14533
  });
14126
14534
  if (create_parent_mapping) {
@@ -14661,9 +15069,9 @@ var ContextStreamClient = class {
14661
15069
  candidateParts.push("## Relevant Code\n");
14662
15070
  currentChars += 18;
14663
15071
  const codeEntries = code.results.map((c) => {
14664
- const path22 = c.file_path || "file";
15072
+ const path23 = c.file_path || "file";
14665
15073
  const content = c.content?.slice(0, 150) || "";
14666
- return { path: path22, entry: `\u2022 ${path22}: ${content}...
15074
+ return { path: path23, entry: `\u2022 ${path23}: ${content}...
14667
15075
  ` };
14668
15076
  });
14669
15077
  for (const c of codeEntries) {
@@ -14753,6 +15161,14 @@ var ContextStreamClient = class {
14753
15161
  const format = params.format || "minified";
14754
15162
  const usePackDefault = this.config.contextPackEnabled !== false && !!withDefaults.project_id;
14755
15163
  const mode = params.mode || (usePackDefault ? "pack" : "standard");
15164
+ const contextTimeoutSeconds = (() => {
15165
+ const raw = process.env.MCP_CONTEXT_SMART_TIMEOUT_SECS;
15166
+ if (!raw) return 45;
15167
+ const parsed = Number(raw.trim());
15168
+ if (!Number.isFinite(parsed)) return 45;
15169
+ if (parsed < 5 || parsed > 55) return 45;
15170
+ return Math.floor(parsed);
15171
+ })();
14756
15172
  if (!withDefaults.workspace_id) {
14757
15173
  return {
14758
15174
  context: "[NO_WORKSPACE]",
@@ -14814,7 +15230,9 @@ var ContextStreamClient = class {
14814
15230
  ...params.session_id !== void 0 && { session_id: params.session_id },
14815
15231
  ...params.client_name !== void 0 && { client_name: params.client_name },
14816
15232
  ...params.assistant_message !== void 0 && { assistant_message: params.assistant_message }
14817
- }
15233
+ },
15234
+ timeoutMs: contextTimeoutSeconds * 1e3,
15235
+ retries: 0
14818
15236
  });
14819
15237
  const data = unwrapApiResponse(apiResult);
14820
15238
  let versionNotice2 = null;
@@ -16811,6 +17229,170 @@ var TASK_HINTS = {
16811
17229
  linked_to_plan: "Task linked to plan. Progress will be tracked."
16812
17230
  };
16813
17231
 
17232
+ // src/project-index-utils.ts
17233
+ var INDEX_FRESH_HOURS = 1;
17234
+ var INDEX_RECENT_HOURS = 24;
17235
+ var INDEX_STALE_HOURS = 24 * 7;
17236
+ function asRecord(value) {
17237
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
17238
+ }
17239
+ function candidateObjects(result) {
17240
+ const root = asRecord(result);
17241
+ const data = asRecord(root?.data);
17242
+ if (data && root) return [data, root];
17243
+ if (data) return [data];
17244
+ if (root) return [root];
17245
+ return [];
17246
+ }
17247
+ function readBoolean(candidates, key) {
17248
+ for (const candidate of candidates) {
17249
+ const value = candidate[key];
17250
+ if (typeof value === "boolean") {
17251
+ return value;
17252
+ }
17253
+ }
17254
+ return void 0;
17255
+ }
17256
+ function readNumber(candidates, keys) {
17257
+ for (const candidate of candidates) {
17258
+ for (const key of keys) {
17259
+ const value = candidate[key];
17260
+ if (typeof value === "number" && Number.isFinite(value)) {
17261
+ return value;
17262
+ }
17263
+ if (typeof value === "string" && value.trim()) {
17264
+ const parsed = Number(value);
17265
+ if (Number.isFinite(parsed)) {
17266
+ return parsed;
17267
+ }
17268
+ }
17269
+ }
17270
+ }
17271
+ return void 0;
17272
+ }
17273
+ function readString(candidates, key) {
17274
+ for (const candidate of candidates) {
17275
+ const value = candidate[key];
17276
+ if (typeof value === "string" && value.trim()) {
17277
+ return value.trim();
17278
+ }
17279
+ }
17280
+ return void 0;
17281
+ }
17282
+ function extractIndexTimestamp(result) {
17283
+ const candidates = candidateObjects(result);
17284
+ for (const key of ["last_updated", "indexed_at", "last_indexed"]) {
17285
+ const raw = readString(candidates, key);
17286
+ if (!raw) continue;
17287
+ const parsed = new Date(raw);
17288
+ if (!Number.isNaN(parsed.getTime())) {
17289
+ return parsed;
17290
+ }
17291
+ }
17292
+ return void 0;
17293
+ }
17294
+ function apiResultReportsIndexed(result) {
17295
+ const candidates = candidateObjects(result);
17296
+ const indexed = readBoolean(candidates, "indexed");
17297
+ if (indexed !== void 0) {
17298
+ return indexed;
17299
+ }
17300
+ const indexedFiles = readNumber(candidates, ["indexed_files", "indexed_file_count"]) ?? 0;
17301
+ if (indexedFiles > 0) {
17302
+ return true;
17303
+ }
17304
+ const totalFiles = readNumber(candidates, ["total_files"]) ?? 0;
17305
+ if (totalFiles > 0) {
17306
+ const status = readString(candidates, "status")?.toLowerCase();
17307
+ if (status === "completed" || status === "ready") {
17308
+ return true;
17309
+ }
17310
+ }
17311
+ return false;
17312
+ }
17313
+ function apiResultIsIndexing(result) {
17314
+ const candidates = candidateObjects(result);
17315
+ const projectIndexState = readString(candidates, "project_index_state")?.toLowerCase();
17316
+ if (projectIndexState === "indexing" || projectIndexState === "committing") {
17317
+ return true;
17318
+ }
17319
+ const status = readString(candidates, "status")?.toLowerCase();
17320
+ if (status === "indexing" || status === "processing") {
17321
+ return true;
17322
+ }
17323
+ const pendingFiles = readNumber(candidates, ["pending_files"]) ?? 0;
17324
+ return pendingFiles > 0;
17325
+ }
17326
+ function countFromObject(value) {
17327
+ const obj = asRecord(value);
17328
+ if (!obj) return void 0;
17329
+ if (Array.isArray(obj.entries)) {
17330
+ return obj.entries.length;
17331
+ }
17332
+ if (Array.isArray(obj.history)) {
17333
+ return obj.history.length;
17334
+ }
17335
+ return void 0;
17336
+ }
17337
+ function indexHistoryEntryCount(result) {
17338
+ const rootCount = countFromObject(result);
17339
+ if (typeof rootCount === "number") {
17340
+ return rootCount;
17341
+ }
17342
+ const root = asRecord(result);
17343
+ const dataCount = countFromObject(root?.data);
17344
+ if (typeof dataCount === "number") {
17345
+ return dataCount;
17346
+ }
17347
+ if (Array.isArray(result)) {
17348
+ return result.length;
17349
+ }
17350
+ if (Array.isArray(root?.data)) {
17351
+ return root.data.length;
17352
+ }
17353
+ return 0;
17354
+ }
17355
+ function classifyIndexFreshness(indexed, ageHours) {
17356
+ if (!indexed) {
17357
+ return "missing";
17358
+ }
17359
+ if (typeof ageHours !== "number" || Number.isNaN(ageHours)) {
17360
+ return "unknown";
17361
+ }
17362
+ if (ageHours <= INDEX_FRESH_HOURS) {
17363
+ return "fresh";
17364
+ }
17365
+ if (ageHours <= INDEX_RECENT_HOURS) {
17366
+ return "recent";
17367
+ }
17368
+ if (ageHours <= INDEX_STALE_HOURS) {
17369
+ return "aging";
17370
+ }
17371
+ return "stale";
17372
+ }
17373
+ function classifyIndexConfidence(indexed, apiIndexed, locallyIndexed, freshness) {
17374
+ if (!indexed) {
17375
+ return {
17376
+ confidence: "low",
17377
+ reason: "Neither API status nor local index metadata currently indicates a usable index."
17378
+ };
17379
+ }
17380
+ if (apiIndexed && locallyIndexed) {
17381
+ const reason = freshness === "stale" ? "API and local metadata agree, but index age indicates stale coverage." : "API and local metadata agree for this project scope.";
17382
+ return { confidence: "high", reason };
17383
+ }
17384
+ if (apiIndexed || locallyIndexed) {
17385
+ return {
17386
+ confidence: "medium",
17387
+ reason: "Only one source reports index readiness (API vs local metadata)."
17388
+ };
17389
+ }
17390
+ return {
17391
+ confidence: "low",
17392
+ reason: "Index state is inferred but lacks corroborating API/local metadata."
17393
+ };
17394
+ }
17395
+
16814
17396
  // src/tools.ts
16815
17397
  function parseBoolEnvDefault(raw, fallback) {
16816
17398
  if (raw === void 0) return fallback;
@@ -17591,9 +18173,9 @@ function humanizeKey(raw) {
17591
18173
  const withSpaces = raw.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ");
17592
18174
  return withSpaces.toLowerCase();
17593
18175
  }
17594
- function buildParamDescription(key, path22) {
18176
+ function buildParamDescription(key, path23) {
17595
18177
  const normalized = key in DEFAULT_PARAM_DESCRIPTIONS ? key : key.toLowerCase();
17596
- const parent = path22[path22.length - 1];
18178
+ const parent = path23[path23.length - 1];
17597
18179
  if (parent === "target") {
17598
18180
  if (key === "id") return "Target identifier (module path, function id, etc.).";
17599
18181
  if (key === "type") return "Target type (module, file, function, type, variable).";
@@ -17624,7 +18206,7 @@ function getDescription(schema) {
17624
18206
  if (def?.description && def.description.trim()) return def.description;
17625
18207
  return void 0;
17626
18208
  }
17627
- function applyParamDescriptions(schema, path22 = []) {
18209
+ function applyParamDescriptions(schema, path23 = []) {
17628
18210
  if (!(schema instanceof external_exports.ZodObject)) {
17629
18211
  return schema;
17630
18212
  }
@@ -17635,7 +18217,7 @@ function applyParamDescriptions(schema, path22 = []) {
17635
18217
  let nextField = field;
17636
18218
  const existingDescription = getDescription(field);
17637
18219
  if (field instanceof external_exports.ZodObject) {
17638
- const nested = applyParamDescriptions(field, [...path22, key]);
18220
+ const nested = applyParamDescriptions(field, [...path23, key]);
17639
18221
  if (nested !== field) {
17640
18222
  nextField = nested;
17641
18223
  changed = true;
@@ -17647,7 +18229,7 @@ function applyParamDescriptions(schema, path22 = []) {
17647
18229
  changed = true;
17648
18230
  }
17649
18231
  } else {
17650
- nextField = nextField.describe(buildParamDescription(key, path22));
18232
+ nextField = nextField.describe(buildParamDescription(key, path23));
17651
18233
  changed = true;
17652
18234
  }
17653
18235
  nextShape[key] = nextField;
@@ -19008,6 +19590,288 @@ Hint: Run session_init(folder_path="<your_project_path>") first to establish a s
19008
19590
  typeof ctx?.project_id === "string" ? ctx.project_id : void 0
19009
19591
  );
19010
19592
  }
19593
+ function isNotFoundError(error) {
19594
+ return error instanceof HttpError && error.status === 404;
19595
+ }
19596
+ function isRequiresIngestEndpointError(error) {
19597
+ if (!(error instanceof HttpError)) return false;
19598
+ if (error.status !== 400) return false;
19599
+ const message = String(error.message || "").toLowerCase();
19600
+ return message.includes("requires using the ingest endpoint");
19601
+ }
19602
+ function appendNote(existing, extra) {
19603
+ return existing ? `${existing} ${extra}` : extra;
19604
+ }
19605
+ function extractSearchEnvelope(result) {
19606
+ const data = result?.data ?? result ?? {};
19607
+ const results = Array.isArray(data?.results) ? data.results : [];
19608
+ const total = typeof data?.total === "number" ? data.total : results.length;
19609
+ return { results, total };
19610
+ }
19611
+ function maxResultScore(result) {
19612
+ const { results } = extractSearchEnvelope(result);
19613
+ let max = 0;
19614
+ for (const entry of results) {
19615
+ const score = typeof entry?.score === "number" ? entry.score : 0;
19616
+ if (score > max) max = score;
19617
+ }
19618
+ return max;
19619
+ }
19620
+ function extractQuotedLiteral(query) {
19621
+ const trimmed = query.trim();
19622
+ if (trimmed.length < 2) return void 0;
19623
+ const isDoubleQuoted = trimmed.startsWith('"') && trimmed.endsWith('"');
19624
+ const isSingleQuoted = trimmed.startsWith("'") && trimmed.endsWith("'");
19625
+ if (!isDoubleQuoted && !isSingleQuoted) return void 0;
19626
+ const literal = trimmed.slice(1, -1).trim();
19627
+ return literal || void 0;
19628
+ }
19629
+ function escapeRegexLiteral(input) {
19630
+ return input.replace(/[\\.+*?()[\]{}|^$]/g, "\\$&");
19631
+ }
19632
+ const COUNT_QUERY_PREFIXES = ["how many ", "count ", "count of ", "number of ", "total "];
19633
+ const ALL_MATCH_KEYWORDS = [
19634
+ "all occurrences",
19635
+ "all matches",
19636
+ "find all",
19637
+ "every usage",
19638
+ "every occurrence",
19639
+ "all usages"
19640
+ ];
19641
+ const TEAM_QUERY_KEYWORDS = [
19642
+ "team-wide",
19643
+ "teamwide",
19644
+ "cross-project",
19645
+ "cross project",
19646
+ "across projects",
19647
+ "all workspaces",
19648
+ "all projects"
19649
+ ];
19650
+ const DOC_QUERY_KEYWORDS = ["doc", "docs", "document", "documents", "spec", "specification", "plan", "roadmap"];
19651
+ const DOC_LOOKUP_VERBS = ["list", "show", "find", "open", "read", "lookup", "look up", "get"];
19652
+ const QUESTION_WORDS = ["how", "what", "where", "why", "when", "which", "who", "does", "is", "can", "should"];
19653
+ const HYBRID_LOW_CONFIDENCE_SCORE = 0.35;
19654
+ const SEMANTIC_SWITCH_MIN_IMPROVEMENT = 0.08;
19655
+ function isIdentifierQuery(query) {
19656
+ const trimmed = query.trim();
19657
+ if (!trimmed || trimmed.length < 2 || trimmed.includes(" ")) return false;
19658
+ if (!/^[A-Za-z0-9_:]+$/.test(trimmed)) return false;
19659
+ const hasMixedCase = /[a-z]/.test(trimmed) && /[A-Z]/.test(trimmed);
19660
+ const hasUnderscore = trimmed.includes("_");
19661
+ const isAllCaps = trimmed.length >= 3 && /^[A-Z0-9_]+$/.test(trimmed);
19662
+ return hasMixedCase || hasUnderscore || isAllCaps;
19663
+ }
19664
+ function hasRegexCharacters(query) {
19665
+ const trimmed = query.trim();
19666
+ if (/[\^$+{}[\]|()\\]/.test(trimmed)) return true;
19667
+ if (!trimmed.includes("?")) return false;
19668
+ const trailingOnly = trimmed.endsWith("?") && !trimmed.slice(0, -1).includes("?");
19669
+ return !trailingOnly;
19670
+ }
19671
+ function isGlobLike(query) {
19672
+ const trimmed = query.trim();
19673
+ return trimmed.includes("*") || trimmed.includes("?") && !trimmed.endsWith("?");
19674
+ }
19675
+ function isCountQuery(queryLower) {
19676
+ return COUNT_QUERY_PREFIXES.some((prefix) => queryLower.startsWith(prefix)) || queryLower.includes("how many") && queryLower.includes("are there");
19677
+ }
19678
+ function isAllMatchesQuery(queryLower) {
19679
+ return ALL_MATCH_KEYWORDS.some((keyword) => queryLower.includes(keyword));
19680
+ }
19681
+ function isTeamQuery(queryLower) {
19682
+ return TEAM_QUERY_KEYWORDS.some((keyword) => queryLower.includes(keyword));
19683
+ }
19684
+ function isDocLookupQuery(query) {
19685
+ const lower = query.trim().toLowerCase();
19686
+ if (!lower) return false;
19687
+ if (lower.includes(".rs") || lower.includes(".ts") || lower.includes(".js") || lower.includes("src/") || lower.includes("crates/") || lower.includes("function ") || lower.includes("class ")) {
19688
+ return false;
19689
+ }
19690
+ const hasDocTerm = DOC_QUERY_KEYWORDS.some((keyword) => lower.includes(keyword));
19691
+ if (!hasDocTerm) return false;
19692
+ const hasLookupVerb = DOC_LOOKUP_VERBS.some((keyword) => lower.includes(keyword));
19693
+ return hasLookupVerb || lower.startsWith("docs ") || lower.startsWith("doc ");
19694
+ }
19695
+ function recommendSearchMode(query) {
19696
+ const trimmed = query.trim();
19697
+ if (!trimmed) {
19698
+ return { mode: "hybrid", reason: "Defaulted to hybrid for broad discovery." };
19699
+ }
19700
+ const lower = trimmed.toLowerCase();
19701
+ const wordCount = trimmed.split(/\s+/).filter(Boolean).length;
19702
+ if (isTeamQuery(lower)) {
19703
+ return { mode: "team", reason: "Detected team/cross-project intent." };
19704
+ }
19705
+ if (isAllMatchesQuery(lower)) {
19706
+ return {
19707
+ mode: "exhaustive",
19708
+ reason: "Detected all-occurrences intent; exhaustive mode is complete."
19709
+ };
19710
+ }
19711
+ if (extractQuotedLiteral(trimmed)) {
19712
+ return { mode: "keyword", reason: "Detected quoted exact-match query." };
19713
+ }
19714
+ if (isGlobLike(trimmed) || hasRegexCharacters(trimmed)) {
19715
+ return { mode: "pattern", reason: "Detected glob/regex pattern." };
19716
+ }
19717
+ if (isIdentifierQuery(trimmed)) {
19718
+ return {
19719
+ mode: "refactor",
19720
+ reason: "Detected identifier-like query; refactor mode is more precise."
19721
+ };
19722
+ }
19723
+ if (QUESTION_WORDS.some((w) => lower.startsWith(w)) || trimmed.endsWith("?") || wordCount >= 3) {
19724
+ return {
19725
+ mode: "semantic",
19726
+ reason: "Detected natural-language query; semantic mode is a better fit."
19727
+ };
19728
+ }
19729
+ return { mode: "hybrid", reason: "Hybrid mode provides balanced coverage." };
19730
+ }
19731
+ function suggestOutputFormat(query, mode) {
19732
+ const lower = query.trim().toLowerCase();
19733
+ if (isCountQuery(lower)) {
19734
+ return "count";
19735
+ }
19736
+ if (isIdentifierQuery(query)) {
19737
+ if (mode === "refactor" || mode === "exhaustive") return "paths";
19738
+ return "minimal";
19739
+ }
19740
+ return void 0;
19741
+ }
19742
+ function shouldRetrySemanticFallback(query, mode, result) {
19743
+ if (mode !== "hybrid") return false;
19744
+ if (recommendSearchMode(query).mode !== "semantic") return false;
19745
+ const { results } = extractSearchEnvelope(result);
19746
+ return results.length === 0 || maxResultScore(result) < HYBRID_LOW_CONFIDENCE_SCORE;
19747
+ }
19748
+ function shouldPreferSemanticResults(hybridResult, semanticResult) {
19749
+ const hybrid = extractSearchEnvelope(hybridResult);
19750
+ const semantic = extractSearchEnvelope(semanticResult);
19751
+ if (semantic.results.length === 0) return false;
19752
+ if (hybrid.results.length === 0) return true;
19753
+ const hybridTop = maxResultScore(hybridResult);
19754
+ const semanticTop = maxResultScore(semanticResult);
19755
+ return semanticTop > hybridTop + SEMANTIC_SWITCH_MIN_IMPROVEMENT;
19756
+ }
19757
+ function shouldRetryKeywordWithSemantic(query) {
19758
+ return recommendSearchMode(query).mode === "semantic";
19759
+ }
19760
+ function shouldRetryKeywordWithSymbolModes(query) {
19761
+ return isIdentifierQuery(query);
19762
+ }
19763
+ function extractCollectionArray(value) {
19764
+ if (Array.isArray(value)) return value;
19765
+ if (!value || typeof value !== "object") return void 0;
19766
+ for (const key of ["items", "results", "docs"]) {
19767
+ if (Array.isArray(value[key])) return value[key];
19768
+ }
19769
+ if ("data" in value) return extractCollectionArray(value.data);
19770
+ return void 0;
19771
+ }
19772
+ function tokenizeForDocMatch(query) {
19773
+ const stopWords = /* @__PURE__ */ new Set([
19774
+ "the",
19775
+ "and",
19776
+ "for",
19777
+ "with",
19778
+ "from",
19779
+ "that",
19780
+ "this",
19781
+ "docs",
19782
+ "doc",
19783
+ "document",
19784
+ "list",
19785
+ "show",
19786
+ "find",
19787
+ "plan",
19788
+ "phase",
19789
+ "phases"
19790
+ ]);
19791
+ return query.split(/[^A-Za-z0-9]+/).map((term) => term.trim().toLowerCase()).filter((term) => term.length >= 3 && !stopWords.has(term));
19792
+ }
19793
+ function scoreDocMatch(doc, terms) {
19794
+ const title = String(doc?.title ?? doc?.name ?? doc?.summary ?? "").toLowerCase();
19795
+ const content = String(doc?.content ?? "").toLowerCase();
19796
+ const haystack = `${title} ${content}`;
19797
+ return terms.reduce((score, term) => haystack.includes(term) ? score + 1 : score, 0);
19798
+ }
19799
+ function rankDocsForQuery(docs, query, limit) {
19800
+ const terms = tokenizeForDocMatch(query);
19801
+ if (terms.length === 0) return docs.slice(0, limit);
19802
+ const scored = docs.map((doc, idx) => ({ idx, score: scoreDocMatch(doc, terms) }));
19803
+ scored.sort((a, b) => b.score - a.score || a.idx - b.idx);
19804
+ const matched = scored.filter((entry) => entry.score > 0).slice(0, limit).map((entry) => docs[entry.idx]);
19805
+ return matched.length > 0 ? matched : docs.slice(0, limit);
19806
+ }
19807
+ async function findDocsFallback(workspaceId, candidateProjectIds, query, limit) {
19808
+ const uniqueCandidates = [];
19809
+ for (const candidate of candidateProjectIds) {
19810
+ if (!uniqueCandidates.includes(candidate)) {
19811
+ uniqueCandidates.push(candidate);
19812
+ }
19813
+ }
19814
+ const maxDocs = Math.max(1, Math.min(limit ?? 20, 50));
19815
+ for (const candidate of uniqueCandidates) {
19816
+ try {
19817
+ const docsResponse = await client.docsList({
19818
+ workspace_id: workspaceId,
19819
+ project_id: candidate,
19820
+ per_page: maxDocs
19821
+ });
19822
+ const items = extractCollectionArray(docsResponse);
19823
+ if (!items || items.length === 0) continue;
19824
+ const ranked = rankDocsForQuery(items, query, maxDocs);
19825
+ if (ranked.length > 0) {
19826
+ return { docs: ranked, project_id: candidate };
19827
+ }
19828
+ } catch {
19829
+ }
19830
+ }
19831
+ return void 0;
19832
+ }
19833
+ async function indexedProjectIdForFolder(folderPath) {
19834
+ const status = await readIndexStatus();
19835
+ const projects = status.projects ?? {};
19836
+ const resolvedFolder = path6.resolve(folderPath);
19837
+ let bestMatch;
19838
+ for (const [projectPath, info] of Object.entries(projects)) {
19839
+ const resolvedProjectPath = path6.resolve(projectPath);
19840
+ const matches = resolvedFolder === resolvedProjectPath || resolvedFolder.startsWith(`${resolvedProjectPath}${path6.sep}`) || resolvedProjectPath.startsWith(`${resolvedFolder}${path6.sep}`);
19841
+ if (!matches) continue;
19842
+ if (!info?.indexed_at) continue;
19843
+ const indexedAt = new Date(info.indexed_at);
19844
+ if (!Number.isNaN(indexedAt.getTime())) {
19845
+ const diffDays = (Date.now() - indexedAt.getTime()) / (1e3 * 60 * 60 * 24);
19846
+ if (diffDays > 7) continue;
19847
+ }
19848
+ const projectId = typeof info.project_id === "string" && info.project_id.trim() ? info.project_id.trim() : void 0;
19849
+ if (!projectId) continue;
19850
+ const matchLen = resolvedProjectPath.length;
19851
+ if (!bestMatch || matchLen > bestMatch.matchLen) {
19852
+ bestMatch = { projectId, matchLen };
19853
+ }
19854
+ }
19855
+ return bestMatch?.projectId;
19856
+ }
19857
+ async function executeSearchMode(mode, params) {
19858
+ switch (mode) {
19859
+ case "hybrid":
19860
+ return client.searchHybrid(params);
19861
+ case "semantic":
19862
+ return client.searchSemantic(params);
19863
+ case "keyword":
19864
+ return client.searchKeyword(params);
19865
+ case "pattern":
19866
+ return client.searchPattern(params);
19867
+ case "exhaustive":
19868
+ return client.searchExhaustive(params);
19869
+ case "refactor":
19870
+ return client.searchRefactor(params);
19871
+ default:
19872
+ return client.searchHybrid(params);
19873
+ }
19874
+ }
19011
19875
  async function validateReadableDirectory(inputPath) {
19012
19876
  const resolvedPath = path6.resolve(inputPath);
19013
19877
  let stats;
@@ -23533,48 +24397,80 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
23533
24397
  if (authError) return authError;
23534
24398
  client.checkAndIndexChangedFiles().catch(() => {
23535
24399
  });
23536
- const params = normalizeSearchParams(input);
23537
24400
  const startTime = Date.now();
23538
- const requestedMode = input.mode || "auto";
23539
- let result;
23540
- let toolType;
23541
- switch (requestedMode) {
23542
- case "auto":
23543
- case "hybrid":
23544
- result = await client.searchHybrid(params);
23545
- toolType = "search_hybrid";
23546
- break;
23547
- case "semantic":
23548
- result = await client.searchSemantic(params);
23549
- toolType = "search_semantic";
23550
- break;
23551
- case "keyword":
23552
- result = await client.searchKeyword(params);
23553
- toolType = "search_keyword";
23554
- break;
23555
- case "pattern":
23556
- result = await client.searchPattern(params);
23557
- toolType = "search_pattern";
23558
- break;
23559
- case "exhaustive":
23560
- result = await client.searchExhaustive(params);
23561
- toolType = "search_exhaustive";
23562
- break;
23563
- case "refactor":
23564
- result = await client.searchRefactor(params);
23565
- toolType = "search_refactor";
23566
- break;
23567
- case "team": {
24401
+ const modeInput = input.mode || "auto";
24402
+ const modeRecommendation = recommendSearchMode(input.query);
24403
+ const modeAutoSelected = modeInput === "auto";
24404
+ const requestedMode = modeAutoSelected ? modeRecommendation.mode : modeInput === "auto" ? "hybrid" : modeInput;
24405
+ let workspaceId = resolveWorkspaceId(input.workspace_id);
24406
+ const sessionProjectId = resolveProjectId(void 0);
24407
+ const requestedExplicitProjectId = normalizeUuid(input.project_id);
24408
+ let explicitProjectId = requestedExplicitProjectId;
24409
+ let explicitProjectScopeNote;
24410
+ const folderPath = resolveFolderPath(void 0, sessionManager);
24411
+ let resolvedFolderProjectId;
24412
+ if (folderPath) {
24413
+ const mapping = resolveWorkspace(folderPath);
24414
+ const mappedWorkspaceId = normalizeUuid(mapping.config?.workspace_id);
24415
+ if (!workspaceId && mappedWorkspaceId) {
24416
+ workspaceId = mappedWorkspaceId;
24417
+ }
24418
+ resolvedFolderProjectId = normalizeUuid(mapping.config?.project_id);
24419
+ }
24420
+ const localIndexProjectId = folderPath ? await indexedProjectIdForFolder(folderPath) : void 0;
24421
+ if (explicitProjectId) {
24422
+ try {
24423
+ const project = await client.getProject(explicitProjectId);
24424
+ const projectWorkspaceId = normalizeUuid(project?.workspace_id || project?.workspaceId);
24425
+ if (workspaceId && projectWorkspaceId && projectWorkspaceId !== workspaceId) {
24426
+ explicitProjectScopeNote = `Explicit project_id ${explicitProjectId} belongs to workspace ${projectWorkspaceId}; auto-corrected to current workspace ${workspaceId}.`;
24427
+ explicitProjectId = void 0;
24428
+ }
24429
+ } catch (error) {
24430
+ if (isNotFoundError(error)) {
24431
+ explicitProjectScopeNote = `Explicit project_id ${explicitProjectId} was not found; auto-corrected using folder/index project mapping.`;
24432
+ explicitProjectId = void 0;
24433
+ } else {
24434
+ throw error;
24435
+ }
24436
+ }
24437
+ }
24438
+ const candidateProjectIds = [];
24439
+ const pushCandidateProjectId = (candidate) => {
24440
+ if (!candidateProjectIds.includes(candidate)) {
24441
+ candidateProjectIds.push(candidate);
24442
+ }
24443
+ };
24444
+ if (explicitProjectId) {
24445
+ pushCandidateProjectId(explicitProjectId);
24446
+ pushCandidateProjectId(resolvedFolderProjectId);
24447
+ pushCandidateProjectId(localIndexProjectId);
24448
+ pushCandidateProjectId(sessionProjectId);
24449
+ } else {
24450
+ pushCandidateProjectId(resolvedFolderProjectId);
24451
+ pushCandidateProjectId(localIndexProjectId);
24452
+ pushCandidateProjectId(sessionProjectId);
24453
+ if (candidateProjectIds.length === 0) {
24454
+ pushCandidateProjectId(void 0);
24455
+ }
24456
+ }
24457
+ pushCandidateProjectId(void 0);
24458
+ const modeToToolType = {
24459
+ hybrid: "search_hybrid",
24460
+ semantic: "search_semantic",
24461
+ keyword: "search_keyword",
24462
+ pattern: "search_pattern",
24463
+ exhaustive: "search_exhaustive",
24464
+ refactor: "search_refactor",
24465
+ team: "search_hybrid"
24466
+ };
24467
+ const runSearchForMode = async (mode, baseParams2) => {
24468
+ if (mode === "team") {
23568
24469
  const isTeamPlanForSearch = await client.isTeamPlan();
23569
24470
  if (!isTeamPlanForSearch) {
23570
- return {
23571
- content: [
23572
- {
23573
- type: "text",
23574
- text: "Team search requires a Team subscription. Your current plan does not include team features.\n\nUpgrade at: https://contextstream.io/pricing"
23575
- }
23576
- ]
23577
- };
24471
+ throw new Error(
24472
+ "Team search requires a Team subscription. Your current plan does not include team features.\n\nUpgrade at: https://contextstream.io/pricing"
24473
+ );
23578
24474
  }
23579
24475
  const teamWorkspacesForSearch = await client.listTeamWorkspaces({ page_size: 100 });
23580
24476
  const workspacesForSearch = teamWorkspacesForSearch?.items || teamWorkspacesForSearch?.data?.items || [];
@@ -23583,14 +24479,15 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
23583
24479
  for (const ws of workspacesForSearch.slice(0, 10)) {
23584
24480
  try {
23585
24481
  const wsSearchResult = await client.searchHybrid({
23586
- ...params,
24482
+ ...baseParams2,
23587
24483
  workspace_id: ws.id,
24484
+ project_id: void 0,
23588
24485
  limit: perWorkspaceLimit
23589
24486
  });
23590
- const wsResults = wsSearchResult?.data?.results || wsSearchResult?.results || [];
24487
+ const wsResults = extractSearchEnvelope(wsSearchResult).results;
23591
24488
  allSearchResults.push(
23592
- ...wsResults.map((r) => ({
23593
- ...r,
24489
+ ...wsResults.map((entry) => ({
24490
+ ...entry,
23594
24491
  workspace_name: ws.name,
23595
24492
  workspace_id: ws.id
23596
24493
  }))
@@ -23598,32 +24495,284 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
23598
24495
  } catch {
23599
24496
  }
23600
24497
  }
23601
- allSearchResults.sort((a, b) => (b.score || 0) - (a.score || 0));
24498
+ allSearchResults.sort((a, b) => (b?.score || 0) - (a?.score || 0));
23602
24499
  const limitedResults = allSearchResults.slice(0, input.limit || 20);
23603
- result = {
23604
- data: {
23605
- results: limitedResults,
23606
- total: limitedResults.length,
23607
- workspaces_searched: workspacesForSearch.length
24500
+ return {
24501
+ result: {
24502
+ data: {
24503
+ results: limitedResults,
24504
+ total: limitedResults.length,
24505
+ workspaces_searched: workspacesForSearch.length
24506
+ }
24507
+ },
24508
+ executedMode: "team"
24509
+ };
24510
+ }
24511
+ const dispatchMode = mode === "hybrid" ? "hybrid" : mode;
24512
+ let result = await executeSearchMode(dispatchMode, baseParams2);
24513
+ let executedMode = dispatchMode;
24514
+ let fallbackNote;
24515
+ if (shouldRetrySemanticFallback(input.query, dispatchMode, result)) {
24516
+ try {
24517
+ const semanticResult = await executeSearchMode("semantic", baseParams2);
24518
+ if (shouldPreferSemanticResults(result, semanticResult)) {
24519
+ result = semanticResult;
24520
+ executedMode = "semantic";
24521
+ fallbackNote = appendNote(
24522
+ fallbackNote,
24523
+ "Hybrid results looked low-confidence for this natural-language query; retried with semantic and used semantic results."
24524
+ );
24525
+ }
24526
+ } catch {
24527
+ }
24528
+ }
24529
+ const currentResults = () => extractSearchEnvelope(result).results;
24530
+ if (dispatchMode === "keyword" && currentResults().length === 0) {
24531
+ const literal = extractQuotedLiteral(input.query);
24532
+ if (literal) {
24533
+ if (literal !== baseParams2.query) {
24534
+ try {
24535
+ const keywordUnquoted = await executeSearchMode("keyword", {
24536
+ ...baseParams2,
24537
+ query: literal
24538
+ });
24539
+ if (extractSearchEnvelope(keywordUnquoted).results.length > 0) {
24540
+ result = keywordUnquoted;
24541
+ executedMode = "keyword";
24542
+ fallbackNote = appendNote(
24543
+ fallbackNote,
24544
+ "Quoted keyword search returned no results; retried keyword search without quotes."
24545
+ );
24546
+ }
24547
+ } catch {
24548
+ }
24549
+ }
24550
+ if (currentResults().length === 0) {
24551
+ try {
24552
+ const patternResult = await executeSearchMode("pattern", {
24553
+ ...baseParams2,
24554
+ query: escapeRegexLiteral(literal)
24555
+ });
24556
+ if (extractSearchEnvelope(patternResult).results.length > 0) {
24557
+ result = patternResult;
24558
+ executedMode = "pattern";
24559
+ fallbackNote = appendNote(
24560
+ fallbackNote,
24561
+ "Quoted keyword search returned no results; retried literal pattern search."
24562
+ );
24563
+ }
24564
+ } catch {
24565
+ }
24566
+ }
24567
+ if (currentResults().length === 0) {
24568
+ try {
24569
+ const exhaustiveResult = await executeSearchMode("exhaustive", {
24570
+ ...baseParams2,
24571
+ query: literal
24572
+ });
24573
+ if (extractSearchEnvelope(exhaustiveResult).results.length > 0) {
24574
+ result = exhaustiveResult;
24575
+ executedMode = "exhaustive";
24576
+ fallbackNote = appendNote(
24577
+ fallbackNote,
24578
+ "Quoted keyword search returned no results; retried exhaustive search for complete literal coverage."
24579
+ );
24580
+ }
24581
+ } catch {
24582
+ }
24583
+ }
24584
+ }
24585
+ }
24586
+ if ((dispatchMode === "refactor" || dispatchMode === "exhaustive") && currentResults().length === 0) {
24587
+ try {
24588
+ const keywordResult = await executeSearchMode("keyword", baseParams2);
24589
+ if (extractSearchEnvelope(keywordResult).results.length > 0) {
24590
+ result = keywordResult;
24591
+ executedMode = "keyword";
24592
+ fallbackNote = appendNote(
24593
+ fallbackNote,
24594
+ "Requested mode returned no results; retried keyword search and found matches."
24595
+ );
24596
+ }
24597
+ } catch {
24598
+ }
24599
+ }
24600
+ if (dispatchMode === "keyword" && currentResults().length === 0) {
24601
+ if (shouldRetryKeywordWithSymbolModes(input.query)) {
24602
+ try {
24603
+ const refactorResult = await executeSearchMode("refactor", baseParams2);
24604
+ if (extractSearchEnvelope(refactorResult).results.length > 0) {
24605
+ result = refactorResult;
24606
+ executedMode = "refactor";
24607
+ fallbackNote = appendNote(
24608
+ fallbackNote,
24609
+ "Keyword search returned no results; retried refactor search for identifier matching."
24610
+ );
24611
+ }
24612
+ } catch {
24613
+ }
24614
+ if (currentResults().length === 0) {
24615
+ try {
24616
+ const exhaustiveResult = await executeSearchMode("exhaustive", baseParams2);
24617
+ if (extractSearchEnvelope(exhaustiveResult).results.length > 0) {
24618
+ result = exhaustiveResult;
24619
+ executedMode = "exhaustive";
24620
+ fallbackNote = appendNote(
24621
+ fallbackNote,
24622
+ "Keyword search returned no results; retried exhaustive search for complete identifier coverage."
24623
+ );
24624
+ }
24625
+ } catch {
24626
+ }
24627
+ }
24628
+ }
24629
+ if (currentResults().length === 0 && shouldRetryKeywordWithSemantic(input.query)) {
24630
+ try {
24631
+ const semanticResult = await executeSearchMode("semantic", baseParams2);
24632
+ if (extractSearchEnvelope(semanticResult).results.length > 0) {
24633
+ result = semanticResult;
24634
+ executedMode = "semantic";
24635
+ fallbackNote = appendNote(
24636
+ fallbackNote,
24637
+ "Keyword search returned no results; retried semantic search for natural-language intent."
24638
+ );
24639
+ }
24640
+ } catch {
24641
+ }
24642
+ }
24643
+ if (currentResults().length === 0) {
24644
+ try {
24645
+ const hybridResult = await executeSearchMode("hybrid", baseParams2);
24646
+ if (extractSearchEnvelope(hybridResult).results.length > 0) {
24647
+ result = hybridResult;
24648
+ executedMode = "hybrid";
24649
+ fallbackNote = appendNote(
24650
+ fallbackNote,
24651
+ "Keyword search returned no results; retried hybrid search as a broad fallback."
24652
+ );
24653
+ }
24654
+ } catch {
23608
24655
  }
24656
+ }
24657
+ }
24658
+ return { result, executedMode, fallbackNote };
24659
+ };
24660
+ let selected;
24661
+ let explicitScopeHadNoResults = false;
24662
+ const baseParams = normalizeSearchParams({
24663
+ ...input,
24664
+ workspace_id: workspaceId,
24665
+ project_id: void 0,
24666
+ output_format: input.output_format || suggestOutputFormat(input.query, requestedMode === "team" ? "hybrid" : requestedMode)
24667
+ });
24668
+ if (requestedMode === "team") {
24669
+ try {
24670
+ const teamResult = await runSearchForMode("team", {
24671
+ ...baseParams,
24672
+ project_id: void 0
24673
+ });
24674
+ selected = {
24675
+ index: 0,
24676
+ project_id: void 0,
24677
+ result: teamResult.result,
24678
+ executedMode: "team",
24679
+ fallbackNote: teamResult.fallbackNote
23609
24680
  };
23610
- toolType = "search_hybrid";
23611
- break;
24681
+ } catch (error) {
24682
+ const message = error instanceof Error ? error.message : String(error);
24683
+ return errorResult(message);
23612
24684
  }
23613
- default:
23614
- result = await client.searchHybrid(params);
23615
- toolType = "search_hybrid";
24685
+ } else {
24686
+ for (const [index, candidateProjectId] of candidateProjectIds.entries()) {
24687
+ const paramsForCandidate = {
24688
+ ...baseParams,
24689
+ workspace_id: workspaceId,
24690
+ project_id: candidateProjectId
24691
+ };
24692
+ try {
24693
+ const modeResult = await runSearchForMode(requestedMode, paramsForCandidate);
24694
+ const envelope = extractSearchEnvelope(modeResult.result);
24695
+ if (explicitProjectId && index === 0 && envelope.results.length === 0) {
24696
+ explicitScopeHadNoResults = true;
24697
+ }
24698
+ if (envelope.results.length > 0) {
24699
+ selected = {
24700
+ index,
24701
+ project_id: candidateProjectId,
24702
+ result: modeResult.result,
24703
+ executedMode: modeResult.executedMode,
24704
+ fallbackNote: modeResult.fallbackNote
24705
+ };
24706
+ break;
24707
+ }
24708
+ if (!selected) {
24709
+ selected = {
24710
+ index,
24711
+ project_id: candidateProjectId,
24712
+ result: modeResult.result,
24713
+ executedMode: modeResult.executedMode,
24714
+ fallbackNote: modeResult.fallbackNote
24715
+ };
24716
+ }
24717
+ } catch (error) {
24718
+ if (isNotFoundError(error)) {
24719
+ continue;
24720
+ }
24721
+ throw error;
24722
+ }
24723
+ }
24724
+ }
24725
+ if (!selected) {
24726
+ const baseMessage = "Project not found for current context. Call init(...) in this folder or pass a valid project_id explicitly.";
24727
+ return errorResult(
24728
+ explicitProjectScopeNote ? `${explicitProjectScopeNote} ${baseMessage}` : baseMessage
24729
+ );
24730
+ }
24731
+ let modeFallbackNote = selected.fallbackNote;
24732
+ if (explicitProjectScopeNote) {
24733
+ modeFallbackNote = appendNote(modeFallbackNote, explicitProjectScopeNote);
24734
+ }
24735
+ if (explicitScopeHadNoResults && selected.index > 0) {
24736
+ modeFallbackNote = appendNote(
24737
+ modeFallbackNote,
24738
+ "Explicit project_id returned no results; retried folder/local project mapping."
24739
+ );
24740
+ }
24741
+ if (!selected.project_id && selected.index > 0) {
24742
+ modeFallbackNote = appendNote(
24743
+ modeFallbackNote,
24744
+ "Project-scoped search returned no results; retried workspace-wide scope."
24745
+ );
23616
24746
  }
24747
+ if (folderPath && localIndexProjectId && selected.project_id !== localIndexProjectId) {
24748
+ const resolvedScope = selected.project_id || "workspace-wide scope";
24749
+ modeFallbackNote = appendNote(
24750
+ modeFallbackNote,
24751
+ `Local index mapping for this folder points to project_id ${localIndexProjectId}; search resolved ${resolvedScope}. If results look stale, run project(action="ingest_local", path="${folderPath}").`
24752
+ );
24753
+ }
24754
+ if (requestedMode !== "team" && !explicitProjectId && sessionManager && folderPath && (selected.project_id !== sessionProjectId || workspaceId !== resolveWorkspaceId(void 0))) {
24755
+ sessionManager.updateScope({
24756
+ workspace_id: workspaceId,
24757
+ project_id: selected.project_id || sessionProjectId,
24758
+ folder_path: folderPath
24759
+ });
24760
+ }
24761
+ const docsFallback = requestedMode !== "team" && isDocLookupQuery(input.query) && extractSearchEnvelope(selected.result).results.length === 0 ? await findDocsFallback(workspaceId, candidateProjectIds, input.query, input.limit) : void 0;
23617
24762
  const roundTripMs = Date.now() - startTime;
23618
- const data = result?.data || result;
23619
- const results = data?.results || [];
23620
- const total = data?.total ?? results.length;
24763
+ const { results, total } = extractSearchEnvelope(selected.result);
23621
24764
  const lines = [];
23622
24765
  if (SHOW_TIMING) {
23623
24766
  lines.push(`\u2713 ${total} results in ${roundTripMs}ms`);
23624
24767
  } else {
23625
24768
  lines.push(`\u{1F50D} ${total} results for "${input.query}"`);
23626
24769
  }
24770
+ if (modeAutoSelected) {
24771
+ lines.push(`Mode auto-selected: \`${requestedMode}\`. ${modeRecommendation.reason}`);
24772
+ }
24773
+ if (modeFallbackNote) {
24774
+ lines.push(modeFallbackNote);
24775
+ }
23627
24776
  if (results.length > 0) {
23628
24777
  lines.push("");
23629
24778
  results.forEach((r, i) => {
@@ -23637,15 +24786,69 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
23637
24786
  });
23638
24787
  } else {
23639
24788
  lines.push("");
23640
- lines.push("No results found. Try a different query or search mode.");
24789
+ if (docsFallback?.docs?.length) {
24790
+ const docsScope = docsFallback.project_id ? ` (project_id: ${docsFallback.project_id})` : "";
24791
+ lines.push(
24792
+ `No codebase results found. This query looks like a docs lookup; found ${docsFallback.docs.length} docs in ContextStream memory${docsScope}:`
24793
+ );
24794
+ lines.push("");
24795
+ docsFallback.docs.slice(0, 10).forEach((doc, i) => {
24796
+ const title = doc?.title || doc?.name || doc?.summary || "Untitled";
24797
+ const id = doc?.id || "unknown";
24798
+ const docType = doc?.doc_type || "doc";
24799
+ lines.push(`${i + 1}. ${title} (id: ${id}) [${docType}]`);
24800
+ });
24801
+ lines.push("");
24802
+ lines.push(
24803
+ `Use memory(action="get_doc", doc_id="...") to open a specific doc or memory(action="list_docs") to browse more.`
24804
+ );
24805
+ } else {
24806
+ lines.push("No results found. Try a different query or search mode.");
24807
+ if (isDocLookupQuery(input.query)) {
24808
+ lines.push(
24809
+ `This query appears to target saved docs. Try memory(action="list_docs") and then memory(action="get_doc", doc_id="...").`
24810
+ );
24811
+ }
24812
+ if (folderPath) {
24813
+ lines.push(
24814
+ `If results seem stale after recent edits, refresh local index with project(action="ingest_local", path="${folderPath}").`
24815
+ );
24816
+ }
24817
+ }
23641
24818
  }
24819
+ const rawData = selected.result?.data;
24820
+ const structuredData = rawData && typeof rawData === "object" ? { ...rawData } : { ...selected.result };
24821
+ if (requestedMode !== "team") {
24822
+ if (requestedExplicitProjectId) {
24823
+ structuredData.requested_explicit_project_id = requestedExplicitProjectId;
24824
+ structuredData.effective_explicit_project_id = explicitProjectId;
24825
+ structuredData.explicit_project_autocorrected = requestedExplicitProjectId !== explicitProjectId;
24826
+ }
24827
+ if (selected.project_id !== sessionProjectId) {
24828
+ structuredData.original_project_id = sessionProjectId;
24829
+ }
24830
+ structuredData.resolved_project_id = selected.project_id;
24831
+ structuredData.resolution_rank = selected.index;
24832
+ }
24833
+ if (docsFallback?.docs?.length) {
24834
+ structuredData.memory_docs_fallback = {
24835
+ project_id: docsFallback.project_id,
24836
+ docs: docsFallback.docs,
24837
+ count: docsFallback.docs.length
24838
+ };
24839
+ }
24840
+ const resultWithMeta = {
24841
+ ...selected.result,
24842
+ data: structuredData
24843
+ };
23642
24844
  lines.push("");
23643
24845
  lines.push("--- Full Results ---");
23644
- lines.push(formatContent(result));
24846
+ lines.push(formatContent(resultWithMeta));
23645
24847
  const outputText = lines.join("\n");
24848
+ const toolType = modeToToolType[selected.executedMode] || "search_hybrid";
23646
24849
  trackToolTokenSavings(client, toolType, outputText, {
23647
- workspace_id: params.workspace_id,
23648
- project_id: params.project_id
24850
+ workspace_id: workspaceId,
24851
+ project_id: selected.project_id
23649
24852
  });
23650
24853
  return {
23651
24854
  content: [{ type: "text", text: outputText }]
@@ -23771,8 +24974,10 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
23771
24974
  })
23772
24975
  },
23773
24976
  async (input) => {
23774
- const workspaceId = resolveWorkspaceId(input.workspace_id);
23775
- const projectId = resolveProjectId(input.project_id);
24977
+ let workspaceId = resolveWorkspaceId(input.workspace_id);
24978
+ const explicitProjectId = normalizeUuid(input.project_id);
24979
+ let projectId = explicitProjectId || resolveProjectId(void 0);
24980
+ const folderPath = input.folder_path || input.path || resolveFolderPath(void 0, sessionManager) || void 0;
23776
24981
  switch (input.action) {
23777
24982
  case "capture": {
23778
24983
  if (!input.event_type || !input.title || !input.content) {
@@ -24534,6 +25739,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
24534
25739
  plan_step_id: external_exports.string().optional().describe("Which plan step this task implements"),
24535
25740
  description: external_exports.string().optional().describe("Description for task"),
24536
25741
  task_status: external_exports.enum(["pending", "in_progress", "completed", "blocked", "cancelled"]).optional().describe("Task status"),
25742
+ status: external_exports.enum(["pending", "in_progress", "completed", "blocked", "cancelled"]).optional().describe("Backward-compatible alias for task_status in task actions"),
24537
25743
  priority: external_exports.enum(["low", "medium", "high", "urgent"]).optional().describe("Task priority"),
24538
25744
  order: external_exports.number().optional().describe("Task order within plan"),
24539
25745
  task_ids: external_exports.array(external_exports.string().uuid()).optional().describe("Task IDs for reorder_tasks"),
@@ -24595,8 +25801,10 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
24595
25801
  })
24596
25802
  },
24597
25803
  async (input) => {
24598
- const workspaceId = resolveWorkspaceId(input.workspace_id);
24599
- const projectId = resolveProjectId(input.project_id);
25804
+ let workspaceId = resolveWorkspaceId(input.workspace_id);
25805
+ const explicitProjectId = normalizeUuid(input.project_id);
25806
+ let projectId = explicitProjectId || resolveProjectId(void 0);
25807
+ const folderPath = input.folder_path || input.path || resolveFolderPath(void 0, sessionManager) || void 0;
24600
25808
  switch (input.action) {
24601
25809
  case "create_event": {
24602
25810
  if (!input.event_type || !input.title || !input.content) {
@@ -24834,6 +26042,7 @@ ${formatContent(result)}`
24834
26042
  if (!workspaceId) {
24835
26043
  return errorResult("create_task requires workspace_id. Call session_init first.");
24836
26044
  }
26045
+ const requestedTaskStatus = input.task_status ?? input.status;
24837
26046
  const result = await client.createTask({
24838
26047
  workspace_id: workspaceId,
24839
26048
  project_id: projectId,
@@ -24842,7 +26051,7 @@ ${formatContent(result)}`
24842
26051
  description: input.description,
24843
26052
  plan_id: input.plan_id ?? void 0,
24844
26053
  plan_step_id: input.plan_step_id,
24845
- status: input.task_status,
26054
+ status: requestedTaskStatus,
24846
26055
  priority: input.priority,
24847
26056
  order: input.order,
24848
26057
  code_refs: input.code_refs,
@@ -24870,12 +26079,13 @@ ${formatContent(result)}`
24870
26079
  if (!input.task_id) {
24871
26080
  return errorResult("update_task requires: task_id");
24872
26081
  }
26082
+ const requestedTaskStatus = input.task_status ?? input.status;
24873
26083
  const result = await client.updateTask({
24874
26084
  task_id: input.task_id,
24875
26085
  title: input.title,
24876
26086
  content: input.content,
24877
26087
  description: input.description,
24878
- status: input.task_status,
26088
+ status: requestedTaskStatus,
24879
26089
  priority: input.priority,
24880
26090
  order: input.order,
24881
26091
  plan_id: input.plan_id,
@@ -24885,11 +26095,11 @@ ${formatContent(result)}`
24885
26095
  blocked_reason: input.blocked_reason
24886
26096
  });
24887
26097
  let taskUpdateHint = "Task updated.";
24888
- if (input.task_status === "completed") {
26098
+ if (requestedTaskStatus === "completed") {
24889
26099
  taskUpdateHint = TASK_HINTS.completed;
24890
- } else if (input.task_status === "blocked") {
26100
+ } else if (requestedTaskStatus === "blocked") {
24891
26101
  taskUpdateHint = TASK_HINTS.blocked;
24892
- } else if (input.task_status === "cancelled") {
26102
+ } else if (requestedTaskStatus === "cancelled") {
24893
26103
  taskUpdateHint = TASK_HINTS.cancelled;
24894
26104
  }
24895
26105
  const resultWithHint = { ...result, hint: taskUpdateHint };
@@ -24912,11 +26122,12 @@ ${formatContent(result)}`
24912
26122
  if (!workspaceId) {
24913
26123
  return errorResult("list_tasks requires workspace_id. Call session_init first.");
24914
26124
  }
26125
+ const requestedTaskStatus = input.task_status ?? input.status;
24915
26126
  const result = await client.listTasks({
24916
26127
  workspace_id: workspaceId,
24917
26128
  project_id: projectId,
24918
26129
  plan_id: input.plan_id ?? void 0,
24919
- status: input.task_status,
26130
+ status: requestedTaskStatus,
24920
26131
  priority: input.priority,
24921
26132
  limit: input.limit,
24922
26133
  is_personal: input.is_personal
@@ -25203,11 +26414,12 @@ ${formatContent(result)}`
25203
26414
  const teamWorkspacesForTasks = await client.listTeamWorkspaces({ page_size: 100 });
25204
26415
  const workspacesForTasks = teamWorkspacesForTasks?.items || teamWorkspacesForTasks?.data?.items || [];
25205
26416
  const allTasks = [];
26417
+ const requestedTaskStatus = input.task_status ?? input.status;
25206
26418
  for (const ws of workspacesForTasks.slice(0, 10)) {
25207
26419
  try {
25208
26420
  const tasks = await client.listTasks({
25209
26421
  workspace_id: ws.id,
25210
- status: input.task_status,
26422
+ status: requestedTaskStatus,
25211
26423
  limit: input.limit ? Math.ceil(input.limit / workspacesForTasks.length) : 10
25212
26424
  });
25213
26425
  const items = tasks?.data?.tasks || tasks?.tasks || tasks?.data?.items || tasks?.items || [];
@@ -25662,8 +26874,10 @@ ${formatContent(result)}`
25662
26874
  })
25663
26875
  },
25664
26876
  async (input) => {
25665
- const workspaceId = resolveWorkspaceId(input.workspace_id);
25666
- const projectId = resolveProjectId(input.project_id);
26877
+ let workspaceId = resolveWorkspaceId(input.workspace_id);
26878
+ const explicitProjectId = normalizeUuid(input.project_id);
26879
+ let projectId = explicitProjectId || resolveProjectId(void 0);
26880
+ const folderPath = input.folder_path || input.path || resolveFolderPath(void 0, sessionManager) || void 0;
25667
26881
  switch (input.action) {
25668
26882
  case "list": {
25669
26883
  const result = await client.listProjects({
@@ -25713,10 +26927,50 @@ ${formatContent(result)}`
25713
26927
  if (!projectId) {
25714
26928
  return errorResult("index requires: project_id");
25715
26929
  }
25716
- const result = await client.indexProject(projectId);
25717
- return {
25718
- content: [{ type: "text", text: formatContent(result) }]
25719
- };
26930
+ try {
26931
+ const result = await client.indexProject(projectId);
26932
+ return {
26933
+ content: [{ type: "text", text: formatContent(result) }]
26934
+ };
26935
+ } catch (error) {
26936
+ if (!isRequiresIngestEndpointError(error)) {
26937
+ throw error;
26938
+ }
26939
+ if (!folderPath) {
26940
+ return errorResult(
26941
+ 'Index endpoint is unavailable for this local project type and no folder context is set. Run project(action="ingest_local", path="<folder>").'
26942
+ );
26943
+ }
26944
+ const validPath = await validateReadableDirectory(folderPath);
26945
+ if (!validPath.ok) {
26946
+ return errorResult(
26947
+ `Index endpoint is unavailable for this project type and folder context path is invalid: ${folderPath}. ${validPath.error}`
26948
+ );
26949
+ }
26950
+ const ingestOptions = {
26951
+ ...input.write_to_disk !== void 0 && { write_to_disk: input.write_to_disk },
26952
+ ...input.overwrite !== void 0 && { overwrite: input.overwrite },
26953
+ ...input.force !== void 0 && { force: input.force }
26954
+ };
26955
+ startBackgroundIngest(projectId, validPath.resolvedPath, ingestOptions);
26956
+ const response = {
26957
+ status: "started",
26958
+ message: "Index endpoint unavailable for this project type; started ingest_local fallback in background",
26959
+ project_id: projectId,
26960
+ path: validPath.resolvedPath,
26961
+ fallback_action: "ingest_local",
26962
+ note: "Use 'project' with action 'index_status' to monitor progress."
26963
+ };
26964
+ return {
26965
+ content: [
26966
+ {
26967
+ type: "text",
26968
+ text: `Index endpoint is unavailable for this local project type. Started ingest_local in background for directory: ${validPath.resolvedPath}. Use 'project' with action 'index_status' to monitor progress.`
26969
+ }
26970
+ ],
26971
+ structuredContent: response
26972
+ };
26973
+ }
25720
26974
  }
25721
26975
  case "overview": {
25722
26976
  if (!projectId) {
@@ -25746,12 +27000,122 @@ ${formatContent(result)}`
25746
27000
  };
25747
27001
  }
25748
27002
  case "index_status": {
25749
- if (!projectId) {
25750
- return errorResult("index_status requires: project_id");
27003
+ let resolvedFolderProjectId;
27004
+ if (folderPath) {
27005
+ const mapping = resolveWorkspace(folderPath);
27006
+ const mappedWorkspaceId = normalizeUuid(mapping.config?.workspace_id);
27007
+ if (!workspaceId && mappedWorkspaceId) {
27008
+ workspaceId = mappedWorkspaceId;
27009
+ }
27010
+ resolvedFolderProjectId = normalizeUuid(mapping.config?.project_id);
27011
+ }
27012
+ const localIndexProjectId = folderPath ? await indexedProjectIdForFolder(folderPath) : void 0;
27013
+ const candidateIds = [];
27014
+ const pushCandidate = (id) => {
27015
+ if (id && !candidateIds.includes(id)) {
27016
+ candidateIds.push(id);
27017
+ }
27018
+ };
27019
+ if (explicitProjectId) {
27020
+ pushCandidate(explicitProjectId);
27021
+ pushCandidate(resolvedFolderProjectId);
27022
+ pushCandidate(localIndexProjectId);
27023
+ pushCandidate(projectId);
27024
+ } else {
27025
+ pushCandidate(resolvedFolderProjectId);
27026
+ pushCandidate(localIndexProjectId);
27027
+ pushCandidate(projectId);
27028
+ }
27029
+ if (candidateIds.length === 0) {
27030
+ return errorResult(
27031
+ "index_status requires: project_id. Call init(...) first or pass project_id explicitly."
27032
+ );
27033
+ }
27034
+ let selected;
27035
+ for (const [index, candidateId] of candidateIds.entries()) {
27036
+ try {
27037
+ const statusResult = await client.projectIndexStatus(candidateId);
27038
+ const apiIndexed = apiResultReportsIndexed(statusResult);
27039
+ if (apiIndexed) {
27040
+ selected = { index, projectId: candidateId, result: statusResult, apiIndexed };
27041
+ break;
27042
+ }
27043
+ if (!selected) {
27044
+ selected = { index, projectId: candidateId, result: statusResult, apiIndexed };
27045
+ }
27046
+ } catch (error) {
27047
+ if (isNotFoundError(error)) {
27048
+ continue;
27049
+ }
27050
+ throw error;
27051
+ }
27052
+ }
27053
+ if (!selected) {
27054
+ return errorResult(
27055
+ "Project not found for current context. Call init(...) in this folder or pass a valid project_id explicitly."
27056
+ );
27057
+ }
27058
+ const originalProjectId = projectId;
27059
+ if (projectId !== selected.projectId && folderPath) {
27060
+ projectId = selected.projectId;
27061
+ sessionManager?.updateScope({
27062
+ workspace_id: workspaceId,
27063
+ project_id: projectId,
27064
+ folder_path: folderPath
27065
+ });
27066
+ }
27067
+ const locallyIndexed = localIndexProjectId !== void 0 ? localIndexProjectId === selected.projectId : Boolean(folderPath && await indexedProjectIdForFolder(folderPath));
27068
+ const apiIndexing = apiResultIsIndexing(selected.result);
27069
+ const indexed = selected.apiIndexed || locallyIndexed;
27070
+ const indexedAt = extractIndexTimestamp(selected.result);
27071
+ const ageHours = indexedAt !== void 0 ? Math.floor((Date.now() - indexedAt.getTime()) / (1e3 * 60 * 60)) : void 0;
27072
+ const freshness = classifyIndexFreshness(indexed, ageHours);
27073
+ const { confidence: confidenceLevel, reason: confidenceReason } = classifyIndexConfidence(
27074
+ indexed,
27075
+ selected.apiIndexed,
27076
+ locallyIndexed,
27077
+ freshness
27078
+ );
27079
+ const response = selected.result && typeof selected.result === "object" ? { ...selected.result } : {};
27080
+ const responseData = response.data && typeof response.data === "object" ? { ...response.data } : {};
27081
+ responseData.indexed = indexed;
27082
+ if (locallyIndexed && !selected.apiIndexed) {
27083
+ responseData.indexed_source = "local";
27084
+ }
27085
+ if (originalProjectId && originalProjectId !== selected.projectId) {
27086
+ responseData.original_project_id = originalProjectId;
27087
+ }
27088
+ responseData.resolved_project_id = selected.projectId;
27089
+ responseData.resolution_rank = selected.index;
27090
+ responseData.index_freshness = freshness;
27091
+ responseData.index_age_hours = ageHours ?? null;
27092
+ responseData.index_confidence = confidenceLevel;
27093
+ responseData.index_confidence_reason = confidenceReason;
27094
+ responseData.index_timestamp = indexedAt ? indexedAt.toISOString() : null;
27095
+ responseData.index_in_progress = apiIndexing;
27096
+ response.data = responseData;
27097
+ let text = "";
27098
+ if (apiIndexing) {
27099
+ text = indexed ? "Project indexing is in progress. Search is using the latest committed generation." : "Project indexing is in progress. Semantic search will be available after the first commit.";
27100
+ } else if (indexed) {
27101
+ text = locallyIndexed && !selected.apiIndexed ? "Project index is ready (local state). Semantic search is available." : "Project index is ready. Semantic search is available.";
27102
+ } else {
27103
+ text = 'Project index not found. Run project(action="ingest_local", path="<folder>") to start indexing.';
27104
+ }
27105
+ const ageDisplay = typeof ageHours === "number" ? `${ageHours}h` : "unknown";
27106
+ text += ` Freshness: ${freshness} (${ageDisplay}). Confidence: ${confidenceLevel}.`;
27107
+ if (confidenceLevel !== "high") {
27108
+ text += ` ${confidenceReason}`;
27109
+ }
27110
+ if (freshness === "stale" || freshness === "missing") {
27111
+ const ingestPath = folderPath || "<folder>";
27112
+ text += ` Refresh with project(action="ingest_local", path="${ingestPath}").`;
25751
27113
  }
25752
- const result = await client.projectIndexStatus(projectId);
25753
27114
  return {
25754
- content: [{ type: "text", text: formatContent(result) }]
27115
+ content: [{ type: "text", text: `${text}
27116
+
27117
+ ${formatContent(response)}` }],
27118
+ structuredContent: response
25755
27119
  };
25756
27120
  }
25757
27121
  case "index_history": {
@@ -25769,18 +27133,39 @@ ${formatContent(result)}`
25769
27133
  page: input.page,
25770
27134
  limit: input.page_size
25771
27135
  });
27136
+ const count = indexHistoryEntryCount(result);
27137
+ const response = result && typeof result === "object" ? Array.isArray(result) ? [...result] : { ...result } : result;
27138
+ if (response && typeof response === "object" && !Array.isArray(response)) {
27139
+ response.entries_count = count;
27140
+ if (response.data && typeof response.data === "object") {
27141
+ response.data = {
27142
+ ...response.data,
27143
+ entries_count: count
27144
+ };
27145
+ }
27146
+ }
27147
+ const structuredHistory = response && typeof response === "object" && !Array.isArray(response) ? response : void 0;
25772
27148
  return {
25773
- content: [{ type: "text", text: formatContent(result) }]
27149
+ content: [
27150
+ {
27151
+ type: "text",
27152
+ text: `Found ${count} index history entries.
27153
+
27154
+ ${formatContent(response)}`
27155
+ }
27156
+ ],
27157
+ ...structuredHistory ? { structuredContent: structuredHistory } : {}
25774
27158
  };
25775
27159
  }
25776
27160
  case "ingest_local": {
25777
- if (!input.path) {
27161
+ const ingestPath = input.path || input.folder_path;
27162
+ if (!ingestPath) {
25778
27163
  return errorResult("ingest_local requires: path");
25779
27164
  }
25780
27165
  if (!projectId) {
25781
27166
  return errorResult("ingest_local requires: project_id");
25782
27167
  }
25783
- const validPath = await validateReadableDirectory(input.path);
27168
+ const validPath = await validateReadableDirectory(ingestPath);
25784
27169
  if (!validPath.ok) {
25785
27170
  return errorResult(validPath.error);
25786
27171
  }
@@ -25793,7 +27178,7 @@ ${formatContent(result)}`
25793
27178
  const forceNote = input.force ? " (force mode - version checks bypassed)" : "";
25794
27179
  const result = {
25795
27180
  status: "started",
25796
- message: `Ingestion running in background${forceNote}`,
27181
+ message: `Updating index in background${forceNote}`,
25797
27182
  project_id: projectId,
25798
27183
  path: validPath.resolvedPath,
25799
27184
  ...input.write_to_disk !== void 0 && { write_to_disk: input.write_to_disk },
@@ -25805,7 +27190,7 @@ ${formatContent(result)}`
25805
27190
  content: [
25806
27191
  {
25807
27192
  type: "text",
25808
- text: `Ingestion started in background${forceNote} for directory: ${validPath.resolvedPath}. Use 'project' with action 'index_status' to monitor progress.`
27193
+ text: `Updating index in background${forceNote} for directory: ${validPath.resolvedPath}. Use 'project' with action 'index_status' to monitor progress.`
25809
27194
  }
25810
27195
  ]
25811
27196
  };
@@ -26770,13 +28155,13 @@ Example workflow:
26770
28155
  );
26771
28156
  }
26772
28157
  if (input.file_path) {
26773
- const fs21 = await import("fs/promises");
28158
+ const fs22 = await import("fs/promises");
26774
28159
  const pathModule = await import("path");
26775
28160
  const filePath = input.file_path.startsWith("~") ? input.file_path.replace("~", process.env.HOME || "") : input.file_path;
26776
28161
  const resolvedPath = pathModule.resolve(filePath);
26777
28162
  let fileStats;
26778
28163
  try {
26779
- fileStats = await fs21.stat(resolvedPath);
28164
+ fileStats = await fs22.stat(resolvedPath);
26780
28165
  } catch {
26781
28166
  return errorResult(`File not found: ${resolvedPath}`);
26782
28167
  }
@@ -26823,7 +28208,7 @@ Example workflow:
26823
28208
  mime_type: mimeType,
26824
28209
  tags: input.tags
26825
28210
  });
26826
- const fileBuffer = await fs21.readFile(resolvedPath);
28211
+ const fileBuffer = await fs22.readFile(resolvedPath);
26827
28212
  const uploadResponse = await fetch(uploadInit.upload_url, {
26828
28213
  method: "PUT",
26829
28214
  headers: uploadInit.headers,
@@ -28227,11 +29612,40 @@ var SessionManager = class _SessionManager {
28227
29612
  this.folderPath = contextFolderPath;
28228
29613
  }
28229
29614
  }
29615
+ /**
29616
+ * Update active workspace/project scope without resetting session identity.
29617
+ * Used when tools auto-resolve stale scope from folder/local index context.
29618
+ */
29619
+ updateScope(input) {
29620
+ const nextWorkspaceId = typeof input.workspace_id === "string" && input.workspace_id.trim() ? input.workspace_id : void 0;
29621
+ const nextProjectId = typeof input.project_id === "string" && input.project_id.trim() ? input.project_id : void 0;
29622
+ const nextFolderPath = typeof input.folder_path === "string" && input.folder_path.trim() ? input.folder_path : void 0;
29623
+ if (!this.context) {
29624
+ this.context = {};
29625
+ }
29626
+ if (nextWorkspaceId) {
29627
+ this.context.workspace_id = nextWorkspaceId;
29628
+ }
29629
+ if (nextProjectId) {
29630
+ this.context.project_id = nextProjectId;
29631
+ }
29632
+ if (nextFolderPath) {
29633
+ this.context.folder_path = nextFolderPath;
29634
+ this.folderPath = nextFolderPath;
29635
+ }
29636
+ if (nextWorkspaceId || nextProjectId) {
29637
+ this.initialized = true;
29638
+ this.client.setDefaults({
29639
+ workspace_id: typeof this.context.workspace_id === "string" ? this.context.workspace_id : void 0,
29640
+ project_id: typeof this.context.project_id === "string" ? this.context.project_id : void 0
29641
+ });
29642
+ }
29643
+ }
28230
29644
  /**
28231
29645
  * Set the folder path hint (can be passed from tools that know the workspace path)
28232
29646
  */
28233
- setFolderPath(path22) {
28234
- this.folderPath = path22;
29647
+ setFolderPath(path23) {
29648
+ this.folderPath = path23;
28235
29649
  }
28236
29650
  /**
28237
29651
  * Mark that context_smart has been called in this session.
@@ -28421,7 +29835,7 @@ var SessionManager = class _SessionManager {
28421
29835
  }
28422
29836
  if (this.ideRoots.length === 0) {
28423
29837
  const cwd = process.cwd();
28424
- const fs21 = await import("fs");
29838
+ const fs22 = await import("fs");
28425
29839
  const projectIndicators = [
28426
29840
  ".git",
28427
29841
  "package.json",
@@ -28431,7 +29845,7 @@ var SessionManager = class _SessionManager {
28431
29845
  ];
28432
29846
  const hasProjectIndicator = projectIndicators.some((f) => {
28433
29847
  try {
28434
- return fs21.existsSync(`${cwd}/${f}`);
29848
+ return fs22.existsSync(`${cwd}/${f}`);
28435
29849
  } catch {
28436
29850
  return false;
28437
29851
  }
@@ -29010,9 +30424,9 @@ async function runHttpGateway() {
29010
30424
 
29011
30425
  // src/index.ts
29012
30426
  init_version();
29013
- import { existsSync as existsSync18, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
29014
- import { homedir as homedir20 } from "os";
29015
- import { join as join23 } from "path";
30427
+ import { existsSync as existsSync18, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
30428
+ import { homedir as homedir21 } from "os";
30429
+ import { join as join24 } from "path";
29016
30430
 
29017
30431
  // src/setup.ts
29018
30432
  import * as fs7 from "node:fs/promises";
@@ -29705,10 +31119,91 @@ function createProgressBar(progress, width = 30) {
29705
31119
  const emptyBar = "\u2591".repeat(empty);
29706
31120
  return `${colors.cyan}${filledBar}${colors.dim}${emptyBar}${colors.reset}`;
29707
31121
  }
31122
+ async function selectProjectForCurrentDirectory(client, cwd, workspaceId, dryRun, rl) {
31123
+ if (!workspaceId || workspaceId === "dry-run") {
31124
+ return void 0;
31125
+ }
31126
+ const folderName = path8.basename(cwd) || "project";
31127
+ let projects = [];
31128
+ try {
31129
+ const result = await client.listProjects({
31130
+ workspace_id: workspaceId,
31131
+ page_size: 200
31132
+ });
31133
+ projects = Array.isArray(result?.items) ? result.items : Array.isArray(result?.data?.items) ? result.data.items : [];
31134
+ } catch {
31135
+ projects = [];
31136
+ }
31137
+ projects = projects.filter((p) => typeof p?.id === "string" && typeof p?.name === "string").sort((a, b) => (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase()));
31138
+ const local = readLocalConfig(cwd);
31139
+ const linked = local?.project_id ? projects.find((p) => p.id === local.project_id) : void 0;
31140
+ const folderMatch = projects.find((p) => p.name?.toLowerCase() === folderName.toLowerCase());
31141
+ const options = [];
31142
+ const seen = /* @__PURE__ */ new Set();
31143
+ if (linked?.id && linked?.name) {
31144
+ seen.add(linked.id);
31145
+ options.push({
31146
+ label: `Use currently linked project: ${linked.name} (${linked.id})`,
31147
+ kind: "existing",
31148
+ project: { id: linked.id, name: linked.name }
31149
+ });
31150
+ }
31151
+ if (folderMatch?.id && folderMatch?.name && !seen.has(folderMatch.id)) {
31152
+ seen.add(folderMatch.id);
31153
+ options.push({
31154
+ label: `Use project matching this folder: ${folderMatch.name} (${folderMatch.id})`,
31155
+ kind: "existing",
31156
+ project: { id: folderMatch.id, name: folderMatch.name }
31157
+ });
31158
+ }
31159
+ for (const project of projects) {
31160
+ if (!project.id || !project.name || seen.has(project.id)) continue;
31161
+ seen.add(project.id);
31162
+ options.push({
31163
+ label: `Use existing project: ${project.name} (${project.id})`,
31164
+ kind: "existing",
31165
+ project: { id: project.id, name: project.name }
31166
+ });
31167
+ }
31168
+ options.push({ label: `Create new project: ${folderName}`, kind: "create" });
31169
+ options.push({ label: "Skip project selection", kind: "skip" });
31170
+ if (options.length === 0) {
31171
+ return void 0;
31172
+ }
31173
+ console.log("\nProject selection (current directory):");
31174
+ options.forEach((opt, i) => console.log(` ${i + 1}) ${opt.label}`));
31175
+ const choiceRaw = normalizeInput(
31176
+ await rl.question(`Choose [1-${options.length}] (default 1): `)
31177
+ );
31178
+ const choiceNum = Number.parseInt(choiceRaw || "1", 10);
31179
+ const selected = Number.isFinite(choiceNum) ? options[choiceNum - 1] : options[0];
31180
+ if (!selected || selected.kind === "skip") {
31181
+ return void 0;
31182
+ }
31183
+ if (selected.kind === "existing" && selected.project) {
31184
+ return selected.project;
31185
+ }
31186
+ if (selected.kind === "create") {
31187
+ if (dryRun) {
31188
+ return { id: "dry-run-project", name: folderName };
31189
+ }
31190
+ const created = await client.createProject({
31191
+ workspace_id: workspaceId,
31192
+ name: folderName,
31193
+ description: `Project linked from ${cwd}`
31194
+ });
31195
+ const projectId = typeof created?.id === "string" ? created.id : typeof created?.data?.id === "string" ? created.data.id : void 0;
31196
+ const projectName = typeof created?.name === "string" ? created.name : typeof created?.data?.name === "string" ? created.data.name : folderName;
31197
+ if (projectId) {
31198
+ return { id: projectId, name: projectName };
31199
+ }
31200
+ }
31201
+ return void 0;
31202
+ }
29708
31203
  async function indexProjectWithProgress(client, projectPath, workspaceId) {
29709
31204
  const projectName = path8.basename(projectPath);
29710
31205
  console.log(`
29711
- ${colors.bright}Indexing: ${projectName}${colors.reset}`);
31206
+ ${colors.bright}Updating index for '${projectName}'...${colors.reset}`);
29712
31207
  console.log(`${colors.dim}${projectPath}${colors.reset}
29713
31208
  `);
29714
31209
  let projectId;
@@ -29758,12 +31253,12 @@ ${colors.bright}Indexing: ${projectName}${colors.reset}`);
29758
31253
  const countResult = await countIndexableFiles(projectPath, { maxFiles: 5e4 });
29759
31254
  totalFiles = countResult.count;
29760
31255
  if (countResult.stopped) {
29761
- console.log(`${colors.dim}Found 50,000+ indexable files${colors.reset}`);
31256
+ console.log(`${colors.dim}Found 50,000+ files for indexing${colors.reset}`);
29762
31257
  } else if (totalFiles === 0) {
29763
31258
  console.log(`${colors.dim}No indexable files found${colors.reset}`);
29764
31259
  return;
29765
31260
  } else {
29766
- console.log(`${colors.dim}Found ${totalFiles.toLocaleString()} indexable files${colors.reset}`);
31261
+ console.log(`${colors.dim}Found ${totalFiles.toLocaleString()} files for indexing${colors.reset}`);
29767
31262
  }
29768
31263
  } catch {
29769
31264
  console.log(`${colors.dim}Scanning files...${colors.reset}`);
@@ -29826,15 +31321,19 @@ ${colors.bright}Indexing: ${projectName}${colors.reset}`);
29826
31321
  const finalSize = formatBytes(bytesIndexed);
29827
31322
  process.stdout.write("\r" + " ".repeat(120) + "\r");
29828
31323
  if (failedBatches > 0) {
29829
- console.log(`${colors.yellow}\u2713${colors.reset} Indexed ${colors.bright}${filesIndexed.toLocaleString()}${colors.reset} files (${finalSize}) in ${elapsed}s (${failedBatches} batches had errors)`);
31324
+ console.log(
31325
+ `${colors.yellow}\u2713${colors.reset} Index update complete: ${colors.bright}${filesIndexed.toLocaleString()}${colors.reset} files indexed (${finalSize}) in ${elapsed}s (${failedBatches} batches had errors)`
31326
+ );
29830
31327
  } else {
29831
- console.log(`${colors.green}\u2713${colors.reset} Indexed ${colors.bright}${filesIndexed.toLocaleString()}${colors.reset} files (${finalSize}) in ${elapsed}s`);
31328
+ console.log(
31329
+ `${colors.green}\u2713${colors.reset} Index update complete: ${colors.bright}${filesIndexed.toLocaleString()}${colors.reset} files indexed (${finalSize}) in ${elapsed}s`
31330
+ );
29832
31331
  }
29833
31332
  } catch (err) {
29834
31333
  clearInterval(progressInterval);
29835
31334
  process.stdout.write("\r" + " ".repeat(120) + "\r");
29836
31335
  const msg = err instanceof Error ? err.message : String(err);
29837
- console.log(`${colors.yellow}! Indexing failed: ${msg}${colors.reset}`);
31336
+ console.log(`${colors.yellow}! Index update failed: ${msg}${colors.reset}`);
29838
31337
  }
29839
31338
  }
29840
31339
  async function runSetupWizard(args) {
@@ -29973,10 +31472,10 @@ Code: ${device.user_code}`);
29973
31472
  if (poll && poll.status === "pending") {
29974
31473
  const intervalSeconds = typeof poll.interval === "number" ? poll.interval : 5;
29975
31474
  const waitMs = Math.max(1, intervalSeconds) * 1e3;
29976
- await new Promise((resolve16) => setTimeout(resolve16, waitMs));
31475
+ await new Promise((resolve18) => setTimeout(resolve18, waitMs));
29977
31476
  continue;
29978
31477
  }
29979
- await new Promise((resolve16) => setTimeout(resolve16, 1e3));
31478
+ await new Promise((resolve18) => setTimeout(resolve18, 1e3));
29980
31479
  }
29981
31480
  if (!accessToken) {
29982
31481
  throw new Error(
@@ -30024,6 +31523,7 @@ Code: ${device.user_code}`);
30024
31523
  }
30025
31524
  let workspaceId;
30026
31525
  let workspaceName;
31526
+ let selectedCurrentProject;
30027
31527
  console.log("Workspace setup:");
30028
31528
  console.log(" 1) Create a new workspace");
30029
31529
  console.log(" 2) Select an existing workspace");
@@ -30081,6 +31581,21 @@ Code: ${device.user_code}`);
30081
31581
  }
30082
31582
  }
30083
31583
  }
31584
+ if (workspaceId && workspaceId !== "dry-run") {
31585
+ selectedCurrentProject = await selectProjectForCurrentDirectory(
31586
+ client,
31587
+ process.cwd(),
31588
+ workspaceId,
31589
+ dryRun,
31590
+ rl
31591
+ );
31592
+ if (selectedCurrentProject) {
31593
+ console.log(
31594
+ `Selected project: ${selectedCurrentProject.name} (${selectedCurrentProject.id})
31595
+ `
31596
+ );
31597
+ }
31598
+ }
30084
31599
  const NO_HOOKS_EDITORS2 = ["codex", "aider", "antigravity"];
30085
31600
  const getModeForEditor = (editor) => NO_HOOKS_EDITORS2.includes(editor) ? "full" : "bootstrap";
30086
31601
  const detectedPlanName = await client.getPlanName();
@@ -30399,6 +31914,10 @@ Applying to ${projects.length} project(s)...`);
30399
31914
  workspace_id: workspaceId,
30400
31915
  workspace_name: workspaceName,
30401
31916
  create_parent_mapping: createParentMapping,
31917
+ ...path8.resolve(projectPath) === path8.resolve(process.cwd()) && selectedCurrentProject ? {
31918
+ project_id: selectedCurrentProject.id,
31919
+ project_name: selectedCurrentProject.name
31920
+ } : {},
30402
31921
  // Include version and config info for desktop app compatibility
30403
31922
  version: VERSION,
30404
31923
  configured_editors: configuredEditors,
@@ -30538,7 +32057,7 @@ Applying to ${projects.length} project(s)...`);
30538
32057
  if (indexingEnabled) {
30539
32058
  await indexProjectWithProgress(client, process.cwd(), cwdConfig.workspace_id);
30540
32059
  } else {
30541
- console.log("\nSkipping indexing. You can index later with: contextstream-mcp index <path>");
32060
+ console.log("\nIndexing skipped for now. You can start it later with: contextstream-mcp index <path>");
30542
32061
  }
30543
32062
  }
30544
32063
  } catch {
@@ -30553,7 +32072,7 @@ Applying to ${projects.length} project(s)...`);
30553
32072
  console.log("though larger projects may take a bit longer.\n");
30554
32073
  console.log("Your code is private and securely stored.\n");
30555
32074
  const indexChoice = normalizeInput(
30556
- await rl.question("Perform indexing for full-featured context? [Y/n]: ")
32075
+ await rl.question("Update index for full-featured context now? [Y/n]: ")
30557
32076
  ).toLowerCase();
30558
32077
  const indexingEnabled = indexChoice !== "n" && indexChoice !== "no";
30559
32078
  for (const projectPath of projects) {
@@ -30571,7 +32090,7 @@ Applying to ${projects.length} project(s)...`);
30571
32090
  await indexProjectWithProgress(client, projectPath, workspaceId);
30572
32091
  }
30573
32092
  } else {
30574
- console.log("\nSkipping indexing. You can index later with: contextstream-mcp index <path>");
32093
+ console.log("\nIndexing skipped for now. You can start it later with: contextstream-mcp index <path>");
30575
32094
  }
30576
32095
  }
30577
32096
  console.log("\nDone.");
@@ -30602,14 +32121,14 @@ Applying to ${projects.length} project(s)...`);
30602
32121
  // src/index.ts
30603
32122
  var ENABLE_PROMPTS2 = (process.env.CONTEXTSTREAM_ENABLE_PROMPTS || "true").toLowerCase() !== "false";
30604
32123
  function showFirstRunMessage() {
30605
- const configDir = join23(homedir20(), ".contextstream");
30606
- const starShownFile = join23(configDir, ".star-shown");
32124
+ const configDir = join24(homedir21(), ".contextstream");
32125
+ const starShownFile = join24(configDir, ".star-shown");
30607
32126
  if (existsSync18(starShownFile)) {
30608
32127
  return;
30609
32128
  }
30610
32129
  if (!existsSync18(configDir)) {
30611
32130
  try {
30612
- mkdirSync6(configDir, { recursive: true });
32131
+ mkdirSync7(configDir, { recursive: true });
30613
32132
  } catch {
30614
32133
  return;
30615
32134
  }
@@ -30622,7 +32141,7 @@ function showFirstRunMessage() {
30622
32141
  console.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
30623
32142
  console.error("");
30624
32143
  try {
30625
- writeFileSync7(starShownFile, (/* @__PURE__ */ new Date()).toISOString());
32144
+ writeFileSync8(starShownFile, (/* @__PURE__ */ new Date()).toISOString());
30626
32145
  } catch {
30627
32146
  }
30628
32147
  }