@contextstream/mcp-server 0.4.71 → 0.4.73

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
@@ -1109,6 +1109,7 @@ async function readFilesFromDirectory(rootPath, options = {}) {
1109
1109
  } catch {
1110
1110
  return;
1111
1111
  }
1112
+ entries.sort((a, b) => a.name.localeCompare(b.name));
1112
1113
  for (const entry of entries) {
1113
1114
  if (files.length >= maxFiles) break;
1114
1115
  const fullPath = path2.join(dir, entry.name);
@@ -1168,6 +1169,7 @@ async function* readAllFilesInBatches(rootPath, options = {}) {
1168
1169
  } catch {
1169
1170
  return;
1170
1171
  }
1172
+ entries.sort((a, b) => a.name.localeCompare(b.name));
1171
1173
  for (const entry of entries) {
1172
1174
  const fullPath = path2.join(dir, entry.name);
1173
1175
  const relPath = path2.join(relativePath, entry.name);
@@ -1254,6 +1256,7 @@ async function* readChangedFilesInBatches(rootPath, sinceTimestamp, options = {}
1254
1256
  } catch {
1255
1257
  return;
1256
1258
  }
1259
+ entries.sort((a, b) => a.name.localeCompare(b.name));
1257
1260
  for (const entry of entries) {
1258
1261
  const fullPath = path2.join(dir, entry.name);
1259
1262
  const relPath = path2.join(relativePath, entry.name);
@@ -1344,6 +1347,7 @@ async function countIndexableFiles(rootPath, options = {}) {
1344
1347
  } catch {
1345
1348
  return;
1346
1349
  }
1350
+ entries.sort((a, b) => a.name.localeCompare(b.name));
1347
1351
  for (const entry of entries) {
1348
1352
  if (count >= maxFiles) {
1349
1353
  stopped = true;
@@ -4896,7 +4900,7 @@ __export(post_write_exports, {
4896
4900
  });
4897
4901
  import * as fs9 from "node:fs";
4898
4902
  import * as path10 from "node:path";
4899
- import { homedir as homedir8 } from "node:os";
4903
+ import { homedir as homedir9 } from "node:os";
4900
4904
  function extractFilePath(input) {
4901
4905
  if (input.tool_input) {
4902
4906
  const filePath = input.tool_input.file_path || input.tool_input.notebook_path || input.tool_input.path;
@@ -4966,7 +4970,7 @@ function loadApiConfig(startDir) {
4966
4970
  currentDir = parentDir;
4967
4971
  }
4968
4972
  if (!apiKey) {
4969
- const homeMcpPath = path10.join(homedir8(), ".mcp.json");
4973
+ const homeMcpPath = path10.join(homedir9(), ".mcp.json");
4970
4974
  if (fs9.existsSync(homeMcpPath)) {
4971
4975
  try {
4972
4976
  const content = fs9.readFileSync(homeMcpPath, "utf-8");
@@ -5246,7 +5250,7 @@ var init_post_write = __esm({
5246
5250
  // src/hooks/common.ts
5247
5251
  import * as fs10 from "node:fs";
5248
5252
  import * as path11 from "node:path";
5249
- import { homedir as homedir9 } from "node:os";
5253
+ import { homedir as homedir10 } from "node:os";
5250
5254
  function readHookInput() {
5251
5255
  try {
5252
5256
  return JSON.parse(fs10.readFileSync(0, "utf8"));
@@ -5311,7 +5315,7 @@ function loadHookConfig(cwd) {
5311
5315
  searchDir = parentDir;
5312
5316
  }
5313
5317
  if (!apiKey && !jwt) {
5314
- const homeMcpPath = path11.join(homedir9(), ".mcp.json");
5318
+ const homeMcpPath = path11.join(homedir10(), ".mcp.json");
5315
5319
  if (fs10.existsSync(homeMcpPath)) {
5316
5320
  try {
5317
5321
  const config = JSON.parse(fs10.readFileSync(homeMcpPath, "utf8"));
@@ -5476,7 +5480,7 @@ __export(post_tool_use_failure_exports, {
5476
5480
  });
5477
5481
  import * as fs11 from "node:fs";
5478
5482
  import * as path12 from "node:path";
5479
- import { homedir as homedir10 } from "node:os";
5483
+ import { homedir as homedir11 } from "node:os";
5480
5484
  function extractErrorText(input) {
5481
5485
  return typeof input.error === "string" && input.error || typeof input.tool_error === "string" && input.tool_error || typeof input.stderr === "string" && input.stderr || "Tool execution failed";
5482
5486
  }
@@ -5546,7 +5550,7 @@ var init_post_tool_use_failure = __esm({
5546
5550
  "src/hooks/post-tool-use-failure.ts"() {
5547
5551
  "use strict";
5548
5552
  init_common();
5549
- FAILURE_COUNTERS_FILE = path12.join(homedir10(), ".contextstream", "hook-failure-counts.json");
5553
+ FAILURE_COUNTERS_FILE = path12.join(homedir11(), ".contextstream", "hook-failure-counts.json");
5550
5554
  isDirectRun2 = process.argv[1]?.includes("post-tool-use-failure") || process.argv[2] === "post-tool-use-failure";
5551
5555
  if (isDirectRun2) {
5552
5556
  runPostToolUseFailureHook().catch(() => process.exit(0));
@@ -6003,7 +6007,7 @@ var init_teammate_idle = __esm({
6003
6007
  // src/hooks/prompt-state.ts
6004
6008
  import * as fs13 from "node:fs";
6005
6009
  import * as path13 from "node:path";
6006
- import { homedir as homedir11 } from "node:os";
6010
+ import { homedir as homedir12 } from "node:os";
6007
6011
  function defaultState() {
6008
6012
  return { workspaces: {} };
6009
6013
  }
@@ -6205,7 +6209,7 @@ var STATE_PATH;
6205
6209
  var init_prompt_state = __esm({
6206
6210
  "src/hooks/prompt-state.ts"() {
6207
6211
  "use strict";
6208
- STATE_PATH = path13.join(homedir11(), ".contextstream", "prompt-state.json");
6212
+ STATE_PATH = path13.join(homedir12(), ".contextstream", "prompt-state.json");
6209
6213
  }
6210
6214
  });
6211
6215
 
@@ -6216,7 +6220,7 @@ __export(pre_tool_use_exports, {
6216
6220
  });
6217
6221
  import * as fs14 from "node:fs";
6218
6222
  import * as path14 from "node:path";
6219
- import { homedir as homedir12 } from "node:os";
6223
+ import { homedir as homedir13 } from "node:os";
6220
6224
  function isDiscoveryGlob(pattern) {
6221
6225
  const patternLower = pattern.toLowerCase();
6222
6226
  for (const p of DISCOVERY_PATTERNS) {
@@ -6642,7 +6646,7 @@ var init_pre_tool_use = __esm({
6642
6646
  "use strict";
6643
6647
  init_prompt_state();
6644
6648
  ENABLED2 = process.env.CONTEXTSTREAM_HOOK_ENABLED !== "false";
6645
- INDEX_STATUS_FILE = path14.join(homedir12(), ".contextstream", "indexed-projects.json");
6649
+ INDEX_STATUS_FILE = path14.join(homedir13(), ".contextstream", "indexed-projects.json");
6646
6650
  DEBUG_FILE = "/tmp/pretooluse-hook-debug.log";
6647
6651
  STALE_THRESHOLD_DAYS = 7;
6648
6652
  CONTEXT_FRESHNESS_SECONDS = 120;
@@ -6664,7 +6668,7 @@ __export(user_prompt_submit_exports, {
6664
6668
  });
6665
6669
  import * as fs15 from "node:fs";
6666
6670
  import * as path15 from "node:path";
6667
- import { homedir as homedir13 } from "node:os";
6671
+ import { homedir as homedir14 } from "node:os";
6668
6672
  function loadConfigFromMcpJson(cwd) {
6669
6673
  let searchDir = path15.resolve(cwd);
6670
6674
  for (let i = 0; i < 5; i++) {
@@ -6709,7 +6713,7 @@ function loadConfigFromMcpJson(cwd) {
6709
6713
  searchDir = parentDir;
6710
6714
  }
6711
6715
  if (!API_KEY2) {
6712
- const homeMcpPath = path15.join(homedir13(), ".mcp.json");
6716
+ const homeMcpPath = path15.join(homedir14(), ".mcp.json");
6713
6717
  if (fs15.existsSync(homeMcpPath)) {
6714
6718
  try {
6715
6719
  const content = fs15.readFileSync(homeMcpPath, "utf-8");
@@ -7271,7 +7275,7 @@ __export(pre_compact_exports, {
7271
7275
  });
7272
7276
  import * as fs16 from "node:fs";
7273
7277
  import * as path16 from "node:path";
7274
- import { homedir as homedir14 } from "node:os";
7278
+ import { homedir as homedir15 } from "node:os";
7275
7279
  function loadConfigFromMcpJson2(cwd) {
7276
7280
  let searchDir = path16.resolve(cwd);
7277
7281
  for (let i = 0; i < 5; i++) {
@@ -7310,7 +7314,7 @@ function loadConfigFromMcpJson2(cwd) {
7310
7314
  searchDir = parentDir;
7311
7315
  }
7312
7316
  if (!API_KEY3) {
7313
- const homeMcpPath = path16.join(homedir14(), ".mcp.json");
7317
+ const homeMcpPath = path16.join(homedir15(), ".mcp.json");
7314
7318
  if (fs16.existsSync(homeMcpPath)) {
7315
7319
  try {
7316
7320
  const content = fs16.readFileSync(homeMcpPath, "utf-8");
@@ -7594,7 +7598,7 @@ __export(post_compact_exports, {
7594
7598
  });
7595
7599
  import * as fs17 from "node:fs";
7596
7600
  import * as path17 from "node:path";
7597
- import { homedir as homedir15 } from "node:os";
7601
+ import { homedir as homedir16 } from "node:os";
7598
7602
  function loadConfigFromMcpJson3(cwd) {
7599
7603
  let searchDir = path17.resolve(cwd);
7600
7604
  for (let i = 0; i < 5; i++) {
@@ -7633,7 +7637,7 @@ function loadConfigFromMcpJson3(cwd) {
7633
7637
  searchDir = parentDir;
7634
7638
  }
7635
7639
  if (!API_KEY4) {
7636
- const homeMcpPath = path17.join(homedir15(), ".mcp.json");
7640
+ const homeMcpPath = path17.join(homedir16(), ".mcp.json");
7637
7641
  if (fs17.existsSync(homeMcpPath)) {
7638
7642
  try {
7639
7643
  const content = fs17.readFileSync(homeMcpPath, "utf-8");
@@ -7772,7 +7776,7 @@ __export(session_init_exports, {
7772
7776
  });
7773
7777
  import * as fs18 from "node:fs";
7774
7778
  import * as path18 from "node:path";
7775
- import { homedir as homedir16 } from "node:os";
7779
+ import { homedir as homedir17 } from "node:os";
7776
7780
  function loadConfigFromMcpJson4(cwd) {
7777
7781
  let searchDir = path18.resolve(cwd);
7778
7782
  for (let i = 0; i < 5; i++) {
@@ -7817,7 +7821,7 @@ function loadConfigFromMcpJson4(cwd) {
7817
7821
  searchDir = parentDir;
7818
7822
  }
7819
7823
  if (!API_KEY5) {
7820
- const homeMcpPath = path18.join(homedir16(), ".mcp.json");
7824
+ const homeMcpPath = path18.join(homedir17(), ".mcp.json");
7821
7825
  if (fs18.existsSync(homeMcpPath)) {
7822
7826
  try {
7823
7827
  const content = fs18.readFileSync(homeMcpPath, "utf-8");
@@ -8076,7 +8080,7 @@ __export(session_end_exports, {
8076
8080
  });
8077
8081
  import * as fs19 from "node:fs";
8078
8082
  import * as path19 from "node:path";
8079
- import { homedir as homedir17 } from "node:os";
8083
+ import { homedir as homedir18 } from "node:os";
8080
8084
  function loadConfigFromMcpJson5(cwd) {
8081
8085
  let searchDir = path19.resolve(cwd);
8082
8086
  for (let i = 0; i < 5; i++) {
@@ -8118,7 +8122,7 @@ function loadConfigFromMcpJson5(cwd) {
8118
8122
  searchDir = parentDir;
8119
8123
  }
8120
8124
  if (!API_KEY6) {
8121
- const homeMcpPath = path19.join(homedir17(), ".mcp.json");
8125
+ const homeMcpPath = path19.join(homedir18(), ".mcp.json");
8122
8126
  if (fs19.existsSync(homeMcpPath)) {
8123
8127
  try {
8124
8128
  const content = fs19.readFileSync(homeMcpPath, "utf-8");
@@ -8508,7 +8512,7 @@ __export(verify_key_exports, {
8508
8512
  });
8509
8513
  import * as fs20 from "node:fs";
8510
8514
  import * as path20 from "node:path";
8511
- import { homedir as homedir18 } from "node:os";
8515
+ import { homedir as homedir19 } from "node:os";
8512
8516
  function maskApiKey2(key) {
8513
8517
  if (!key || key.length < 8) return "***";
8514
8518
  const prefixMatch = key.match(/^([a-z]{2,3}_)/i);
@@ -8541,11 +8545,11 @@ function extractFromMcpConfig(config) {
8541
8545
  function getClaudeDesktopConfigPath() {
8542
8546
  const platform2 = process.platform;
8543
8547
  if (platform2 === "darwin") {
8544
- return path20.join(homedir18(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
8548
+ return path20.join(homedir19(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
8545
8549
  } else if (platform2 === "win32") {
8546
8550
  return path20.join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
8547
8551
  } else {
8548
- return path20.join(homedir18(), ".config", "Claude", "claude_desktop_config.json");
8552
+ return path20.join(homedir19(), ".config", "Claude", "claude_desktop_config.json");
8549
8553
  }
8550
8554
  }
8551
8555
  function loadApiKey() {
@@ -8583,7 +8587,7 @@ function loadApiKey() {
8583
8587
  if (parentDir === searchDir) break;
8584
8588
  searchDir = parentDir;
8585
8589
  }
8586
- const globalMcpPath = path20.join(homedir18(), ".mcp.json");
8590
+ const globalMcpPath = path20.join(homedir19(), ".mcp.json");
8587
8591
  if (fs20.existsSync(globalMcpPath)) {
8588
8592
  try {
8589
8593
  const content = fs20.readFileSync(globalMcpPath, "utf-8");
@@ -8602,7 +8606,7 @@ function loadApiKey() {
8602
8606
  }
8603
8607
  const cursorPaths = [
8604
8608
  path20.join(process.cwd(), ".cursor", "mcp.json"),
8605
- path20.join(homedir18(), ".cursor", "mcp.json")
8609
+ path20.join(homedir19(), ".cursor", "mcp.json")
8606
8610
  ];
8607
8611
  for (const cursorPath of cursorPaths) {
8608
8612
  if (fs20.existsSync(cursorPath)) {
@@ -8640,9 +8644,9 @@ function loadApiKey() {
8640
8644
  }
8641
8645
  }
8642
8646
  const vscodePaths = [
8643
- path20.join(homedir18(), ".vscode", "mcp.json"),
8644
- path20.join(homedir18(), ".codeium", "windsurf", "mcp_config.json"),
8645
- path20.join(homedir18(), ".continue", "config.json")
8647
+ path20.join(homedir19(), ".vscode", "mcp.json"),
8648
+ path20.join(homedir19(), ".codeium", "windsurf", "mcp_config.json"),
8649
+ path20.join(homedir19(), ".continue", "config.json")
8646
8650
  ];
8647
8651
  for (const vsPath of vscodePaths) {
8648
8652
  if (fs20.existsSync(vsPath)) {
@@ -8662,7 +8666,7 @@ function loadApiKey() {
8662
8666
  }
8663
8667
  }
8664
8668
  }
8665
- const credentialsPath = path20.join(homedir18(), ".contextstream", "credentials.json");
8669
+ const credentialsPath = path20.join(homedir19(), ".contextstream", "credentials.json");
8666
8670
  if (fs20.existsSync(credentialsPath)) {
8667
8671
  try {
8668
8672
  const content = fs20.readFileSync(credentialsPath, "utf-8");
@@ -12888,6 +12892,7 @@ function loadConfig() {
12888
12892
  // src/client.ts
12889
12893
  import { randomUUID } from "node:crypto";
12890
12894
  import * as path5 from "node:path";
12895
+ import { homedir as homedir4 } from "node:os";
12891
12896
 
12892
12897
  // src/auth-context.ts
12893
12898
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -13476,6 +13481,59 @@ function isDecisionResult(item) {
13476
13481
  if (tags.includes("decision")) return true;
13477
13482
  return false;
13478
13483
  }
13484
+ var WIRE_CODE_TO_KIND = {
13485
+ W: "rule",
13486
+ P: "rule",
13487
+ L: "lesson",
13488
+ D: "decision",
13489
+ M: "memory",
13490
+ T: "memory",
13491
+ VC: "vcs",
13492
+ PR: "preference",
13493
+ SK: "skill",
13494
+ TN: "transcript_snapshot",
13495
+ C: "code",
13496
+ F: "flash",
13497
+ G: "graph",
13498
+ I: "instruction",
13499
+ TS: "tool_suggestion",
13500
+ WA: "warning",
13501
+ SY: "synthesis",
13502
+ PK: "pack",
13503
+ SR: "suggested_rule",
13504
+ KN: "knowledge_node"
13505
+ };
13506
+ var KIND_DEFAULT_PRECEDENCE = {
13507
+ rule: "always",
13508
+ lesson: "critical",
13509
+ decision: "high",
13510
+ preference: "high",
13511
+ skill: "high",
13512
+ memory: "normal",
13513
+ knowledge_node: "normal",
13514
+ code: "normal",
13515
+ flash: "normal",
13516
+ instruction: "normal",
13517
+ tool_suggestion: "normal",
13518
+ warning: "normal",
13519
+ synthesis: "normal",
13520
+ pack: "normal",
13521
+ suggested_rule: "normal",
13522
+ vcs: "normal",
13523
+ transcript_snapshot: "normal",
13524
+ graph: "low",
13525
+ unknown: "low"
13526
+ };
13527
+ function resolveItemKind(item) {
13528
+ if (item.item_type) {
13529
+ const lower = item.item_type.toLowerCase();
13530
+ if (lower in KIND_DEFAULT_PRECEDENCE) return lower;
13531
+ }
13532
+ return WIRE_CODE_TO_KIND[item.typ] || "unknown";
13533
+ }
13534
+ function filterItemsByKind(items, kind) {
13535
+ return items.filter((i) => resolveItemKind(i) === kind).sort((a, b) => b.score - a.score);
13536
+ }
13479
13537
  function pickString(value) {
13480
13538
  if (typeof value !== "string") return null;
13481
13539
  const trimmed = value.trim();
@@ -13545,6 +13603,77 @@ function isMultiProjectFolder(folderPath) {
13545
13603
  return { isMultiProject: false, projectCount: 0, projectNames: [] };
13546
13604
  }
13547
13605
  }
13606
+ var INDEX_KEEPER_INCREMENTAL_INTERVAL_MS = 1e4;
13607
+ var INDEX_KEEPER_AGING_INTERVAL_MS = 3e5;
13608
+ var INDEX_KEEPER_STALE_INTERVAL_MS = 6e4;
13609
+ var INDEX_KEEPER_AGING_THRESHOLD_HOURS = 4;
13610
+ var INDEX_KEEPER_AGING_MAX_FILES = 2e4;
13611
+ var IndexKeeper = class {
13612
+ constructor() {
13613
+ this.lastIncremental = 0;
13614
+ this.lastAging = 0;
13615
+ this.lastStale = 0;
13616
+ this.client = null;
13617
+ }
13618
+ attach(client) {
13619
+ this.client = client;
13620
+ }
13621
+ shouldFire(lastMs, intervalMs) {
13622
+ return Date.now() - lastMs >= intervalMs;
13623
+ }
13624
+ async tick(projectId, folderPath) {
13625
+ if (!this.client || !projectId || !folderPath) return;
13626
+ try {
13627
+ await this.checkIncremental(projectId, folderPath);
13628
+ await this.checkAgingRefresh(projectId, folderPath);
13629
+ } catch {
13630
+ }
13631
+ }
13632
+ async checkIncremental(projectId, folderPath) {
13633
+ if (!this.shouldFire(this.lastIncremental, INDEX_KEEPER_INCREMENTAL_INTERVAL_MS)) return;
13634
+ this.lastIncremental = Date.now();
13635
+ if (!this.client) return;
13636
+ try {
13637
+ await this.client.checkAndIndexChangedFiles();
13638
+ } catch {
13639
+ }
13640
+ }
13641
+ async checkAgingRefresh(projectId, folderPath) {
13642
+ if (!this.shouldFire(this.lastAging, INDEX_KEEPER_AGING_INTERVAL_MS)) return;
13643
+ this.lastAging = Date.now();
13644
+ if (!this.client) return;
13645
+ const ageHours = this.client.localIndexAgeHours(folderPath);
13646
+ if (ageHours === null || ageHours < INDEX_KEEPER_AGING_THRESHOLD_HOURS) return;
13647
+ console.error(`[ContextStream] IndexKeeper: aging refresh for ${folderPath} (${ageHours}h old)`);
13648
+ try {
13649
+ await this.client.ingestLocal({
13650
+ projectId,
13651
+ rootPath: folderPath,
13652
+ maxFiles: INDEX_KEEPER_AGING_MAX_FILES
13653
+ });
13654
+ } catch (e) {
13655
+ console.error(`[ContextStream] IndexKeeper aging refresh failed:`, e);
13656
+ }
13657
+ }
13658
+ maybeTriggerStaleReingest(params) {
13659
+ if (params.freshness !== "stale") return void 0;
13660
+ if (!this.shouldFire(this.lastStale, INDEX_KEEPER_STALE_INTERVAL_MS)) return void 0;
13661
+ this.lastStale = Date.now();
13662
+ if (!this.client) return void 0;
13663
+ const client = this.client;
13664
+ const pid = params.projectId;
13665
+ const fp = params.folderPath;
13666
+ setImmediate(async () => {
13667
+ try {
13668
+ await client.ingestLocal({ projectId: pid, rootPath: fp });
13669
+ console.error(`[ContextStream] IndexKeeper: stale re-ingest completed for ${fp}`);
13670
+ } catch (e) {
13671
+ console.error(`[ContextStream] IndexKeeper stale re-ingest failed:`, e);
13672
+ }
13673
+ });
13674
+ return `Started background re-index for ${fp} because the current index is stale.`;
13675
+ }
13676
+ };
13548
13677
  var ContextStreamClient = class _ContextStreamClient {
13549
13678
  constructor(config) {
13550
13679
  this.config = config;
@@ -14454,6 +14583,16 @@ var ContextStreamClient = class _ContextStreamClient {
14454
14583
  }
14455
14584
  });
14456
14585
  }
14586
+ async ingestFromPath(projectId, pathStr, opts) {
14587
+ return request(this.config, `/projects/${projectId}/files/ingest-from-path`, {
14588
+ body: {
14589
+ path: pathStr,
14590
+ force: opts?.force ?? false,
14591
+ include_media: opts?.include_media ?? false,
14592
+ max_files: opts?.max_files
14593
+ }
14594
+ });
14595
+ }
14457
14596
  /**
14458
14597
  * High-level local ingest: reads files from disk, applies SHA-256 content
14459
14598
  * hash filtering, batches, and sends to the API with cooldown/status handling.
@@ -14511,7 +14650,22 @@ var ContextStreamClient = class _ContextStreamClient {
14511
14650
  }
14512
14651
  }
14513
14652
  } catch (e) {
14514
- console.error(`[ContextStream] Batch ingest error:`, e);
14653
+ console.error(`[ContextStream] Batch ingest error (attempt 1):`, e);
14654
+ try {
14655
+ const retryResult = await this.ingestFiles(projectId, filteredBatch, ingestOptions);
14656
+ const data = retryResult.data;
14657
+ if (data) {
14658
+ filesIndexed += data.files_indexed ?? 0;
14659
+ apiSkipped += data.files_skipped ?? 0;
14660
+ lastStatus = data.status;
14661
+ if (data.status === "cooldown" || data.status === "daily_limit_exceeded") {
14662
+ abortedEarly = true;
14663
+ break;
14664
+ }
14665
+ }
14666
+ } catch (retryErr) {
14667
+ console.error(`[ContextStream] Batch ingest retry failed:`, retryErr);
14668
+ }
14515
14669
  }
14516
14670
  }
14517
14671
  writeHashManifest(projectId, newHashes);
@@ -14564,6 +14718,28 @@ var ContextStreamClient = class _ContextStreamClient {
14564
14718
  this.indexChangedFilesAsync().catch(() => {
14565
14719
  });
14566
14720
  }
14721
+ localIndexAgeHours(folderPath) {
14722
+ try {
14723
+ const resolvedFolder = path5.resolve(folderPath);
14724
+ const fs21 = __require("node:fs");
14725
+ const statusPath = path5.join(homedir4(), ".contextstream", "index-status.json");
14726
+ const raw = fs21.readFileSync(statusPath, "utf-8");
14727
+ const status = JSON.parse(raw);
14728
+ const projects = status.projects ?? {};
14729
+ for (const [projectPath, info] of Object.entries(projects)) {
14730
+ const resolvedProjectPath = path5.resolve(projectPath);
14731
+ if (resolvedFolder === resolvedProjectPath || resolvedFolder.startsWith(`${resolvedProjectPath}${path5.sep}`)) {
14732
+ if (info?.indexed_at) {
14733
+ const ageMs = Date.now() - new Date(info.indexed_at).getTime();
14734
+ return Math.floor(ageMs / 36e5);
14735
+ }
14736
+ }
14737
+ }
14738
+ return null;
14739
+ } catch {
14740
+ return null;
14741
+ }
14742
+ }
14567
14743
  /**
14568
14744
  * Internal async method to find and index changed files.
14569
14745
  * Called by checkAndIndexChangedFiles in fire-and-forget mode.
@@ -16340,6 +16516,61 @@ ${context}`;
16340
16516
  ...this.indexRefreshInProgress ? { index_status: "refreshing" } : {}
16341
16517
  };
16342
16518
  }
16519
+ async getContextFast(params) {
16520
+ const withDefaults = this.withDefaults(params);
16521
+ if (!withDefaults.workspace_id) return null;
16522
+ try {
16523
+ const result = await request(this.config, "/context/hook", {
16524
+ body: withDefaults,
16525
+ timeoutMs: 5e3
16526
+ });
16527
+ const data = result?.data ?? result ?? {};
16528
+ return {
16529
+ context: data.context || "",
16530
+ items: Array.isArray(data.items) ? data.items : void 0,
16531
+ action_required: data.action_required
16532
+ };
16533
+ } catch {
16534
+ return null;
16535
+ }
16536
+ }
16537
+ async getVcsRepos(params) {
16538
+ try {
16539
+ const result = await request(this.config, `/integrations/workspaces/${params.workspace_id}/vcs/repos`, {
16540
+ method: "GET",
16541
+ timeoutMs: 3e3
16542
+ });
16543
+ return Array.isArray(result?.data) ? result.data : Array.isArray(result) ? result : [];
16544
+ } catch {
16545
+ return [];
16546
+ }
16547
+ }
16548
+ async getVcsResource(params) {
16549
+ const qs = params.per_page ? `?per_page=${params.per_page}` : "";
16550
+ try {
16551
+ const result = await request(this.config, `/integrations/workspaces/${params.workspace_id}/vcs/${params.path}${qs}`, {
16552
+ method: "GET",
16553
+ timeoutMs: 4e3
16554
+ });
16555
+ return Array.isArray(result?.data) ? result.data : Array.isArray(result) ? result : [];
16556
+ } catch {
16557
+ return [];
16558
+ }
16559
+ }
16560
+ async vcsApiRequest(params) {
16561
+ const queryParts = [];
16562
+ if (params.query) {
16563
+ for (const [k, v] of Object.entries(params.query)) {
16564
+ if (v !== void 0 && v !== null) queryParts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
16565
+ }
16566
+ }
16567
+ const qs = queryParts.length > 0 ? `?${queryParts.join("&")}` : "";
16568
+ const url = `/integrations/workspaces/${params.workspace_id}/vcs/${params.path}${qs}`;
16569
+ return request(this.config, url, {
16570
+ method: params.method,
16571
+ ...params.body ? { body: params.body } : {}
16572
+ });
16573
+ }
16343
16574
  /**
16344
16575
  * Get high-priority lessons that should be surfaced proactively.
16345
16576
  * Returns critical and high severity lessons for warnings.
@@ -17775,7 +18006,7 @@ ${context}`;
17775
18006
  import * as fs6 from "node:fs";
17776
18007
  import * as path7 from "node:path";
17777
18008
  import { execFile } from "node:child_process";
17778
- import { homedir as homedir5 } from "node:os";
18009
+ import { homedir as homedir6 } from "node:os";
17779
18010
  import { promisify as promisify2 } from "node:util";
17780
18011
  init_files();
17781
18012
  init_rules_templates();
@@ -18360,9 +18591,9 @@ function resolveTodoCompletionUpdate(input) {
18360
18591
  // src/hot-paths.ts
18361
18592
  import * as fs5 from "node:fs";
18362
18593
  import * as path6 from "node:path";
18363
- import { homedir as homedir4 } from "node:os";
18594
+ import { homedir as homedir5 } from "node:os";
18364
18595
  var STORE_VERSION = 1;
18365
- var STORE_DIR = path6.join(homedir4(), ".contextstream");
18596
+ var STORE_DIR = path6.join(homedir5(), ".contextstream");
18366
18597
  var STORE_FILE = path6.join(STORE_DIR, "hot-paths.json");
18367
18598
  var MAX_PATHS_PER_SCOPE = 400;
18368
18599
  var HALF_LIFE_MS = 3 * 24 * 60 * 60 * 1e3;
@@ -18808,9 +19039,9 @@ var RULES_PROJECT_FILES = {
18808
19039
  aider: ".aider.conf.yml"
18809
19040
  };
18810
19041
  var RULES_GLOBAL_FILES = {
18811
- codex: [path7.join(homedir5(), ".codex", "AGENTS.md")],
18812
- kilo: [path7.join(homedir5(), ".kilocode", "rules", "contextstream.md")],
18813
- roo: [path7.join(homedir5(), ".roo", "rules", "contextstream.md")]
19042
+ codex: [path7.join(homedir6(), ".codex", "AGENTS.md")],
19043
+ kilo: [path7.join(homedir6(), ".kilocode", "rules", "contextstream.md")],
19044
+ roo: [path7.join(homedir6(), ".roo", "rules", "contextstream.md")]
18814
19045
  };
18815
19046
  var rulesNoticeCache = /* @__PURE__ */ new Map();
18816
19047
  function compareVersions2(v1, v2) {
@@ -19973,6 +20204,7 @@ function parsePositiveInt(raw, fallback) {
19973
20204
  var OUTPUT_FORMAT = process.env.CONTEXTSTREAM_OUTPUT_FORMAT || "compact";
19974
20205
  var COMPACT_OUTPUT = OUTPUT_FORMAT === "compact";
19975
20206
  var SHOW_TIMING = process.env.CONTEXTSTREAM_SHOW_TIMING === "true" || process.env.CONTEXTSTREAM_SHOW_TIMING === "1";
20207
+ var CONCISE_TOOL_TEXT = process.env.CONTEXTSTREAM_CONCISE_TOOL_TEXT?.toLowerCase() !== "false";
19976
20208
  var INCLUDE_STRUCTURED_CONTENT = parseBoolEnvDefault(
19977
20209
  process.env.CONTEXTSTREAM_INCLUDE_STRUCTURED_CONTENT,
19978
20210
  true
@@ -19989,6 +20221,73 @@ function maybeStripStructuredContent(result) {
19989
20221
  const { structuredContent: _structuredContent, ...rest } = result;
19990
20222
  return rest;
19991
20223
  }
20224
+ function formatTypedLessons(items, compact) {
20225
+ if (items.length === 0) return "";
20226
+ const MAX = 5;
20227
+ const entries = items.slice(0, MAX).map((item) => {
20228
+ const sev = item.score >= 0.8 ? "CRIT" : item.score >= 0.5 ? "HIGH" : "note";
20229
+ return compact ? `[LESSON:${sev}] ${item.value}` : `Lesson (${sev}): ${item.value}`;
20230
+ });
20231
+ return entries.join("\n");
20232
+ }
20233
+ function formatTypedPreferences(items, compact) {
20234
+ if (items.length === 0) return "";
20235
+ const MAX = 5;
20236
+ const entries = items.slice(0, MAX).map(
20237
+ (item) => compact ? `[PREF] ${item.value}` : `Preference: ${item.value}`
20238
+ );
20239
+ return entries.join("\n");
20240
+ }
20241
+ function formatTypedVcs(items, compact) {
20242
+ if (items.length === 0) return "";
20243
+ const MAX = 5;
20244
+ const entries = items.slice(0, MAX).map(
20245
+ (item) => compact ? `[VCS] ${item.value}` : `VCS: ${item.value}`
20246
+ );
20247
+ return entries.join("\n");
20248
+ }
20249
+ function formatTypedSkills(items, compact) {
20250
+ if (items.length === 0) return "";
20251
+ const MAX = 5;
20252
+ const entries = items.slice(0, MAX).map(
20253
+ (item) => compact ? `[SKILL] ${item.value}` : `Skill: ${item.value}`
20254
+ );
20255
+ return entries.join("\n");
20256
+ }
20257
+ function formatTypedSnapshots(items, compact) {
20258
+ if (items.length === 0) return "";
20259
+ const MAX = 3;
20260
+ const entries = items.slice(0, MAX).map(
20261
+ (item) => compact ? `[SNAPSHOT] ${item.value}` : `Transcript snapshot: ${item.value}`
20262
+ );
20263
+ return entries.join("\n");
20264
+ }
20265
+ function hasServerVcsItems(items) {
20266
+ if (!items || items.length === 0) return false;
20267
+ return items.some((i) => resolveItemKind(i) === "vcs");
20268
+ }
20269
+ var WARM_CACHE_TTL_MS = 3e4;
20270
+ var warmCacheEntry = null;
20271
+ function getWarmCacheKey(workspaceId, projectId) {
20272
+ return `${workspaceId || ""}:${projectId || ""}`;
20273
+ }
20274
+ function getWarmCache(workspaceId, projectId) {
20275
+ if (!warmCacheEntry) return null;
20276
+ const key = getWarmCacheKey(workspaceId, projectId);
20277
+ if (warmCacheEntry.key !== key) return null;
20278
+ if (Date.now() - warmCacheEntry.timestamp > WARM_CACHE_TTL_MS) {
20279
+ warmCacheEntry = null;
20280
+ return null;
20281
+ }
20282
+ return warmCacheEntry.result;
20283
+ }
20284
+ function setWarmCache(workspaceId, projectId, result) {
20285
+ warmCacheEntry = {
20286
+ key: getWarmCacheKey(workspaceId, projectId),
20287
+ timestamp: Date.now(),
20288
+ result
20289
+ };
20290
+ }
19992
20291
  var CONSOLIDATED_MODE = process.env.CONTEXTSTREAM_CONSOLIDATED !== "false";
19993
20292
  var CONSOLIDATED_TOOLS = /* @__PURE__ */ new Set([
19994
20293
  "init",
@@ -20351,6 +20650,8 @@ function setupClientDetection(server) {
20351
20650
  }
20352
20651
  function registerTools(server, client, sessionManager, options) {
20353
20652
  const upgradeUrl2 = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
20653
+ const indexKeeper = new IndexKeeper();
20654
+ indexKeeper.attach(client);
20354
20655
  const toolSurfaceProfile = normalizeToolSurfaceProfile(
20355
20656
  options?.toolSurfaceProfile || process.env.CONTEXTSTREAM_TOOL_SURFACE_PROFILE
20356
20657
  );
@@ -20968,6 +21269,13 @@ Next step: ${createCommand}`
20968
21269
  function isNotFoundError(error) {
20969
21270
  return error instanceof HttpError && error.status === 404;
20970
21271
  }
21272
+ function isAccessDeniedError(error) {
21273
+ return error instanceof HttpError && (error.status === 403 || error.status === 401);
21274
+ }
21275
+ function isScopeInvalidResult(result) {
21276
+ const data = result?.data ?? result ?? {};
21277
+ return data.scope_is_valid === false || data.scope_invalid === true || typeof data.error === "string" && data.error.includes("project_access_denied");
21278
+ }
20971
21279
  function isRequiresIngestEndpointError(error) {
20972
21280
  if (!(error instanceof HttpError)) return false;
20973
21281
  if (error.status !== 400) return false;
@@ -20983,6 +21291,100 @@ Next step: ${createCommand}`
20983
21291
  (t) => /[A-Z][a-z]/.test(t) || t.includes("_") || t.includes(".") || t.includes("::")
20984
21292
  );
20985
21293
  }
21294
+ function isArtifactLikePath(filePath) {
21295
+ const normalized = filePath.toLowerCase().replace(/\\/g, "/");
21296
+ return normalized.includes("/.next/") || normalized.includes("/.next.bak/") || normalized.includes("/node_modules/") || normalized.includes("/dist/") || normalized.includes("/build/") || normalized.includes("/target/") || normalized.includes("/coverage/") || normalized.endsWith(".js.map") || normalized.endsWith(".css.map") || normalized.endsWith(".d.ts.map") || normalized.endsWith(".min.js") || normalized.startsWith("archives-ignore/") || normalized.includes("/archives-ignore/") || normalized.startsWith("archives-") && normalized.includes("/tasks/");
21297
+ }
21298
+ function queryExplicitlyTargetsArtifacts(query) {
21299
+ const lower = query.toLowerCase();
21300
+ return lower.includes(".map") || lower.includes("source map") || lower.includes("sourcemap") || lower.includes("node_modules") || lower.includes(".next") || lower.includes("dist/") || lower.includes("build/") || lower.includes("coverage");
21301
+ }
21302
+ function shouldFilterArtifactPaths(mode, query) {
21303
+ if (mode === "pattern" || mode === "exhaustive") return false;
21304
+ if (queryExplicitlyTargetsArtifacts(query)) return false;
21305
+ return true;
21306
+ }
21307
+ const MIRROR_PREFIXES = [
21308
+ "contextstream-ai-brain-export/",
21309
+ "contextstream/",
21310
+ "web/users/"
21311
+ ];
21312
+ function canonicalizeRepoPath(filePath) {
21313
+ let normalized = filePath.replace(/\\/g, "/");
21314
+ while (normalized.startsWith("./")) {
21315
+ normalized = normalized.slice(2);
21316
+ }
21317
+ for (const prefix of MIRROR_PREFIXES) {
21318
+ if (normalized.startsWith(prefix)) {
21319
+ normalized = normalized.slice(prefix.length);
21320
+ break;
21321
+ }
21322
+ }
21323
+ if (normalized.startsWith(".claude/worktrees/")) {
21324
+ const rest = normalized.slice(".claude/worktrees/".length);
21325
+ const slashIdx = rest.indexOf("/");
21326
+ if (slashIdx >= 0) {
21327
+ normalized = rest.slice(slashIdx + 1);
21328
+ }
21329
+ }
21330
+ const projectsIdx = normalized.indexOf("/projects/");
21331
+ if (projectsIdx >= 0) {
21332
+ const afterProjects = normalized.slice(projectsIdx + "/projects/".length);
21333
+ const uuidEndIdx = afterProjects.indexOf("/");
21334
+ if (uuidEndIdx >= 0 && /^[0-9a-f-]{36}/i.test(afterProjects)) {
21335
+ normalized = afterProjects.slice(uuidEndIdx + 1);
21336
+ }
21337
+ }
21338
+ return normalized;
21339
+ }
21340
+ function extractSymbolAnchors(query) {
21341
+ const tokens = query.split(/\s+/).filter(Boolean);
21342
+ const anchors = [];
21343
+ for (const t of tokens) {
21344
+ const cleaned = t.replace(/^["'`]+|["'`]+$/g, "");
21345
+ if (!cleaned) continue;
21346
+ const hasMixedCase = /[a-z]/.test(cleaned) && /[A-Z]/.test(cleaned);
21347
+ const hasUnderscore = cleaned.includes("_");
21348
+ const hasNamespaceSep = cleaned.includes("::") || cleaned.includes(".");
21349
+ const isAllCaps = cleaned.length >= 3 && /^[A-Z0-9_]+$/.test(cleaned);
21350
+ if (hasMixedCase || hasUnderscore || hasNamespaceSep || isAllCaps) {
21351
+ anchors.push(cleaned);
21352
+ }
21353
+ }
21354
+ return anchors;
21355
+ }
21356
+ function applySymbolAnchorRerank(results, anchors) {
21357
+ if (anchors.length === 0 || results.length === 0) return results;
21358
+ const lowerAnchors = anchors.map((a) => a.toLowerCase());
21359
+ const scored = results.map((r, idx) => {
21360
+ const fp = (r.file_path || "").toLowerCase();
21361
+ const content = (r.content || "").toLowerCase();
21362
+ let anchorHits = 0;
21363
+ for (const anchor of lowerAnchors) {
21364
+ if (fp.includes(anchor) || content.includes(anchor)) anchorHits++;
21365
+ }
21366
+ const isNoisy = isArtifactLikePath(r.file_path || "");
21367
+ return { item: r, anchorHits, isNoisy, originalIdx: idx };
21368
+ });
21369
+ scored.sort((a, b) => {
21370
+ if (b.anchorHits !== a.anchorHits) return b.anchorHits - a.anchorHits;
21371
+ if (a.isNoisy !== b.isNoisy) return a.isNoisy ? 1 : -1;
21372
+ return a.originalIdx - b.originalIdx;
21373
+ });
21374
+ return scored.map((s) => s.item);
21375
+ }
21376
+ function deduplicateSearchResults(results) {
21377
+ const seen = /* @__PURE__ */ new Set();
21378
+ return results.filter((r) => {
21379
+ const fp = r.file_path ? canonicalizeRepoPath(r.file_path) : "";
21380
+ const startLine = r.start_line || r.metadata?.start_line || "";
21381
+ const key = fp ? `${fp}:${startLine}` : "";
21382
+ if (!key) return true;
21383
+ if (seen.has(key)) return false;
21384
+ seen.add(key);
21385
+ return true;
21386
+ });
21387
+ }
20986
21388
  async function runLocalRipgrep(query, cwd, limit = 10) {
20987
21389
  try {
20988
21390
  const pattern = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -25521,7 +25923,7 @@ This saves ~80% tokens compared to including full chat history.`,
25521
25923
  project_id: external_exports.string().uuid().optional(),
25522
25924
  max_tokens: external_exports.number().optional().describe("Maximum tokens for context (default: 800)"),
25523
25925
  format: external_exports.enum(["minified", "readable", "structured"]).optional().describe("Context format (default: minified)"),
25524
- mode: external_exports.enum(["standard", "pack"]).optional().describe("Context pack mode (default: pack when enabled)"),
25926
+ mode: external_exports.enum(["standard", "pack", "fast"]).optional().describe("Context mode: standard (default), pack (includes code context), fast (cached quick response)"),
25525
25927
  distill: external_exports.boolean().optional().describe("Use distillation for context pack (default: true)"),
25526
25928
  session_tokens: external_exports.number().optional().describe("Cumulative session token count for context pressure calculation"),
25527
25929
  context_threshold: external_exports.number().optional().describe("Custom context window threshold (defaults to 70k)"),
@@ -25612,21 +26014,149 @@ This saves ~80% tokens compared to including full chat history.`,
25612
26014
  if (!clientName && detectedClientInfo) {
25613
26015
  clientName = detectedClientInfo.name;
25614
26016
  }
25615
- const result = await client.getSmartContext({
25616
- user_message: input.user_message,
25617
- workspace_id: workspaceId,
25618
- project_id: projectId,
25619
- max_tokens: input.max_tokens,
25620
- format: input.format,
25621
- mode: input.mode,
25622
- distill: input.distill,
25623
- session_tokens: sessionTokens,
25624
- context_threshold: contextThreshold,
25625
- save_exchange: input.save_exchange,
25626
- session_id: sessionId,
25627
- client_name: clientName,
25628
- assistant_message: input.assistant_message
25629
- });
26017
+ const conversationTurns = sessionManager?.getConversationTurns() ?? 0;
26018
+ const isFastMode = input.mode === "fast";
26019
+ const folderPathCtx = resolveFolderPath(void 0, sessionManager);
26020
+ if (isFastMode && workspaceId) {
26021
+ const fastResult = await client.getContextFast({ workspace_id: workspaceId, project_id: projectId });
26022
+ if (fastResult && fastResult.context) {
26023
+ const roundTripMs2 = Date.now() - startTime;
26024
+ const timingStr2 = SHOW_TIMING ? ` | ${roundTripMs2}ms` : "";
26025
+ return {
26026
+ content: [{ type: "text", text: `${fastResult.context}
26027
+ ---
26028
+ fast mode${timingStr2}` }]
26029
+ };
26030
+ }
26031
+ }
26032
+ const cachedResult = conversationTurns >= 2 ? getWarmCache(workspaceId, projectId) : null;
26033
+ let proactiveVcsPromise;
26034
+ if (conversationTurns <= 3 && workspaceId && !isFastMode) {
26035
+ proactiveVcsPromise = (async () => {
26036
+ const repos = await client.getVcsRepos({ workspace_id: workspaceId, per_page: 10 });
26037
+ if (repos.length === 0) return { repos: [], pulls: [], issues: [], activity: [], notifications: [] };
26038
+ const pullPromises = repos.slice(0, 5).map(
26039
+ (r) => client.getVcsResource({ workspace_id: workspaceId, path: `repos/${r.id || r.repo_id}/pulls`, per_page: 5 })
26040
+ );
26041
+ const issuePromises = repos.slice(0, 5).map(
26042
+ (r) => client.getVcsResource({ workspace_id: workspaceId, path: `repos/${r.id || r.repo_id}/issues`, per_page: 5 })
26043
+ );
26044
+ const [pullResults, issueResults] = await Promise.all([
26045
+ Promise.all(pullPromises),
26046
+ Promise.all(issuePromises)
26047
+ ]);
26048
+ return {
26049
+ repos,
26050
+ pulls: pullResults.flat(),
26051
+ issues: issueResults.flat(),
26052
+ activity: [],
26053
+ notifications: []
26054
+ };
26055
+ })().catch(() => ({ repos: [], pulls: [], issues: [], activity: [], notifications: [] }));
26056
+ }
26057
+ let recentChangesPromise;
26058
+ if (conversationTurns <= 2 && folderPathCtx && !CONCISE_TOOL_TEXT && !isFastMode) {
26059
+ recentChangesPromise = (async () => {
26060
+ try {
26061
+ const { stdout: stdout2 } = await execFileAsync("git", [
26062
+ "log",
26063
+ "--oneline",
26064
+ "--format=%h %s (%ar)",
26065
+ "-n5"
26066
+ ], { cwd: folderPathCtx, timeout: 3e3, maxBuffer: 64 * 1024 });
26067
+ return stdout2.trim();
26068
+ } catch {
26069
+ return "";
26070
+ }
26071
+ })();
26072
+ }
26073
+ let result;
26074
+ if (cachedResult) {
26075
+ result = cachedResult;
26076
+ const fastOverlay = await client.getContextFast({ workspace_id: workspaceId, project_id: projectId });
26077
+ if (fastOverlay?.context) {
26078
+ result = { ...result, context: result.context + "\n" + fastOverlay.context };
26079
+ if (fastOverlay.items && Array.isArray(fastOverlay.items)) {
26080
+ result.items = [...result.items || [], ...fastOverlay.items];
26081
+ }
26082
+ }
26083
+ } else {
26084
+ result = await client.getSmartContext({
26085
+ user_message: input.user_message,
26086
+ workspace_id: workspaceId,
26087
+ project_id: projectId,
26088
+ max_tokens: input.max_tokens,
26089
+ format: input.format,
26090
+ mode: input.mode === "fast" ? "standard" : input.mode,
26091
+ distill: input.distill,
26092
+ session_tokens: sessionTokens,
26093
+ context_threshold: contextThreshold,
26094
+ save_exchange: input.save_exchange,
26095
+ session_id: sessionId,
26096
+ client_name: clientName,
26097
+ assistant_message: input.assistant_message
26098
+ });
26099
+ if (workspaceId) {
26100
+ setWarmCache(workspaceId, projectId, result);
26101
+ }
26102
+ }
26103
+ const typedItems = Array.isArray(result.items) ? result.items : [];
26104
+ const hasTypedItems = typedItems.length > 0;
26105
+ const compact = COMPACT_OUTPUT || CONCISE_TOOL_TEXT;
26106
+ let typedContextBlock = "";
26107
+ if (hasTypedItems) {
26108
+ const parts = [];
26109
+ const prefItems = filterItemsByKind(typedItems, "preference");
26110
+ const lessonItems = filterItemsByKind(typedItems, "lesson");
26111
+ const vcsItems = filterItemsByKind(typedItems, "vcs");
26112
+ const skillItems = filterItemsByKind(typedItems, "skill");
26113
+ const snapItems = filterItemsByKind(typedItems, "transcript_snapshot");
26114
+ const prefText = formatTypedPreferences(prefItems, compact);
26115
+ if (prefText) parts.push(prefText);
26116
+ const lessonText = formatTypedLessons(lessonItems, compact);
26117
+ if (lessonText) parts.push(lessonText);
26118
+ const vcsText = formatTypedVcs(vcsItems, compact);
26119
+ if (vcsText) parts.push(vcsText);
26120
+ const skillText = formatTypedSkills(skillItems, compact);
26121
+ if (skillText) parts.push(skillText);
26122
+ if (conversationTurns <= 3) {
26123
+ const snapText = formatTypedSnapshots(snapItems, compact);
26124
+ if (snapText) parts.push(snapText);
26125
+ }
26126
+ if (parts.length > 0) {
26127
+ typedContextBlock = "\n" + parts.join("\n") + "\n";
26128
+ }
26129
+ }
26130
+ let proactiveVcsBlock = "";
26131
+ if (proactiveVcsPromise) {
26132
+ try {
26133
+ const vcsCtx = await proactiveVcsPromise;
26134
+ if (!hasServerVcsItems(typedItems) && (vcsCtx.pulls.length > 0 || vcsCtx.issues.length > 0)) {
26135
+ const vParts = [];
26136
+ if (vcsCtx.pulls.length > 0) {
26137
+ vParts.push(compact ? `[VCS] ${vcsCtx.pulls.length} open PR(s)` : `Open PRs: ${vcsCtx.pulls.length}`);
26138
+ }
26139
+ if (vcsCtx.issues.length > 0) {
26140
+ vParts.push(compact ? `[VCS] ${vcsCtx.issues.length} open issue(s)` : `Open issues: ${vcsCtx.issues.length}`);
26141
+ }
26142
+ if (vParts.length > 0) proactiveVcsBlock = "\n" + vParts.join("\n");
26143
+ }
26144
+ } catch {
26145
+ }
26146
+ }
26147
+ let recentChangesBlock = "";
26148
+ if (recentChangesPromise) {
26149
+ try {
26150
+ const changes = await recentChangesPromise;
26151
+ if (changes) {
26152
+ recentChangesBlock = compact ? `
26153
+ [RECENT_CHANGES] ${changes.split("\n").slice(0, 3).join("; ")}` : `
26154
+ [RECENT_CHANGES]
26155
+ ${changes}`;
26156
+ }
26157
+ } catch {
26158
+ }
26159
+ }
25630
26160
  if (sessionManager && result.token_estimate) {
25631
26161
  sessionManager.addTokens(result.token_estimate);
25632
26162
  }
@@ -25792,7 +26322,7 @@ ${versionWarningLine}` : "",
25792
26322
  // Reinforce context() must be called every message
25793
26323
  searchRulesLine
25794
26324
  ].filter(Boolean).join("");
25795
- const finalContext = postCompactContext + result.context;
26325
+ const finalContext = postCompactContext + result.context + typedContextBlock + proactiveVcsBlock + recentChangesBlock;
25796
26326
  const textParts = [
25797
26327
  finalContext,
25798
26328
  footer,
@@ -26807,6 +27337,12 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
26807
27337
  if (authError) return authError;
26808
27338
  client.checkAndIndexChangedFiles().catch(() => {
26809
27339
  });
27340
+ const folderPathForKeeper = resolveFolderPath(void 0, sessionManager);
27341
+ const projectIdForKeeper = resolveProjectId(void 0);
27342
+ if (folderPathForKeeper && projectIdForKeeper) {
27343
+ indexKeeper.tick(projectIdForKeeper, folderPathForKeeper).catch(() => {
27344
+ });
27345
+ }
26810
27346
  const startTime = Date.now();
26811
27347
  const modeInput = input.mode || "auto";
26812
27348
  const modeRecommendation = recommendSearchMode(input.query);
@@ -26833,12 +27369,12 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
26833
27369
  const project = await client.getProject(explicitProjectId);
26834
27370
  const projectWorkspaceId = normalizeUuid(project?.workspace_id || project?.workspaceId);
26835
27371
  if (workspaceId && projectWorkspaceId && projectWorkspaceId !== workspaceId) {
26836
- explicitProjectScopeNote = `Explicit project_id ${explicitProjectId} belongs to workspace ${projectWorkspaceId}; auto-corrected to current workspace ${workspaceId}.`;
27372
+ explicitProjectScopeNote = `Explicit project_id ${explicitProjectId} belongs to a different workspace and was ignored. Do NOT pass this project_id again \u2014 omit it and let the session resolve the correct project scope automatically.`;
26837
27373
  explicitProjectId = void 0;
26838
27374
  }
26839
27375
  } catch (error) {
26840
27376
  if (isNotFoundError(error)) {
26841
- explicitProjectScopeNote = `Explicit project_id ${explicitProjectId} was not found; auto-corrected using folder/index project mapping.`;
27377
+ explicitProjectScopeNote = `Explicit project_id ${explicitProjectId} was not found. Do NOT pass this project_id again \u2014 omit it and let the session resolve the correct project scope automatically.`;
26842
27378
  explicitProjectId = void 0;
26843
27379
  } else {
26844
27380
  throw error;
@@ -27098,6 +27634,10 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
27098
27634
  hot_paths_hint: hotPathsHint,
27099
27635
  output_format: input.output_format || suggestOutputFormat(input.query, requestedMode === "team" ? "hybrid" : requestedMode)
27100
27636
  });
27637
+ let parallelRgPromise;
27638
+ if (containsCodeIdentifiers(input.query) && folderPath && requestedMode !== "pattern") {
27639
+ parallelRgPromise = runLocalRipgrep(input.query, folderPath, input.limit || 10);
27640
+ }
27101
27641
  if (requestedMode === "team") {
27102
27642
  try {
27103
27643
  const teamResult = await runSearchForMode("team", {
@@ -27125,6 +27665,18 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
27125
27665
  try {
27126
27666
  const modeResult = await runSearchForMode(requestedMode, paramsForCandidate);
27127
27667
  const envelope = extractSearchEnvelope(modeResult.result);
27668
+ if (isScopeInvalidResult(modeResult.result)) {
27669
+ if (!selected) {
27670
+ selected = {
27671
+ index,
27672
+ project_id: candidateProjectId,
27673
+ result: modeResult.result,
27674
+ executedMode: modeResult.executedMode,
27675
+ fallbackNote: modeResult.fallbackNote
27676
+ };
27677
+ }
27678
+ continue;
27679
+ }
27128
27680
  if (explicitProjectId && index === 0 && envelope.results.length === 0) {
27129
27681
  explicitScopeHadNoResults = true;
27130
27682
  }
@@ -27148,7 +27700,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
27148
27700
  };
27149
27701
  }
27150
27702
  } catch (error) {
27151
- if (isNotFoundError(error)) {
27703
+ if (isNotFoundError(error) || isAccessDeniedError(error)) {
27152
27704
  continue;
27153
27705
  }
27154
27706
  const errMsg = error instanceof Error ? error.message : String(error);
@@ -27224,7 +27776,59 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
27224
27776
  const docsFallback = requestedMode !== "team" && isDocLookupQuery(input.query) && extractSearchEnvelope(selected.result).results.length === 0 ? await findDocsFallback(workspaceId, candidateProjectIds, input.query, input.limit) : void 0;
27225
27777
  const roundTripMs = Date.now() - startTime;
27226
27778
  let { results, total } = extractSearchEnvelope(selected.result);
27227
- if (results.length === 0 && folderPath) {
27779
+ const scopeInvalid = isScopeInvalidResult(selected.result);
27780
+ for (const r of results) {
27781
+ if (r.file_path) {
27782
+ const canonical = canonicalizeRepoPath(r.file_path);
27783
+ if (canonical !== r.file_path) r.file_path = canonical;
27784
+ }
27785
+ }
27786
+ results = deduplicateSearchResults(results);
27787
+ if (shouldFilterArtifactPaths(selected.executedMode, input.query) && results.length > 0) {
27788
+ const beforeLen = results.length;
27789
+ results = results.filter(
27790
+ (r) => r.file_path ? !isArtifactLikePath(r.file_path) : true
27791
+ );
27792
+ if (results.length < beforeLen) {
27793
+ modeFallbackNote = appendNote(
27794
+ modeFallbackNote,
27795
+ `Filtered ${beforeLen - results.length} artifact/generated path(s).`
27796
+ );
27797
+ }
27798
+ }
27799
+ if (results.length === 0 && !scopeInvalid) {
27800
+ const escalationModes = (() => {
27801
+ switch (selected.executedMode) {
27802
+ case "semantic":
27803
+ return ["hybrid", "keyword"];
27804
+ case "hybrid":
27805
+ return ["keyword"];
27806
+ case "keyword":
27807
+ return ["hybrid"];
27808
+ default:
27809
+ return [];
27810
+ }
27811
+ })();
27812
+ for (const escMode of escalationModes) {
27813
+ try {
27814
+ const escResult = await runSearchForMode(escMode, {
27815
+ ...baseParams,
27816
+ workspace_id: workspaceId,
27817
+ project_id: selected.project_id
27818
+ });
27819
+ const escEnvelope = extractSearchEnvelope(escResult.result);
27820
+ if (escEnvelope.results.length > 0) {
27821
+ results = escEnvelope.results;
27822
+ total = escEnvelope.total;
27823
+ modeFallbackNote = appendNote(modeFallbackNote, `${selected.executedMode} returned 0 results; escalated to ${escMode}.`);
27824
+ break;
27825
+ }
27826
+ } catch {
27827
+ }
27828
+ }
27829
+ }
27830
+ const parallelCoversFallback = !!parallelRgPromise && selected.executedMode !== "pattern";
27831
+ if (results.length === 0 && folderPath && !parallelCoversFallback) {
27228
27832
  const rgResults = await runLocalRipgrep(input.query, folderPath, input.limit || 10);
27229
27833
  if (rgResults.length > 0) {
27230
27834
  results = rgResults.map((r) => ({
@@ -27241,6 +27845,24 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
27241
27845
  );
27242
27846
  }
27243
27847
  }
27848
+ if (parallelRgPromise) {
27849
+ try {
27850
+ const parallelRgResults = await parallelRgPromise;
27851
+ if (parallelRgResults.length > 0) {
27852
+ const existingPaths = new Set(results.map((r) => r.file_path).filter(Boolean));
27853
+ const newEntries = parallelRgResults.filter((r) => !existingPaths.has(r.file_path)).map((r) => ({ file_path: r.file_path, start_line: r.line, content: r.content, score: r.score, source: "local_ripgrep" }));
27854
+ if (newEntries.length > 0) {
27855
+ results = [...results, ...newEntries];
27856
+ }
27857
+ }
27858
+ } catch {
27859
+ }
27860
+ }
27861
+ const symbolAnchors = extractSymbolAnchors(input.query);
27862
+ if (symbolAnchors.length > 0 && results.length > 1) {
27863
+ results = applySymbolAnchorRerank(results, symbolAnchors);
27864
+ }
27865
+ total = results.length;
27244
27866
  const resultPaths = results.map((item) => typeof item?.file_path === "string" ? item.file_path : "").filter(Boolean);
27245
27867
  if (resultPaths.length > 0) {
27246
27868
  globalHotPathStore.recordPaths(
@@ -27255,13 +27877,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
27255
27877
  } else {
27256
27878
  lines.push(`\u{1F50D} ${total} results for "${input.query}"`);
27257
27879
  }
27258
- if (modeAutoSelected) {
27880
+ if (modeAutoSelected && !(CONCISE_TOOL_TEXT && results.length > 0)) {
27259
27881
  lines.push(`Mode auto-selected: \`${requestedMode}\`. ${modeRecommendation.reason}`);
27260
27882
  }
27261
- if (modeFallbackNote) {
27883
+ if (modeFallbackNote && !(CONCISE_TOOL_TEXT && results.length > 0)) {
27262
27884
  lines.push(modeFallbackNote);
27263
27885
  }
27264
- if (hotPathsHint?.entries?.length) {
27886
+ if (hotPathsHint?.entries?.length && !CONCISE_TOOL_TEXT) {
27265
27887
  lines.push(
27266
27888
  `Hot-path hint active (${hotPathsHint.entries.length} paths, confidence ${(hotPathsHint.confidence * 100).toFixed(0)}%).`
27267
27889
  );
@@ -29941,6 +30563,19 @@ ${formatContent(response)}`
29941
30563
  }
29942
30564
  const validPath = await validateReadableDirectory(ingestPath);
29943
30565
  if (!validPath.ok) {
30566
+ try {
30567
+ const pathExists = await fs6.promises.stat(ingestPath).then(() => true).catch(() => false);
30568
+ if (!pathExists) {
30569
+ const apiResult = await client.ingestFromPath(projectId, ingestPath, {
30570
+ force: input.force
30571
+ });
30572
+ return {
30573
+ content: [{ type: "text", text: `Delegated ingest to API for path: ${ingestPath}
30574
+ ${formatContent(apiResult)}` }]
30575
+ };
30576
+ }
30577
+ } catch {
30578
+ }
29944
30579
  return errorResult(validPath.error);
29945
30580
  }
29946
30581
  const ingestOptions = {
@@ -31525,13 +32160,35 @@ Each domain tool has an 'action' parameter for specific operations.` : "";
31525
32160
  "vcs",
31526
32161
  {
31527
32162
  title: "Version Control",
31528
- description: `Git version control operations. Actions: status (working tree status), diff (show changes), log (commit history), blame (line-by-line authorship), branches (list branches), stash_list (list stashes).`,
32163
+ description: `Git version control and remote repo operations.
32164
+
32165
+ Local git actions: status, diff, log, blame, branches, stash_list
32166
+ Remote API actions: list_repos, get_repo, sync_repo, list_pulls, get_pull, get_pull_diff, get_pull_comments, get_pull_commits, get_pull_checks, get_pull_summary, summarize_pull, review_pull, comment_pull, merge_pull, list_issues, get_issue, create_issue, update_issue, comment_issue, list_commits, get_commit, get_commit_diff, compare_refs, list_branches_remote, list_tags, get_tree, get_blob, search_code, search_vcs, get_activity, list_notifications, mark_notification_read, mark_all_notifications_read, list_links, create_link, delete_link, list_automations, create_automation, update_automation, delete_automation, register_webhook, unregister_webhook`,
31529
32167
  inputSchema: external_exports.object({
31530
- action: external_exports.enum(["status", "diff", "log", "blame", "branches", "stash_list"]).describe("VCS action to perform"),
31531
- path: external_exports.string().optional().describe("File path for diff/blame (relative to repo root)"),
31532
- ref: external_exports.string().optional().describe("Git ref (branch, tag, commit) for log/diff"),
31533
- limit: external_exports.number().optional().describe("Max entries for log (default: 20)"),
31534
- staged: external_exports.boolean().optional().describe("Show staged changes only (for diff)")
32168
+ action: external_exports.string().describe("VCS action to perform"),
32169
+ workspace_id: external_exports.string().uuid().optional().describe("Workspace ID for remote VCS actions"),
32170
+ repo_ref: external_exports.string().optional().describe("Repository reference (owner/repo) for remote actions"),
32171
+ repo_id: external_exports.string().optional().describe("Repository ID for remote actions"),
32172
+ path: external_exports.string().optional().describe("File path for diff/blame/tree/blob"),
32173
+ ref: external_exports.string().optional().describe("Git ref (branch, tag, commit)"),
32174
+ base_ref: external_exports.string().optional().describe("Base ref for compare_refs"),
32175
+ limit: external_exports.number().optional().describe("Max entries (default: 20)"),
32176
+ staged: external_exports.boolean().optional().describe("Show staged changes only (for diff)"),
32177
+ pull_number: external_exports.number().optional().describe("Pull request number"),
32178
+ issue_number: external_exports.number().optional().describe("Issue number"),
32179
+ notification_id: external_exports.string().optional().describe("Notification ID"),
32180
+ link_id: external_exports.string().optional().describe("Link ID"),
32181
+ automation_id: external_exports.string().optional().describe("Automation ID"),
32182
+ title: external_exports.string().optional().describe("Title for create_issue"),
32183
+ body: external_exports.string().optional().describe("Body content"),
32184
+ state: external_exports.string().optional().describe("State filter (open/closed)"),
32185
+ labels: external_exports.array(external_exports.string()).optional().describe("Labels for issues"),
32186
+ per_page: external_exports.number().optional().describe("Results per page"),
32187
+ page: external_exports.number().optional().describe("Page number"),
32188
+ event: external_exports.string().optional().describe("Review event (APPROVE/REQUEST_CHANGES/COMMENT)"),
32189
+ provider: external_exports.string().optional().describe("VCS provider (github/gitlab/bitbucket)"),
32190
+ query: external_exports.string().optional().describe("Search query"),
32191
+ data: external_exports.record(external_exports.any()).optional().describe("Additional data for create/update operations")
31535
32192
  })
31536
32193
  },
31537
32194
  async (input) => {
@@ -31551,60 +32208,164 @@ Each domain tool has an 'action' parameter for specific operations.` : "";
31551
32208
  throw err;
31552
32209
  }
31553
32210
  };
31554
- switch (input.action) {
31555
- case "status": {
31556
- const output = await runGit(["status", "--porcelain=v2", "--branch"]);
31557
- return { content: [{ type: "text", text: output || "Working tree clean." }] };
31558
- }
31559
- case "diff": {
31560
- const args = ["diff"];
31561
- if (input.staged) args.push("--staged");
31562
- if (input.ref) args.push(input.ref);
31563
- if (input.path) args.push("--", input.path);
31564
- const output = await runGit(args);
31565
- return { content: [{ type: "text", text: output || "No changes." }] };
31566
- }
31567
- case "log": {
31568
- const limit = input.limit || 20;
31569
- const args = ["log", `--max-count=${limit}`, "--format=%H %ai %an <%ae>%n %s", "--no-decorate"];
31570
- if (input.ref) args.push(input.ref);
31571
- if (input.path) args.push("--", input.path);
31572
- const output = await runGit(args);
31573
- return { content: [{ type: "text", text: output || "No commits." }] };
31574
- }
31575
- case "blame": {
31576
- if (!input.path) return errorResult("blame requires: path");
31577
- const args = ["blame", "--porcelain", input.path];
31578
- if (input.ref) args.splice(1, 0, input.ref);
31579
- const output = await runGit(args);
31580
- const summaryLines = [];
31581
- const blameLines = output.split("\n");
31582
- let currentCommit = "";
31583
- let currentAuthor = "";
31584
- let lineNum = 0;
31585
- for (const line of blameLines) {
31586
- if (/^[0-9a-f]{40}\s/.test(line)) {
31587
- const parts = line.split(" ");
31588
- currentCommit = parts[0].slice(0, 8);
31589
- lineNum = parseInt(parts[2] || "0", 10);
31590
- } else if (line.startsWith("author ")) {
31591
- currentAuthor = line.slice(7);
31592
- } else if (line.startsWith(" ")) {
31593
- summaryLines.push(`${currentCommit} (${currentAuthor}) L${lineNum}: ${line.slice(1)}`);
31594
- }
31595
- }
31596
- return { content: [{ type: "text", text: summaryLines.join("\n") || output }] };
31597
- }
31598
- case "branches": {
31599
- const output = await runGit(["branch", "-a", "--format=%(refname:short) %(objectname:short) %(subject)"]);
31600
- return { content: [{ type: "text", text: output || "No branches." }] };
31601
- }
31602
- case "stash_list": {
31603
- const output = await runGit(["stash", "list"]);
31604
- return { content: [{ type: "text", text: output || "No stashes." }] };
32211
+ const localActions = /* @__PURE__ */ new Set(["status", "diff", "log", "blame", "branches", "stash_list"]);
32212
+ if (localActions.has(input.action)) {
32213
+ switch (input.action) {
32214
+ case "status": {
32215
+ const output = await runGit(["status", "--porcelain=v2", "--branch"]);
32216
+ return { content: [{ type: "text", text: output || "Working tree clean." }] };
32217
+ }
32218
+ case "diff": {
32219
+ const args = ["diff"];
32220
+ if (input.staged) args.push("--staged");
32221
+ if (input.ref) args.push(input.ref);
32222
+ if (input.path) args.push("--", input.path);
32223
+ const output = await runGit(args);
32224
+ return { content: [{ type: "text", text: output || "No changes." }] };
32225
+ }
32226
+ case "log": {
32227
+ const limit = input.limit || 20;
32228
+ const args = ["log", `--max-count=${limit}`, "--format=%H %ai %an <%ae>%n %s", "--no-decorate"];
32229
+ if (input.ref) args.push(input.ref);
32230
+ if (input.path) args.push("--", input.path);
32231
+ const output = await runGit(args);
32232
+ return { content: [{ type: "text", text: output || "No commits." }] };
32233
+ }
32234
+ case "blame": {
32235
+ if (!input.path) return errorResult("blame requires: path");
32236
+ const args = ["blame", "--porcelain", input.path];
32237
+ if (input.ref) args.splice(1, 0, input.ref);
32238
+ const output = await runGit(args);
32239
+ const summaryLines = [];
32240
+ const blameLines = output.split("\n");
32241
+ let currentCommit = "";
32242
+ let currentAuthor = "";
32243
+ let lineNum = 0;
32244
+ for (const line of blameLines) {
32245
+ if (/^[0-9a-f]{40}\s/.test(line)) {
32246
+ const parts = line.split(" ");
32247
+ currentCommit = parts[0].slice(0, 8);
32248
+ lineNum = parseInt(parts[2] || "0", 10);
32249
+ } else if (line.startsWith("author ")) {
32250
+ currentAuthor = line.slice(7);
32251
+ } else if (line.startsWith(" ")) {
32252
+ summaryLines.push(`${currentCommit} (${currentAuthor}) L${lineNum}: ${line.slice(1)}`);
32253
+ }
32254
+ }
32255
+ return { content: [{ type: "text", text: summaryLines.join("\n") || output }] };
32256
+ }
32257
+ case "branches": {
32258
+ const output = await runGit(["branch", "-a", "--format=%(refname:short) %(objectname:short) %(subject)"]);
32259
+ return { content: [{ type: "text", text: output || "No branches." }] };
32260
+ }
32261
+ case "stash_list": {
32262
+ const output = await runGit(["stash", "list"]);
32263
+ return { content: [{ type: "text", text: output || "No stashes." }] };
32264
+ }
32265
+ default:
32266
+ return errorResult(`Unknown local vcs action: ${input.action}`);
31605
32267
  }
32268
+ }
32269
+ let wsId = resolveWorkspaceId(input.workspace_id);
32270
+ if (!wsId) {
32271
+ return errorResult("Remote VCS actions require a workspace_id. Call init() first or pass workspace_id.");
32272
+ }
32273
+ const resolveRepoPath = () => {
32274
+ if (input.repo_id) return `repos/${input.repo_id}`;
32275
+ if (input.repo_ref) return `repos/${encodeURIComponent(input.repo_ref)}`;
32276
+ return "repos";
32277
+ };
32278
+ const repoPath = resolveRepoPath();
32279
+ const q = {
32280
+ state: input.state,
32281
+ per_page: input.per_page,
32282
+ page: input.page
32283
+ };
32284
+ switch (input.action) {
32285
+ case "list_repos":
32286
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: "repos", query: q })) }] };
32287
+ case "get_repo":
32288
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: repoPath })) }] };
32289
+ case "sync_repo":
32290
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "POST", path: `${repoPath}/sync` })) }] };
32291
+ case "list_pulls":
32292
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/pulls`, query: q })) }] };
32293
+ case "get_pull":
32294
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/pulls/${input.pull_number}` })) }] };
32295
+ case "get_pull_diff":
32296
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/pulls/${input.pull_number}/diff` })) }] };
32297
+ case "get_pull_comments":
32298
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/pulls/${input.pull_number}/comments` })) }] };
32299
+ case "get_pull_commits":
32300
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/pulls/${input.pull_number}/commits` })) }] };
32301
+ case "get_pull_checks":
32302
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/pulls/${input.pull_number}/checks` })) }] };
32303
+ case "get_pull_summary":
32304
+ case "summarize_pull":
32305
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/pulls/${input.pull_number}/summary` })) }] };
32306
+ case "review_pull":
32307
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "POST", path: `${repoPath}/pulls/${input.pull_number}/reviews`, body: { event: input.event || "COMMENT", body: input.body } })) }] };
32308
+ case "comment_pull":
32309
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "POST", path: `${repoPath}/pulls/${input.pull_number}/comments`, body: { body: input.body } })) }] };
32310
+ case "merge_pull":
32311
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "PUT", path: `${repoPath}/pulls/${input.pull_number}/merge`, body: input.data })) }] };
32312
+ case "list_issues":
32313
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/issues`, query: q })) }] };
32314
+ case "get_issue":
32315
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/issues/${input.issue_number}` })) }] };
32316
+ case "create_issue":
32317
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "POST", path: `${repoPath}/issues`, body: { title: input.title, body: input.body, labels: input.labels } })) }] };
32318
+ case "update_issue":
32319
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "PATCH", path: `${repoPath}/issues/${input.issue_number}`, body: { title: input.title, body: input.body, state: input.state, labels: input.labels, ...input.data } })) }] };
32320
+ case "comment_issue":
32321
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "POST", path: `${repoPath}/issues/${input.issue_number}/comments`, body: { body: input.body } })) }] };
32322
+ case "list_commits":
32323
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/commits`, query: { ...q, ref: input.ref } })) }] };
32324
+ case "get_commit":
32325
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/commits/${input.ref}` })) }] };
32326
+ case "get_commit_diff":
32327
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/commits/${input.ref}/diff` })) }] };
32328
+ case "compare_refs":
32329
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/compare/${input.base_ref}...${input.ref}` })) }] };
32330
+ case "list_branches_remote":
32331
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/branches`, query: q })) }] };
32332
+ case "list_tags":
32333
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/tags`, query: q })) }] };
32334
+ case "get_tree":
32335
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/tree`, query: { ref: input.ref, path: input.path } })) }] };
32336
+ case "get_blob":
32337
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/blob`, query: { ref: input.ref, path: input.path } })) }] };
32338
+ case "search_code":
32339
+ case "search_vcs":
32340
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/search`, query: { q: input.query, ...q } })) }] };
32341
+ case "get_activity":
32342
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/activity`, query: { limit: input.limit } })) }] };
32343
+ case "list_notifications":
32344
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: "notifications", query: q })) }] };
32345
+ case "mark_notification_read":
32346
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "PUT", path: `notifications/${input.notification_id}/read` })) }] };
32347
+ case "mark_all_notifications_read":
32348
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "PUT", path: "notifications/read-all" })) }] };
32349
+ case "list_links":
32350
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/links` })) }] };
32351
+ case "create_link":
32352
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "POST", path: `${repoPath}/links`, body: input.data })) }] };
32353
+ case "delete_link":
32354
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "DELETE", path: `${repoPath}/links/${input.link_id}` })) }] };
32355
+ case "list_automations":
32356
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "GET", path: `${repoPath}/automations` })) }] };
32357
+ case "create_automation":
32358
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "POST", path: `${repoPath}/automations`, body: input.data })) }] };
32359
+ case "update_automation":
32360
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "PUT", path: `${repoPath}/automations/${input.automation_id}`, body: input.data })) }] };
32361
+ case "delete_automation":
32362
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "DELETE", path: `${repoPath}/automations/${input.automation_id}` })) }] };
32363
+ case "register_webhook":
32364
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "POST", path: `${repoPath}/webhooks`, body: input.data })) }] };
32365
+ case "unregister_webhook":
32366
+ return { content: [{ type: "text", text: formatContent(await client.vcsApiRequest({ workspace_id: wsId, method: "DELETE", path: `${repoPath}/webhooks`, body: input.data })) }] };
31606
32367
  default:
31607
- return errorResult(`Unknown vcs action: ${input.action}`);
32368
+ return errorResult(`Unknown vcs action: ${input.action}. Use 'status', 'diff', 'log', 'blame', 'branches', 'stash_list' for local git, or remote API actions like 'list_repos', 'list_pulls', 'list_issues', etc.`);
31608
32369
  }
31609
32370
  }
31610
32371
  );
@@ -33403,13 +34164,13 @@ async function runHttpGateway() {
33403
34164
  // src/index.ts
33404
34165
  init_version();
33405
34166
  import { existsSync as existsSync16, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
33406
- import { homedir as homedir19 } from "os";
34167
+ import { homedir as homedir20 } from "os";
33407
34168
  import { join as join22 } from "path";
33408
34169
 
33409
34170
  // src/setup.ts
33410
34171
  import * as fs8 from "node:fs/promises";
33411
34172
  import * as path9 from "node:path";
33412
- import { homedir as homedir7 } from "node:os";
34173
+ import { homedir as homedir8 } from "node:os";
33413
34174
  import { stdin, stdout } from "node:process";
33414
34175
  import { createInterface } from "node:readline/promises";
33415
34176
  init_rules_templates();
@@ -33418,12 +34179,12 @@ init_version();
33418
34179
  // src/credentials.ts
33419
34180
  import * as fs7 from "node:fs/promises";
33420
34181
  import * as path8 from "node:path";
33421
- import { homedir as homedir6 } from "node:os";
34182
+ import { homedir as homedir7 } from "node:os";
33422
34183
  function normalizeApiUrl(input) {
33423
34184
  return String(input ?? "").trim().replace(/\/+$/, "");
33424
34185
  }
33425
34186
  function credentialsFilePath() {
33426
- return path8.join(homedir6(), ".contextstream", "credentials.json");
34187
+ return path8.join(homedir7(), ".contextstream", "credentials.json");
33427
34188
  }
33428
34189
  function isRecord(value) {
33429
34190
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -33702,7 +34463,7 @@ async function upsertTextFile(filePath, content, _marker) {
33702
34463
  return replaced.status;
33703
34464
  }
33704
34465
  function globalRulesPathForEditor(editor) {
33705
- const home = homedir7();
34466
+ const home = homedir8();
33706
34467
  switch (editor) {
33707
34468
  case "codex":
33708
34469
  return path9.join(home, ".codex", "AGENTS.md");
@@ -33737,7 +34498,7 @@ async function anyPathExists(paths) {
33737
34498
  return false;
33738
34499
  }
33739
34500
  async function isCodexInstalled() {
33740
- const home = homedir7();
34501
+ const home = homedir8();
33741
34502
  const envHome = process.env.CODEX_HOME;
33742
34503
  const candidates = [
33743
34504
  envHome,
@@ -33748,7 +34509,7 @@ async function isCodexInstalled() {
33748
34509
  return anyPathExists(candidates);
33749
34510
  }
33750
34511
  function openCodeConfigPath() {
33751
- const home = homedir7();
34512
+ const home = homedir8();
33752
34513
  const xdgConfigHome = process.env.XDG_CONFIG_HOME;
33753
34514
  if (process.platform === "win32") {
33754
34515
  const appData = process.env.APPDATA || path9.join(home, "AppData", "Roaming");
@@ -33760,7 +34521,7 @@ function openCodeConfigPath() {
33760
34521
  async function isOpenCodeInstalled() {
33761
34522
  const configPath = openCodeConfigPath();
33762
34523
  const configDir = path9.dirname(configPath);
33763
- const home = homedir7();
34524
+ const home = homedir8();
33764
34525
  const candidates = [
33765
34526
  configDir,
33766
34527
  configPath,
@@ -33770,7 +34531,7 @@ async function isOpenCodeInstalled() {
33770
34531
  return anyPathExists(candidates);
33771
34532
  }
33772
34533
  async function isClaudeInstalled() {
33773
- const home = homedir7();
34534
+ const home = homedir8();
33774
34535
  const candidates = [path9.join(home, ".claude"), path9.join(home, ".config", "claude")];
33775
34536
  const desktopConfig = claudeDesktopConfigPath();
33776
34537
  if (desktopConfig) candidates.push(desktopConfig);
@@ -33783,7 +34544,7 @@ async function isClaudeInstalled() {
33783
34544
  return anyPathExists(candidates);
33784
34545
  }
33785
34546
  async function isClineInstalled() {
33786
- const home = homedir7();
34547
+ const home = homedir8();
33787
34548
  const candidates = [
33788
34549
  path9.join(home, "Documents", "Cline"),
33789
34550
  path9.join(home, ".cline"),
@@ -33792,7 +34553,7 @@ async function isClineInstalled() {
33792
34553
  return anyPathExists(candidates);
33793
34554
  }
33794
34555
  async function isCopilotInstalled() {
33795
- const home = homedir7();
34556
+ const home = homedir8();
33796
34557
  const candidates = [
33797
34558
  path9.join(home, ".copilot"),
33798
34559
  path9.join(home, ".config", "github-copilot"),
@@ -33802,7 +34563,7 @@ async function isCopilotInstalled() {
33802
34563
  return anyPathExists(candidates);
33803
34564
  }
33804
34565
  async function isKiloInstalled() {
33805
- const home = homedir7();
34566
+ const home = homedir8();
33806
34567
  const candidates = [
33807
34568
  kiloConfigDir(),
33808
34569
  path9.join(home, ".kilocode"),
@@ -33811,17 +34572,17 @@ async function isKiloInstalled() {
33811
34572
  return anyPathExists(candidates);
33812
34573
  }
33813
34574
  async function isRooInstalled() {
33814
- const home = homedir7();
34575
+ const home = homedir8();
33815
34576
  const candidates = [path9.join(home, ".roo"), path9.join(home, ".config", "roo")];
33816
34577
  return anyPathExists(candidates);
33817
34578
  }
33818
34579
  async function isAiderInstalled() {
33819
- const home = homedir7();
34580
+ const home = homedir8();
33820
34581
  const candidates = [path9.join(home, ".aider.conf.yml"), path9.join(home, ".config", "aider")];
33821
34582
  return anyPathExists(candidates);
33822
34583
  }
33823
34584
  async function isCursorInstalled() {
33824
- const home = homedir7();
34585
+ const home = homedir8();
33825
34586
  const candidates = [path9.join(home, ".cursor")];
33826
34587
  if (process.platform === "darwin") {
33827
34588
  candidates.push("/Applications/Cursor.app");
@@ -33844,7 +34605,7 @@ async function isCursorInstalled() {
33844
34605
  return anyPathExists(candidates);
33845
34606
  }
33846
34607
  async function isWindsurfInstalled() {
33847
- const home = homedir7();
34608
+ const home = homedir8();
33848
34609
  const candidates = [path9.join(home, ".codeium", "windsurf")];
33849
34610
  if (process.platform === "darwin") {
33850
34611
  candidates.push("/Applications/Windsurf.app");
@@ -33867,7 +34628,7 @@ async function isWindsurfInstalled() {
33867
34628
  return anyPathExists(candidates);
33868
34629
  }
33869
34630
  async function isAntigravityInstalled() {
33870
- const home = homedir7();
34631
+ const home = homedir8();
33871
34632
  const candidates = [path9.join(home, ".gemini")];
33872
34633
  if (process.platform === "darwin") {
33873
34634
  candidates.push("/Applications/Antigravity.app");
@@ -34071,7 +34832,7 @@ function buildContextStreamOpenCodeLocalServer(params) {
34071
34832
  };
34072
34833
  }
34073
34834
  function kiloConfigDir() {
34074
- const home = homedir7();
34835
+ const home = homedir8();
34075
34836
  if (process.platform === "win32") {
34076
34837
  const appData = process.env.APPDATA || path9.join(home, "AppData", "Roaming");
34077
34838
  return path9.join(appData, "kilo");
@@ -34206,7 +34967,7 @@ async function upsertOpenCodeMcpConfig(filePath, server) {
34206
34967
  return before === after ? "skipped" : "updated";
34207
34968
  }
34208
34969
  function claudeDesktopConfigPath() {
34209
- const home = homedir7();
34970
+ const home = homedir8();
34210
34971
  if (process.platform === "darwin") {
34211
34972
  return path9.join(
34212
34973
  home,
@@ -34985,7 +35746,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
34985
35746
  if (mcpScope === "project" && editor !== "codex") continue;
34986
35747
  try {
34987
35748
  if (editor === "codex") {
34988
- const filePath = path9.join(homedir7(), ".codex", "config.toml");
35749
+ const filePath = path9.join(homedir8(), ".codex", "config.toml");
34989
35750
  if (dryRun) {
34990
35751
  writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
34991
35752
  console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
@@ -35001,7 +35762,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
35001
35762
  continue;
35002
35763
  }
35003
35764
  if (editor === "copilot") {
35004
- const filePath = path9.join(homedir7(), ".copilot", "mcp-config.json");
35765
+ const filePath = path9.join(homedir8(), ".copilot", "mcp-config.json");
35005
35766
  if (dryRun) {
35006
35767
  writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
35007
35768
  console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
@@ -35056,7 +35817,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
35056
35817
  continue;
35057
35818
  }
35058
35819
  if (editor === "cursor") {
35059
- const filePath = path9.join(homedir7(), ".cursor", "mcp.json");
35820
+ const filePath = path9.join(homedir8(), ".cursor", "mcp.json");
35060
35821
  if (dryRun) {
35061
35822
  writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
35062
35823
  console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
@@ -35068,7 +35829,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
35068
35829
  continue;
35069
35830
  }
35070
35831
  if (editor === "windsurf") {
35071
- const filePath = path9.join(homedir7(), ".codeium", "windsurf", "mcp_config.json");
35832
+ const filePath = path9.join(homedir8(), ".codeium", "windsurf", "mcp_config.json");
35072
35833
  if (dryRun) {
35073
35834
  writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
35074
35835
  console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
@@ -35338,7 +36099,7 @@ Applying to ${projects.length} project(s)...`);
35338
36099
  }
35339
36100
  if (editor === "windsurf") {
35340
36101
  console.log(
35341
- `- ${EDITOR_LABELS[editor]}: uses global MCP config only (${path9.join(homedir7(), ".codeium", "windsurf", "mcp_config.json")}).`
36102
+ `- ${EDITOR_LABELS[editor]}: uses global MCP config only (${path9.join(homedir8(), ".codeium", "windsurf", "mcp_config.json")}).`
35342
36103
  );
35343
36104
  continue;
35344
36105
  }
@@ -35556,7 +36317,7 @@ Applying to ${projects.length} project(s)...`);
35556
36317
  // src/index.ts
35557
36318
  var ENABLE_PROMPTS2 = (process.env.CONTEXTSTREAM_ENABLE_PROMPTS || "true").toLowerCase() !== "false";
35558
36319
  function showFirstRunMessage() {
35559
- const configDir = join22(homedir19(), ".contextstream");
36320
+ const configDir = join22(homedir20(), ".contextstream");
35560
36321
  const starShownFile = join22(configDir, ".star-shown");
35561
36322
  if (existsSync16(starShownFile)) {
35562
36323
  return;