jinzd-ai-cli 0.4.208 → 0.4.210

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +10 -8
  2. package/README.zh-CN.md +5 -5
  3. package/dist/{batch-3T44NFLG.js → batch-MFPYHOIL.js} +4 -4
  4. package/dist/{chunk-TD7JEHCR.js → chunk-7JWT3KXO.js} +390 -23
  5. package/dist/{chunk-SQB66GP6.js → chunk-BNERFXVO.js} +12 -9
  6. package/dist/{chunk-J22B3OSQ.js → chunk-DJ35G5VO.js} +1 -1
  7. package/dist/{chunk-VWYUB22Y.js → chunk-E2ZWWE6Q.js} +1 -1
  8. package/dist/{chunk-KV4GYNEA.js → chunk-GRJNQJA5.js} +5 -3
  9. package/dist/{chunk-QDY72ABW.js → chunk-HZQVX7VF.js} +5 -3
  10. package/dist/{chunk-O6UFCEUZ.js → chunk-K3CF65QH.js} +12 -9
  11. package/dist/{chunk-2OVMMSGQ.js → chunk-NEPFADHX.js} +10 -9
  12. package/dist/{chunk-P7JY3SWA.js → chunk-NWP7C6CC.js} +1 -1
  13. package/dist/{chunk-TM4MS63K.js → chunk-P6MTFCXB.js} +14 -6
  14. package/dist/{ci-6QWBVRJX.js → ci-QLJUDLMY.js} +3 -2
  15. package/dist/{constants-SZTQNN7K.js → constants-47OR72MV.js} +3 -1
  16. package/dist/{doctor-cli-I2Y2YR4V.js → doctor-cli-32COMA5K.js} +4 -4
  17. package/dist/electron-server.js +652 -457
  18. package/dist/{hub-XVPFBODB.js → hub-3NWJUCLK.js} +1 -1
  19. package/dist/index.js +128 -376
  20. package/dist/{indexer-AKWMYNJI.js → indexer-2AG4G6B5.js} +1 -1
  21. package/dist/{indexer-BMYUUDLH.js → indexer-4WWS3VIL.js} +1 -1
  22. package/dist/{run-tests-HZ2CRCTZ.js → run-tests-NT2UIUVB.js} +1 -1
  23. package/dist/{run-tests-D5SNUBYX.js → run-tests-UT7RZ7Y3.js} +2 -2
  24. package/dist/{server-PVKLPRUP.js → server-AIHVMLNU.js} +5 -5
  25. package/dist/{server-4EFTRFTN.js → server-E5D54DIZ.js} +87 -306
  26. package/dist/{task-orchestrator-2A5VCFLS.js → task-orchestrator-BFDSTSOH.js} +5 -5
  27. package/dist/{usage-Q24E5636.js → usage-P4B2RZKL.js} +2 -2
  28. package/package.json +2 -1
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-SKET65WZ.js";
4
4
  import {
5
5
  indexProject
6
- } from "./chunk-SQB66GP6.js";
6
+ } from "./chunk-BNERFXVO.js";
7
7
  import {
8
8
  AGENTIC_BEHAVIOR_GUIDELINE,
9
9
  APP_NAME,
@@ -12,6 +12,7 @@ import {
12
12
  CONFIG_DIR_NAME,
13
13
  CONFIG_FILE_NAME,
14
14
  CONTEXT_FILE_CANDIDATES,
15
+ CONTEXT_FILE_MAX_BYTES,
15
16
  CUSTOM_COMMANDS_DIR_NAME,
16
17
  DEFAULT_MAX_TOKENS,
17
18
  DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
@@ -36,7 +37,7 @@ import {
36
37
  VERSION,
37
38
  buildUserIdentityPrompt,
38
39
  runTestsTool
39
- } from "./chunk-QDY72ABW.js";
40
+ } from "./chunk-HZQVX7VF.js";
40
41
  import {
41
42
  hasSemanticIndex,
42
43
  semanticSearch
@@ -59,12 +60,12 @@ import {
59
60
  import express from "express";
60
61
  import { createServer } from "http";
61
62
  import { WebSocketServer } from "ws";
62
- import { join as join17, dirname as dirname6, resolve as resolve6, relative as relative3, sep as sep3 } from "path";
63
- import { existsSync as existsSync23, readFileSync as readFileSync16, readdirSync as readdirSync11, statSync as statSync9, realpathSync } from "fs";
63
+ import { join as join19, dirname as dirname6, resolve as resolve7, relative as relative4, sep as sep3 } from "path";
64
+ import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync12, statSync as statSync10, realpathSync } from "fs";
64
65
  import { networkInterfaces } from "os";
65
66
 
66
67
  // src/config/config-manager.ts
67
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
68
+ import { readFileSync, existsSync, mkdirSync } from "fs";
68
69
  import { join } from "path";
69
70
  import { homedir } from "os";
70
71
 
@@ -172,10 +173,15 @@ var ConfigSchema = z.object({
172
173
  systemPrompt: z.string().optional()
173
174
  }).default({}),
174
175
  // 项目上下文文件配置
175
- // 启动时自动读取并注入 system prompt,类似 Claude Code 的 CLAUDE.md 机制
176
- // 默认按顺序查找:AICLI.md → CLAUDE.md → .aicli/context.md
177
- // 设为 false 可禁用此功能
176
+ // 启动时自动读取并注入 system prompt,类似 Claude Code 的 CLAUDE.md / Codex AGENTS.md 机制
177
+ // 默认按顺序查找:AICLI.override.md → AGENTS.override.md → AICLI.md → CLAUDE.md → AGENTS.md
178
+ // 设为 false 可禁用此功能;指定字符串时仅加载 cwd 下的该文件
178
179
  contextFile: z.union([z.string(), z.literal(false)]).default("auto"),
180
+ context: z.object({
181
+ projectDocFallbackFilenames: z.array(z.string()).default([]),
182
+ projectDocMaxBytes: z.number().int().positive().default(32 * 1024),
183
+ showLoadedContextSources: z.boolean().default(false)
184
+ }).default({}),
179
185
  // Google Custom Search API 的 Search Engine ID (cx 参数)
180
186
  // API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
181
187
  // CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
@@ -503,7 +509,7 @@ ${err}`
503
509
  }
504
510
  save() {
505
511
  mkdirSync(this.configDir, { recursive: true });
506
- writeFileSync(this.configPath, JSON.stringify(this.config, null, 2), "utf-8");
512
+ atomicWriteFileSync(this.configPath, JSON.stringify(this.config, null, 2));
507
513
  }
508
514
  getApiKey(providerId) {
509
515
  const envKey = EnvLoader.getApiKey(providerId);
@@ -4736,7 +4742,7 @@ import { platform as platform2 } from "os";
4736
4742
  import { resolve } from "path";
4737
4743
 
4738
4744
  // src/tools/undo-stack.ts
4739
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, rmdirSync, existsSync as existsSync3 } from "fs";
4745
+ import { readFileSync as readFileSync3, writeFileSync, unlinkSync as unlinkSync2, rmdirSync, existsSync as existsSync3 } from "fs";
4740
4746
  var MAX_UNDO_DEPTH = 20;
4741
4747
  var UndoStack = class {
4742
4748
  stack = [];
@@ -4819,7 +4825,7 @@ var UndoStack = class {
4819
4825
  return { entry, result: `Deleted newly created file: ${entry.filePath}` };
4820
4826
  }
4821
4827
  } else {
4822
- writeFileSync2(entry.filePath, entry.previousContent, "utf-8");
4828
+ writeFileSync(entry.filePath, entry.previousContent, "utf-8");
4823
4829
  const lines = entry.previousContent.split("\n").length;
4824
4830
  return {
4825
4831
  entry,
@@ -6489,7 +6495,7 @@ var ToolExecutor = class {
6489
6495
  rl.resume();
6490
6496
  process.stdout.write(prompt);
6491
6497
  this.confirming = true;
6492
- return new Promise((resolve7) => {
6498
+ return new Promise((resolve8) => {
6493
6499
  let completed = false;
6494
6500
  const cleanup = (result) => {
6495
6501
  if (completed) return;
@@ -6499,7 +6505,7 @@ var ToolExecutor = class {
6499
6505
  rl.pause();
6500
6506
  rlAny.output = savedOutput;
6501
6507
  this.confirming = false;
6502
- resolve7(result);
6508
+ resolve8(result);
6503
6509
  };
6504
6510
  const onLine = (line) => {
6505
6511
  const trimmed = line.trim();
@@ -6673,7 +6679,7 @@ var ToolExecutor = class {
6673
6679
  rl.resume();
6674
6680
  process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
6675
6681
  this.confirming = true;
6676
- return new Promise((resolve7) => {
6682
+ return new Promise((resolve8) => {
6677
6683
  let completed = false;
6678
6684
  const cleanup = (answer) => {
6679
6685
  if (completed) return;
@@ -6683,7 +6689,7 @@ var ToolExecutor = class {
6683
6689
  rl.pause();
6684
6690
  rlAny.output = savedOutput;
6685
6691
  this.confirming = false;
6686
- resolve7(answer === "y");
6692
+ resolve8(answer === "y");
6687
6693
  };
6688
6694
  const onLine = (line) => {
6689
6695
  const trimmed = line.trim();
@@ -6864,7 +6870,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
6864
6870
  const mode = appendMode ? "appended" : "written";
6865
6871
  void (async () => {
6866
6872
  try {
6867
- const { updateFile } = await import("./indexer-BMYUUDLH.js");
6873
+ const { updateFile } = await import("./indexer-4WWS3VIL.js");
6868
6874
  await updateFile(process.cwd(), filePath);
6869
6875
  } catch {
6870
6876
  }
@@ -7666,7 +7672,7 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
7666
7672
  walk(rootPath);
7667
7673
  return paths;
7668
7674
  }
7669
- async function searchInFileAsync(fullPath, displayPath, regex, contextLines, maxResults) {
7675
+ async function searchInFileAsync(fullPath, displayPath2, regex, contextLines, maxResults) {
7670
7676
  let content;
7671
7677
  try {
7672
7678
  content = await readFile(fullPath, "utf-8");
@@ -7680,7 +7686,7 @@ async function searchInFileAsync(fullPath, displayPath, regex, contextLines, max
7680
7686
  regex.lastIndex = 0;
7681
7687
  if (regex.test(lines[i])) {
7682
7688
  const match = {
7683
- file: displayPath.replace(/\\/g, "/"),
7689
+ file: displayPath2.replace(/\\/g, "/"),
7684
7690
  lineNumber: i + 1,
7685
7691
  lineText: lines[i]
7686
7692
  };
@@ -7699,7 +7705,7 @@ async function searchInFileAsync(fullPath, displayPath, regex, contextLines, max
7699
7705
  }
7700
7706
  return results;
7701
7707
  }
7702
- function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, results) {
7708
+ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, results) {
7703
7709
  try {
7704
7710
  const stat = statSync4(fullPath);
7705
7711
  if (stat.size > 1e6) return;
@@ -7719,7 +7725,7 @@ function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, re
7719
7725
  regex.lastIndex = 0;
7720
7726
  if (regex.test(lines[i])) {
7721
7727
  const result = {
7722
- file: displayPath,
7728
+ file: displayPath2,
7723
7729
  lineNumber: i + 1,
7724
7730
  lineText: lines[i].trimEnd()
7725
7731
  };
@@ -7932,7 +7938,7 @@ var runInteractiveTool = {
7932
7938
  PYTHONDONTWRITEBYTECODE: "1"
7933
7939
  };
7934
7940
  const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
7935
- return new Promise((resolve7) => {
7941
+ return new Promise((resolve8) => {
7936
7942
  const child = spawn2(executable, cmdArgs.map(String), {
7937
7943
  cwd: process.cwd(),
7938
7944
  env,
@@ -7965,22 +7971,22 @@ var runInteractiveTool = {
7965
7971
  setTimeout(writeNextLine, 400);
7966
7972
  const timer = setTimeout(() => {
7967
7973
  child.kill();
7968
- resolve7(`${prefixWarnings}[Timeout after ${timeout}ms]
7974
+ resolve8(`${prefixWarnings}[Timeout after ${timeout}ms]
7969
7975
  ${buildOutput(stdout, stderr)}`);
7970
7976
  }, timeout);
7971
7977
  child.on("close", (code) => {
7972
7978
  clearTimeout(timer);
7973
7979
  const output = buildOutput(stdout, stderr);
7974
7980
  if (code !== 0 && code !== null) {
7975
- resolve7(`${prefixWarnings}Exit code ${code}:
7981
+ resolve8(`${prefixWarnings}Exit code ${code}:
7976
7982
  ${output}`);
7977
7983
  } else {
7978
- resolve7(`${prefixWarnings}${output || "(no output)"}`);
7984
+ resolve8(`${prefixWarnings}${output || "(no output)"}`);
7979
7985
  }
7980
7986
  });
7981
7987
  child.on("error", (err) => {
7982
7988
  clearTimeout(timer);
7983
- resolve7(
7989
+ resolve8(
7984
7990
  `${prefixWarnings}Failed to start process "${executable}": ${err.message}
7985
7991
  Hint: On Windows, use the full path to the executable, e.g.:
7986
7992
  C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
@@ -8648,7 +8654,7 @@ Any of these triggers means use save_last_response, NOT write_file:
8648
8654
  };
8649
8655
 
8650
8656
  // src/tools/builtin/save-memory.ts
8651
- import { existsSync as existsSync12, statSync as statSync6, appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "fs";
8657
+ import { existsSync as existsSync12, readFileSync as readFileSync9, statSync as statSync6, mkdirSync as mkdirSync6 } from "fs";
8652
8658
  import { join as join7 } from "path";
8653
8659
  import { homedir as homedir5 } from "os";
8654
8660
  function getMemoryFilePath() {
@@ -8685,7 +8691,8 @@ var saveMemoryTool = {
8685
8691
  ## ${timestamp}
8686
8692
  ${content}
8687
8693
  `;
8688
- appendFileSync2(memoryPath, entry, "utf-8");
8694
+ const previous = existsSync12(memoryPath) ? readFileSync9(memoryPath, "utf-8") : "";
8695
+ atomicWriteFileSync(memoryPath, previous + entry);
8689
8696
  const byteSize = statSync6(memoryPath).size;
8690
8697
  return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
8691
8698
  }
@@ -8731,7 +8738,7 @@ function promptUser(rl, question) {
8731
8738
  console.log();
8732
8739
  console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
8733
8740
  process.stdout.write(chalk4.cyan("> "));
8734
- return new Promise((resolve7) => {
8741
+ return new Promise((resolve8) => {
8735
8742
  let completed = false;
8736
8743
  const cleanup = (answer) => {
8737
8744
  if (completed) return;
@@ -8741,7 +8748,7 @@ function promptUser(rl, question) {
8741
8748
  rl.pause();
8742
8749
  rlAny.output = savedOutput;
8743
8750
  askUserContext.prompting = false;
8744
- resolve7(answer);
8751
+ resolve8(answer);
8745
8752
  };
8746
8753
  const onLine = (line) => {
8747
8754
  cleanup(line);
@@ -9750,7 +9757,7 @@ ${commitOutput.trim()}`;
9750
9757
  };
9751
9758
 
9752
9759
  // src/tools/builtin/notebook-edit.ts
9753
- import { readFileSync as readFileSync9, existsSync as existsSync14 } from "fs";
9760
+ import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
9754
9761
  import { writeFile } from "fs/promises";
9755
9762
  import { resolve as resolve4, extname as extname2 } from "path";
9756
9763
  var notebookEditTool = {
@@ -9808,7 +9815,7 @@ var notebookEditTool = {
9808
9815
  if (!existsSync14(absPath)) {
9809
9816
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
9810
9817
  }
9811
- const raw = readFileSync9(absPath, "utf-8");
9818
+ const raw = readFileSync10(absPath, "utf-8");
9812
9819
  const nb = parseNotebook(raw);
9813
9820
  const cellIdx0 = cellIndexRaw - 1;
9814
9821
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -10580,7 +10587,7 @@ var McpClient = class {
10580
10587
  // 内部方法:JSON-RPC 通信
10581
10588
  // ══════════════════════════════════════════════════════════════════
10582
10589
  sendRequest(method, params) {
10583
- return new Promise((resolve7, reject) => {
10590
+ return new Promise((resolve8, reject) => {
10584
10591
  if (!this.process?.stdin?.writable) {
10585
10592
  return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
10586
10593
  }
@@ -10604,7 +10611,7 @@ var McpClient = class {
10604
10611
  this.pendingRequests.set(id, {
10605
10612
  resolve: (result) => {
10606
10613
  cleanup();
10607
- resolve7(result);
10614
+ resolve8(result);
10608
10615
  },
10609
10616
  reject: (error) => {
10610
10617
  cleanup();
@@ -10681,13 +10688,13 @@ var McpClient = class {
10681
10688
  }
10682
10689
  /** Promise 超时包装 */
10683
10690
  withTimeout(promise, ms, label) {
10684
- return new Promise((resolve7, reject) => {
10691
+ return new Promise((resolve8, reject) => {
10685
10692
  const timer = setTimeout(() => {
10686
10693
  reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
10687
10694
  }, ms);
10688
10695
  promise.then((val) => {
10689
10696
  clearTimeout(timer);
10690
- resolve7(val);
10697
+ resolve8(val);
10691
10698
  }).catch((err) => {
10692
10699
  clearTimeout(timer);
10693
10700
  reject(err);
@@ -10968,7 +10975,7 @@ import { existsSync as existsSync16, readdirSync as readdirSync8, mkdirSync as m
10968
10975
  import { join as join10 } from "path";
10969
10976
 
10970
10977
  // src/skills/types.ts
10971
- import { readFileSync as readFileSync10 } from "fs";
10978
+ import { readFileSync as readFileSync11 } from "fs";
10972
10979
  import { basename as basename5 } from "path";
10973
10980
  function parseSimpleYaml(yaml) {
10974
10981
  const result = {};
@@ -10990,7 +10997,7 @@ function parseYamlArray(value) {
10990
10997
  function parseSkillFile(filePath) {
10991
10998
  let raw;
10992
10999
  try {
10993
- raw = readFileSync10(filePath, "utf-8");
11000
+ raw = readFileSync11(filePath, "utf-8");
10994
11001
  } catch {
10995
11002
  return null;
10996
11003
  }
@@ -11119,7 +11126,7 @@ var SkillManager = class {
11119
11126
 
11120
11127
  // src/web/tool-executor-web.ts
11121
11128
  import { randomUUID as randomUUID2 } from "crypto";
11122
- import { existsSync as existsSync17, readFileSync as readFileSync11 } from "fs";
11129
+ import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
11123
11130
  var ToolExecutorWeb = class _ToolExecutorWeb {
11124
11131
  constructor(registry, ws) {
11125
11132
  this.registry = registry;
@@ -11175,33 +11182,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11175
11182
  }
11176
11183
  /** Resolve a pending confirm from client response */
11177
11184
  resolveConfirm(requestId, approved) {
11178
- const resolve7 = this.pendingConfirms.get(requestId);
11179
- if (resolve7) {
11185
+ const resolve8 = this.pendingConfirms.get(requestId);
11186
+ if (resolve8) {
11180
11187
  this.clearPendingTimer(requestId);
11181
11188
  this.pendingConfirms.delete(requestId);
11182
11189
  this.confirming = false;
11183
- resolve7(approved);
11190
+ resolve8(approved);
11184
11191
  }
11185
11192
  }
11186
11193
  /** Resolve a pending batch confirm from client response */
11187
11194
  resolveBatchConfirm(requestId, decision) {
11188
- const resolve7 = this.pendingBatchConfirms.get(requestId);
11189
- if (resolve7) {
11195
+ const resolve8 = this.pendingBatchConfirms.get(requestId);
11196
+ if (resolve8) {
11190
11197
  this.clearPendingTimer(requestId);
11191
11198
  this.pendingBatchConfirms.delete(requestId);
11192
11199
  this.confirming = false;
11193
11200
  if (decision === "all" || decision === "none") {
11194
- resolve7(decision);
11201
+ resolve8(decision);
11195
11202
  } else {
11196
- resolve7(new Set(decision));
11203
+ resolve8(new Set(decision));
11197
11204
  }
11198
11205
  }
11199
11206
  }
11200
11207
  /** Cancel all pending confirms (e.g., on disconnect) */
11201
11208
  cancelAll() {
11202
- for (const resolve7 of this.pendingConfirms.values()) resolve7(false);
11209
+ for (const resolve8 of this.pendingConfirms.values()) resolve8(false);
11203
11210
  this.pendingConfirms.clear();
11204
- for (const resolve7 of this.pendingBatchConfirms.values()) resolve7("none");
11211
+ for (const resolve8 of this.pendingBatchConfirms.values()) resolve8("none");
11205
11212
  this.pendingBatchConfirms.clear();
11206
11213
  this.confirming = false;
11207
11214
  }
@@ -11246,7 +11253,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11246
11253
  const newContent = String(call.arguments["content"] ?? "");
11247
11254
  if (filePath && existsSync17(filePath)) {
11248
11255
  try {
11249
- const old = readFileSync11(filePath, "utf-8");
11256
+ const old = readFileSync12(filePath, "utf-8");
11250
11257
  return renderDiff(old, newContent, { filePath });
11251
11258
  } catch {
11252
11259
  }
@@ -11277,8 +11284,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11277
11284
  diff: this.getDiffPreview(call)
11278
11285
  };
11279
11286
  this.send(msg);
11280
- return new Promise((resolve7) => {
11281
- this.pendingConfirms.set(requestId, resolve7);
11287
+ return new Promise((resolve8) => {
11288
+ this.pendingConfirms.set(requestId, resolve8);
11282
11289
  this.pendingTimers.set(requestId, setTimeout(() => {
11283
11290
  if (this.pendingConfirms.has(requestId)) {
11284
11291
  this.resolveConfirm(requestId, false);
@@ -11302,8 +11309,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11302
11309
  files
11303
11310
  };
11304
11311
  this.send(msg);
11305
- return new Promise((resolve7) => {
11306
- this.pendingBatchConfirms.set(requestId, resolve7);
11312
+ return new Promise((resolve8) => {
11313
+ this.pendingBatchConfirms.set(requestId, resolve8);
11307
11314
  this.pendingTimers.set(requestId, setTimeout(() => {
11308
11315
  if (this.pendingBatchConfirms.has(requestId)) {
11309
11316
  this.resolveBatchConfirm(requestId, "none");
@@ -11447,11 +11454,11 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11447
11454
  };
11448
11455
 
11449
11456
  // src/core/system-prompt-builder.ts
11450
- import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
11457
+ import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
11451
11458
  import { join as join12 } from "path";
11452
11459
 
11453
11460
  // src/repl/dev-state.ts
11454
- import { existsSync as existsSync18, readFileSync as readFileSync12, writeFileSync as writeFileSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync9 } from "fs";
11461
+ import { existsSync as existsSync18, readFileSync as readFileSync13, unlinkSync as unlinkSync4, mkdirSync as mkdirSync9 } from "fs";
11455
11462
  import { join as join11 } from "path";
11456
11463
  import { homedir as homedir6 } from "os";
11457
11464
  function getDevStatePath() {
@@ -11460,7 +11467,7 @@ function getDevStatePath() {
11460
11467
  function loadDevState() {
11461
11468
  const path3 = getDevStatePath();
11462
11469
  if (!existsSync18(path3)) return null;
11463
- const content = readFileSync12(path3, "utf-8").trim();
11470
+ const content = readFileSync13(path3, "utf-8").trim();
11464
11471
  return content || null;
11465
11472
  }
11466
11473
 
@@ -11520,7 +11527,7 @@ ${ctx.activeSkill.content}`);
11520
11527
  function loadMemoryContent(configDir) {
11521
11528
  const memoryPath = join12(configDir, MEMORY_FILE_NAME);
11522
11529
  if (!existsSync19(memoryPath)) return null;
11523
- let content = readFileSync13(memoryPath, "utf-8").trim();
11530
+ let content = readFileSync14(memoryPath, "utf-8").trim();
11524
11531
  if (!content) return null;
11525
11532
  if (content.length > MEMORY_MAX_CHARS) {
11526
11533
  content = content.slice(-MEMORY_MAX_CHARS);
@@ -11728,15 +11735,171 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
11728
11735
  return committedSize < originalSize;
11729
11736
  }
11730
11737
 
11738
+ // src/core/context-files.ts
11739
+ import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
11740
+ import { join as join13, relative as relative3, resolve as resolve5 } from "path";
11741
+ function uniqueNonEmpty(values) {
11742
+ const seen = /* @__PURE__ */ new Set();
11743
+ const out = [];
11744
+ for (const value of values) {
11745
+ const trimmed = value.trim();
11746
+ if (!trimmed || seen.has(trimmed)) continue;
11747
+ seen.add(trimmed);
11748
+ out.push(trimmed);
11749
+ }
11750
+ return out;
11751
+ }
11752
+ function buildContextFileCandidates(fallbackFilenames = []) {
11753
+ return uniqueNonEmpty([...CONTEXT_FILE_CANDIDATES, ...fallbackFilenames]);
11754
+ }
11755
+ function displayPath(filePath, cwd) {
11756
+ const rel = relative3(cwd, filePath);
11757
+ if (rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\")) {
11758
+ return rel;
11759
+ }
11760
+ return filePath;
11761
+ }
11762
+ function isInsideOrEqual(parent, child) {
11763
+ const rel = relative3(resolve5(parent), resolve5(child));
11764
+ return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
11765
+ }
11766
+ function readContextFile(level, filePath, cwd, maxBytes) {
11767
+ const name = filePath.split(/[\\/]/).pop() ?? filePath;
11768
+ const shown = displayPath(filePath, cwd);
11769
+ try {
11770
+ const raw = readFileSync15(filePath);
11771
+ const byteCount = raw.byteLength;
11772
+ if (byteCount === 0) {
11773
+ return {
11774
+ layer: null,
11775
+ skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
11776
+ };
11777
+ }
11778
+ const truncated = byteCount > maxBytes;
11779
+ const bytes = truncated ? raw.subarray(0, maxBytes) : raw;
11780
+ const content = bytes.toString("utf-8").trim();
11781
+ if (!content) {
11782
+ return {
11783
+ layer: null,
11784
+ skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
11785
+ };
11786
+ }
11787
+ return {
11788
+ layer: {
11789
+ level: level === "single" ? "project" : level,
11790
+ filePath,
11791
+ displayPath: shown,
11792
+ fileName: name,
11793
+ content,
11794
+ charCount: content.length,
11795
+ byteCount,
11796
+ truncated
11797
+ },
11798
+ skipped: truncated ? { level, filePath, displayPath: shown, fileName: name, reason: "too-large", message: `truncated to ${maxBytes} bytes` } : null
11799
+ };
11800
+ } catch (err) {
11801
+ return {
11802
+ layer: null,
11803
+ skipped: {
11804
+ level,
11805
+ filePath,
11806
+ displayPath: shown,
11807
+ fileName: name,
11808
+ reason: "read-error",
11809
+ message: err instanceof Error ? err.message : String(err)
11810
+ }
11811
+ };
11812
+ }
11813
+ }
11814
+ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
11815
+ const skipped = [];
11816
+ for (const candidate of candidates) {
11817
+ const filePath = join13(dir, candidate);
11818
+ if (!existsSync20(filePath)) continue;
11819
+ const result = readContextFile(level, filePath, cwd, maxBytes);
11820
+ if (result.skipped) skipped.push(result.skipped);
11821
+ if (result.layer) return { layer: result.layer, skipped };
11822
+ }
11823
+ return { layer: null, skipped };
11824
+ }
11825
+ function loadContextFiles(options) {
11826
+ const cwd = resolve5(options.cwd);
11827
+ const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
11828
+ const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
11829
+ const skipped = [];
11830
+ if (options.setting === false) {
11831
+ return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
11832
+ }
11833
+ if (options.setting && options.setting !== "auto") {
11834
+ const filePath = resolve5(cwd, String(options.setting));
11835
+ if (!isInsideOrEqual(cwd, filePath)) {
11836
+ skipped.push({
11837
+ level: "single",
11838
+ filePath,
11839
+ displayPath: filePath,
11840
+ fileName: String(options.setting),
11841
+ reason: "outside-cwd"
11842
+ });
11843
+ return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
11844
+ }
11845
+ if (!existsSync20(filePath)) {
11846
+ skipped.push({
11847
+ level: "single",
11848
+ filePath,
11849
+ displayPath: displayPath(filePath, cwd),
11850
+ fileName: String(options.setting),
11851
+ reason: "missing"
11852
+ });
11853
+ return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
11854
+ }
11855
+ const result = readContextFile("single", filePath, cwd, maxBytes);
11856
+ if (result.skipped) skipped.push(result.skipped);
11857
+ const layers2 = result.layer ? [result.layer] : [];
11858
+ const mergedContent2 = layers2.map((l) => l.content).join("\n\n---\n\n");
11859
+ return {
11860
+ layers: layers2,
11861
+ mergedContent: mergedContent2,
11862
+ skipped,
11863
+ totalChars: layers2.reduce((sum, l) => sum + l.charCount, 0),
11864
+ totalBytes: layers2.reduce((sum, l) => sum + l.byteCount, 0),
11865
+ maxBytes,
11866
+ candidates
11867
+ };
11868
+ }
11869
+ const layers = [];
11870
+ for (const [level, dir] of [
11871
+ ["global", options.configDir],
11872
+ ["project", options.projectRoot]
11873
+ ]) {
11874
+ const result = findFirstContextFile(level, dir, cwd, candidates, maxBytes);
11875
+ skipped.push(...result.skipped);
11876
+ if (result.layer) layers.push(result.layer);
11877
+ }
11878
+ if (resolve5(options.cwd) !== resolve5(options.projectRoot)) {
11879
+ const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
11880
+ skipped.push(...result.skipped);
11881
+ if (result.layer) layers.push(result.layer);
11882
+ }
11883
+ const mergedContent = layers.map((l) => l.content).join("\n\n---\n\n");
11884
+ return {
11885
+ layers,
11886
+ mergedContent,
11887
+ skipped,
11888
+ totalChars: layers.reduce((sum, l) => sum + l.charCount, 0),
11889
+ totalBytes: layers.reduce((sum, l) => sum + l.byteCount, 0),
11890
+ maxBytes,
11891
+ candidates
11892
+ };
11893
+ }
11894
+
11731
11895
  // src/web/session-handler.ts
11732
- import { existsSync as existsSync21, readFileSync as readFileSync14, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync10, readdirSync as readdirSync9, statSync as statSync8, createWriteStream } from "fs";
11733
- import { join as join15, resolve as resolve5, dirname as dirname5 } from "path";
11734
- import { execSync as execSync3 } from "child_process";
11896
+ import { existsSync as existsSync23, readFileSync as readFileSync17, writeFileSync as writeFileSync2, mkdirSync as mkdirSync10, readdirSync as readdirSync10, statSync as statSync9, createWriteStream } from "fs";
11897
+ import { join as join17, resolve as resolve6, dirname as dirname5 } from "path";
11735
11898
 
11736
11899
  // src/tools/git-context.ts
11737
11900
  import { execSync as execSync2 } from "child_process";
11738
- import { existsSync as existsSync20 } from "fs";
11739
- import { join as join13 } from "path";
11901
+ import { existsSync as existsSync21 } from "fs";
11902
+ import { join as join14 } from "path";
11740
11903
  function runGit2(cmd, cwd) {
11741
11904
  try {
11742
11905
  return execSync2(`git ${cmd}`, {
@@ -11753,7 +11916,7 @@ function getGitRoot(cwd = process.cwd()) {
11753
11916
  return runGit2("rev-parse --show-toplevel", cwd);
11754
11917
  }
11755
11918
  function getGitContext(cwd = process.cwd()) {
11756
- if (!existsSync20(join13(cwd, ".git"))) {
11919
+ if (!existsSync21(join14(cwd, ".git"))) {
11757
11920
  const result = runGit2("rev-parse --git-dir", cwd);
11758
11921
  if (!result) return null;
11759
11922
  }
@@ -11823,6 +11986,264 @@ function formatGitContextForPrompt(ctx) {
11823
11986
  return lines.join("\n");
11824
11987
  }
11825
11988
 
11989
+ // src/cli/review-prompts.ts
11990
+ function buildReviewPrompt(diff, gitContextStr, detailed) {
11991
+ const level = detailed ? "Please perform a detailed in-depth review covering: security, performance, maintainability, error handling, naming conventions, and code duplication." : "Please perform a concise code review focusing on bugs, security issues, and key improvement suggestions.";
11992
+ return `# Code Review Request
11993
+
11994
+ ${level}
11995
+
11996
+ ## Git Status
11997
+ ${gitContextStr}
11998
+
11999
+ ## Code Changes (diff)
12000
+ \`\`\`diff
12001
+ ${diff}
12002
+ \`\`\`
12003
+
12004
+ ## Output Format
12005
+ Please structure your review as follows:
12006
+ 1. **Overall Assessment**: One-sentence summary of the change quality
12007
+ 2. **Issues** (if any): Each issue with [Severity] file:line \u2014 description + suggested fix
12008
+ 3. **Improvement Suggestions** (if any): Non-critical but recommended optimizations
12009
+ 4. **Highlights** (if any): Good practices worth acknowledging
12010
+
12011
+ Severity levels: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F535} Info`;
12012
+ }
12013
+ function buildSecurityReviewPrompt(diff, gitContextStr) {
12014
+ return `# Security Vulnerability Review
12015
+
12016
+ Analyze the following code changes **exclusively for security vulnerabilities**.
12017
+
12018
+ ## Categories to check:
12019
+ 1. **Injection** \u2014 SQL, command, path traversal, XSS, template injection
12020
+ 2. **Authentication & Authorization** \u2014 hardcoded credentials, missing auth checks, privilege escalation
12021
+ 3. **Secrets & Sensitive Data** \u2014 API keys, tokens, passwords in code, logging sensitive data
12022
+ 4. **Input Validation** \u2014 missing validation, unsafe deserialization, buffer issues
12023
+ 5. **Cryptography** \u2014 weak algorithms, improper random, hardcoded IVs/salts
12024
+ 6. **Dependencies** \u2014 known vulnerable packages, unsafe dynamic imports
12025
+ 7. **File System** \u2014 path traversal, unsafe file permissions, symlink attacks
12026
+ 8. **Network** \u2014 SSRF, insecure protocols, missing TLS validation
12027
+
12028
+ ## Git Status
12029
+ ${gitContextStr}
12030
+
12031
+ ## Code Changes (diff)
12032
+ \`\`\`diff
12033
+ ${diff}
12034
+ \`\`\`
12035
+
12036
+ ## Output Format
12037
+ For each finding:
12038
+ - **Severity**: \u{1F534} CRITICAL / \u{1F7E0} HIGH / \u{1F7E1} MEDIUM / \u{1F535} LOW / \u2139\uFE0F INFO
12039
+ - **Category**: (from list above)
12040
+ - **File & location**: file:line
12041
+ - **Description**: what the vulnerability is and how it could be exploited
12042
+ - **Recommended fix**: specific code change to resolve
12043
+
12044
+ If no security issues found, state "\u2705 No security vulnerabilities detected" with a brief explanation of what was checked.`;
12045
+ }
12046
+
12047
+ // src/repl/commands/project-init.ts
12048
+ import { existsSync as existsSync22, readFileSync as readFileSync16, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
12049
+ import { join as join15 } from "path";
12050
+ var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
12051
+ "node_modules",
12052
+ ".git",
12053
+ "dist",
12054
+ "build",
12055
+ "out",
12056
+ "target",
12057
+ ".next",
12058
+ ".nuxt",
12059
+ "__pycache__",
12060
+ ".venv",
12061
+ "venv",
12062
+ ".tox",
12063
+ ".mypy_cache",
12064
+ ".pytest_cache",
12065
+ ".gradle",
12066
+ ".idea",
12067
+ ".vscode",
12068
+ ".vs",
12069
+ "coverage",
12070
+ ".cache",
12071
+ ".parcel-cache",
12072
+ "dist-cjs",
12073
+ "release",
12074
+ ".output",
12075
+ ".turbo",
12076
+ "vendor"
12077
+ ]);
12078
+ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
12079
+ const lines = [];
12080
+ let count = 0;
12081
+ const walk = (d, prefix, depth) => {
12082
+ if (depth > maxDepth || count >= maxEntries) return;
12083
+ let entries;
12084
+ try {
12085
+ entries = readdirSync9(d);
12086
+ } catch {
12087
+ return;
12088
+ }
12089
+ const filtered = entries.filter((e) => !e.startsWith(".") && !SCAN_SKIP_DIRS.has(e));
12090
+ const sorted = filtered.sort((a, b) => {
12091
+ let aIsDir = false, bIsDir = false;
12092
+ try {
12093
+ aIsDir = statSync8(join15(d, a)).isDirectory();
12094
+ } catch {
12095
+ }
12096
+ try {
12097
+ bIsDir = statSync8(join15(d, b)).isDirectory();
12098
+ } catch {
12099
+ }
12100
+ if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
12101
+ return a.localeCompare(b);
12102
+ });
12103
+ for (let i = 0; i < sorted.length && count < maxEntries; i++) {
12104
+ const name = sorted[i];
12105
+ const fullPath = join15(d, name);
12106
+ const isLast = i === sorted.length - 1;
12107
+ const connector = isLast ? "+-- " : "|-- ";
12108
+ let isDir;
12109
+ try {
12110
+ isDir = statSync8(fullPath).isDirectory();
12111
+ } catch {
12112
+ continue;
12113
+ }
12114
+ lines.push(prefix + connector + name + (isDir ? "/" : ""));
12115
+ count++;
12116
+ if (isDir) {
12117
+ walk(fullPath, prefix + (isLast ? " " : "| "), depth + 1);
12118
+ }
12119
+ }
12120
+ };
12121
+ walk(dir, "", 0);
12122
+ if (count >= maxEntries) lines.push("... (truncated)");
12123
+ return lines.join("\n");
12124
+ }
12125
+ function scanProject(cwd) {
12126
+ const info = {
12127
+ type: "unknown",
12128
+ language: "unknown",
12129
+ configFiles: [],
12130
+ directoryStructure: ""
12131
+ };
12132
+ const check = (file) => existsSync22(join15(cwd, file));
12133
+ const configCandidates = [
12134
+ "package.json",
12135
+ "tsconfig.json",
12136
+ "Cargo.toml",
12137
+ "pyproject.toml",
12138
+ "setup.py",
12139
+ "requirements.txt",
12140
+ "go.mod",
12141
+ "pom.xml",
12142
+ "build.gradle",
12143
+ "build.gradle.kts",
12144
+ "CMakeLists.txt",
12145
+ "Makefile",
12146
+ ".csproj",
12147
+ ".sln",
12148
+ "composer.json",
12149
+ "Gemfile",
12150
+ "mix.exs",
12151
+ "deno.json",
12152
+ "bun.lockb"
12153
+ ];
12154
+ info.configFiles = configCandidates.filter(check);
12155
+ if (check("package.json")) {
12156
+ info.type = "node";
12157
+ info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
12158
+ try {
12159
+ const pkg = JSON.parse(readFileSync16(join15(cwd, "package.json"), "utf-8"));
12160
+ const scripts = pkg.scripts ?? {};
12161
+ info.buildCommand = scripts.build ? "npm run build" : void 0;
12162
+ info.testCommand = scripts.test ? "npm test" : void 0;
12163
+ info.devCommand = scripts.dev ? "npm run dev" : void 0;
12164
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
12165
+ if (allDeps["react"]) info.framework = "React";
12166
+ else if (allDeps["vue"]) info.framework = "Vue";
12167
+ else if (allDeps["@angular/core"]) info.framework = "Angular";
12168
+ else if (allDeps["next"]) info.framework = "Next.js";
12169
+ else if (allDeps["nuxt"]) info.framework = "Nuxt";
12170
+ else if (allDeps["express"]) info.framework = "Express";
12171
+ else if (allDeps["fastify"]) info.framework = "Fastify";
12172
+ else if (allDeps["svelte"]) info.framework = "Svelte";
12173
+ } catch {
12174
+ }
12175
+ } else if (check("Cargo.toml")) {
12176
+ info.type = "rust";
12177
+ info.language = "Rust";
12178
+ info.buildCommand = "cargo build";
12179
+ info.testCommand = "cargo test";
12180
+ } else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
12181
+ info.type = "python";
12182
+ info.language = "Python";
12183
+ info.testCommand = check("pytest.ini") || check("pyproject.toml") ? "pytest" : "python -m unittest";
12184
+ } else if (check("go.mod")) {
12185
+ info.type = "go";
12186
+ info.language = "Go";
12187
+ info.buildCommand = "go build ./...";
12188
+ info.testCommand = "go test ./...";
12189
+ } else if (check("pom.xml")) {
12190
+ info.type = "java";
12191
+ info.language = "Java";
12192
+ info.buildCommand = "mvn package";
12193
+ info.testCommand = "mvn test";
12194
+ } else if (check("build.gradle") || check("build.gradle.kts")) {
12195
+ info.type = "java";
12196
+ info.language = "Java/Kotlin";
12197
+ info.buildCommand = "./gradlew build";
12198
+ info.testCommand = "./gradlew test";
12199
+ }
12200
+ info.directoryStructure = scanDirTree(cwd);
12201
+ return info;
12202
+ }
12203
+ function buildInitPrompt(info, cwd) {
12204
+ const parts = [
12205
+ "Please generate an AICLI.md context file (Markdown format) for the following project. This file will be injected into the AI conversation system prompt to help the AI understand the project structure and coding conventions.",
12206
+ "\n## Project Info\n",
12207
+ `- Working directory: ${cwd}`,
12208
+ `- Type: ${info.type}`,
12209
+ `- Language: ${info.language}`
12210
+ ];
12211
+ if (info.framework) parts.push(`- Framework: ${info.framework}`);
12212
+ if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
12213
+ if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
12214
+ if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
12215
+ parts.push(`
12216
+ ## Detected Config Files
12217
+ ${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
12218
+ parts.push(`
12219
+ ## Directory Structure
12220
+ \`\`\`
12221
+ ${info.directoryStructure}
12222
+ \`\`\``);
12223
+ parts.push(`
12224
+ ## Requirements
12225
+ Please generate a structured Markdown file containing:
12226
+ 1. Project overview (one-sentence summary)
12227
+ 2. Tech stack
12228
+ 3. Project structure description (based on the directory structure above)
12229
+ 4. Common commands (build, test, dev, etc.)
12230
+ 5. Code style and conventions (inferred from config files)
12231
+
12232
+ Output the Markdown content directly, do not wrap the entire file in a code block. Keep it concise, within 200 lines.`);
12233
+ return parts.join("\n");
12234
+ }
12235
+
12236
+ // src/core/git-diff.ts
12237
+ import { execFileSync as execFileSync3 } from "child_process";
12238
+ function readGitDiff(options = {}) {
12239
+ const args = ["diff"];
12240
+ if (options.staged) args.push("--staged");
12241
+ return execFileSync3("git", args, {
12242
+ encoding: "utf-8",
12243
+ timeout: options.timeoutMs ?? 1e4
12244
+ }).trim();
12245
+ }
12246
+
11826
12247
  // src/hub/convergence.ts
11827
12248
  function convergenceThreshold(total) {
11828
12249
  if (total <= 0) return Infinity;
@@ -12395,7 +12816,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
12395
12816
  }
12396
12817
 
12397
12818
  // src/hub/persist.ts
12398
- import { join as join14 } from "path";
12819
+ import { join as join16 } from "path";
12399
12820
  function discussionToMessages(state2) {
12400
12821
  const out = [];
12401
12822
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -12433,7 +12854,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
12433
12854
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
12434
12855
  session.titleAiGenerated = true;
12435
12856
  await sm.save();
12436
- return { id: session.id, path: join14(config.getHistoryDir(), `${session.id}.json`) };
12857
+ return { id: session.id, path: join16(config.getHistoryDir(), `${session.id}.json`) };
12437
12858
  }
12438
12859
 
12439
12860
  // src/web/session-handler.ts
@@ -12451,7 +12872,7 @@ function lastAssistantText(messages) {
12451
12872
  }
12452
12873
  return void 0;
12453
12874
  }
12454
- var SessionHandler = class _SessionHandler {
12875
+ var SessionHandler = class {
12455
12876
  ws;
12456
12877
  config;
12457
12878
  providers;
@@ -12488,6 +12909,7 @@ var SessionHandler = class _SessionHandler {
12488
12909
  pendingAutoPause = /* @__PURE__ */ new Map();
12489
12910
  /** Active system prompt from context files */
12490
12911
  activeSystemPrompt;
12912
+ contextLoadResult = null;
12491
12913
  /** Directories added via /add-dir */
12492
12914
  addedDirs = /* @__PURE__ */ new Set();
12493
12915
  /** Track MCP tool names used across this session (for MCP budget prioritization) */
@@ -12594,10 +13016,10 @@ var SessionHandler = class _SessionHandler {
12594
13016
  return;
12595
13017
  }
12596
13018
  case "ask_user_response": {
12597
- const resolve7 = this.pendingAskUser.get(msg.requestId);
12598
- if (resolve7) {
13019
+ const resolve8 = this.pendingAskUser.get(msg.requestId);
13020
+ if (resolve8) {
12599
13021
  this.pendingAskUser.delete(msg.requestId);
12600
- resolve7(msg.answer);
13022
+ resolve8(msg.answer);
12601
13023
  }
12602
13024
  return;
12603
13025
  }
@@ -12608,10 +13030,10 @@ var SessionHandler = class _SessionHandler {
12608
13030
  case "memory_rebuild":
12609
13031
  return this.handleMemoryRebuild(Boolean(msg.full));
12610
13032
  case "auto_pause_response": {
12611
- const resolve7 = this.pendingAutoPause.get(msg.requestId);
12612
- if (resolve7) {
13033
+ const resolve8 = this.pendingAutoPause.get(msg.requestId);
13034
+ if (resolve8) {
12613
13035
  this.pendingAutoPause.delete(msg.requestId);
12614
- resolve7({ action: msg.action, message: msg.message });
13036
+ resolve8({ action: msg.action, message: msg.message });
12615
13037
  }
12616
13038
  return;
12617
13039
  }
@@ -12626,10 +13048,10 @@ var SessionHandler = class _SessionHandler {
12626
13048
  this.hubOrchestrator?.abort();
12627
13049
  return;
12628
13050
  case "hub_steer": {
12629
- const resolve7 = this.pendingHubReview.get(msg.requestId);
12630
- if (resolve7) {
13051
+ const resolve8 = this.pendingHubReview.get(msg.requestId);
13052
+ if (resolve8) {
12631
13053
  this.pendingHubReview.delete(msg.requestId);
12632
- resolve7({ action: msg.action, message: msg.message });
13054
+ resolve8({ action: msg.action, message: msg.message });
12633
13055
  }
12634
13056
  return;
12635
13057
  }
@@ -12641,9 +13063,9 @@ var SessionHandler = class _SessionHandler {
12641
13063
  onDisconnect() {
12642
13064
  this.toolExecutor.cancelAll();
12643
13065
  if (this.abortController) this.abortController.abort();
12644
- for (const resolve7 of this.pendingAskUser.values()) resolve7(null);
13066
+ for (const resolve8 of this.pendingAskUser.values()) resolve8(null);
12645
13067
  this.pendingAskUser.clear();
12646
- for (const resolve7 of this.pendingAutoPause.values()) resolve7({ action: "stop" });
13068
+ for (const resolve8 of this.pendingAutoPause.values()) resolve8({ action: "stop" });
12647
13069
  this.pendingAutoPause.clear();
12648
13070
  this.saveIfNeeded();
12649
13071
  }
@@ -12777,9 +13199,9 @@ var SessionHandler = class _SessionHandler {
12777
13199
  this.hubOrchestrator = orchestrator;
12778
13200
  orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
12779
13201
  if (config.humanSteer) {
12780
- orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve7) => {
13202
+ orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve8) => {
12781
13203
  const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
12782
- this.pendingHubReview.set(requestId, resolve7);
13204
+ this.pendingHubReview.set(requestId, resolve8);
12783
13205
  this.send({ type: "hub_review", requestId, round, maxRounds });
12784
13206
  });
12785
13207
  }
@@ -13116,8 +13538,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
13116
13538
  onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
13117
13539
  requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
13118
13540
  const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
13119
- const pauseResp = await new Promise((resolve7) => {
13120
- this.pendingAutoPause.set(requestId, resolve7);
13541
+ const pauseResp = await new Promise((resolve8) => {
13542
+ this.pendingAutoPause.set(requestId, resolve8);
13121
13543
  this.send({
13122
13544
  type: "auto_pause_request",
13123
13545
  requestId,
@@ -13296,8 +13718,8 @@ ${summaryContent}`,
13296
13718
  }
13297
13719
  if (chunk.done) break;
13298
13720
  }
13299
- await new Promise((resolve7, reject) => {
13300
- fileStream.end((err) => err ? reject(err) : resolve7());
13721
+ await new Promise((resolve8, reject) => {
13722
+ fileStream.end((err) => err ? reject(err) : resolve8());
13301
13723
  });
13302
13724
  const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
13303
13725
  if (verdict.kind === "reject") {
@@ -13323,7 +13745,7 @@ ${summaryContent}`,
13323
13745
  } catch (err) {
13324
13746
  if (fileStream) {
13325
13747
  try {
13326
- await new Promise((resolve7) => fileStream.end(() => resolve7()));
13748
+ await new Promise((resolve8) => fileStream.end(() => resolve8()));
13327
13749
  } catch {
13328
13750
  }
13329
13751
  }
@@ -13612,7 +14034,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
13612
14034
  " /plan [enter|exit] \u2014 Toggle read-only planning mode",
13613
14035
  " /session new|list|load|delete <id> \u2014 Session management",
13614
14036
  " /system [prompt|clear] \u2014 Set/view/reset system prompt",
13615
- " /context [reload] \u2014 Show/reload context layers",
14037
+ " /context [status|reload] \u2014 Show/reload context layers",
13616
14038
  " /status \u2014 Show session info & token usage",
13617
14039
  " /cost \u2014 Show cumulative token usage",
13618
14040
  " /config [show|get|set] \u2014 View/modify configuration",
@@ -13853,9 +14275,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
13853
14275
  let modifiedFiles = 0;
13854
14276
  const diffLines2 = [];
13855
14277
  for (const [filePath, { earliest }] of fileMap) {
13856
- const currentContent = existsSync21(filePath) ? (() => {
14278
+ const currentContent = existsSync23(filePath) ? (() => {
13857
14279
  try {
13858
- return readFileSync14(filePath, "utf-8");
14280
+ return readFileSync17(filePath, "utf-8");
13859
14281
  } catch {
13860
14282
  return null;
13861
14283
  }
@@ -13956,7 +14378,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
13956
14378
  break;
13957
14379
  }
13958
14380
  const sub = args[0]?.toLowerCase();
13959
- const resolve7 = (ref) => {
14381
+ const resolve8 = (ref) => {
13960
14382
  const r = session.resolveBranchRef(ref);
13961
14383
  if (r.ok) return r.id;
13962
14384
  if (r.reason === "ambiguous") {
@@ -14010,7 +14432,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14010
14432
  this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
14011
14433
  break;
14012
14434
  }
14013
- const id = resolve7(ref);
14435
+ const id = resolve8(ref);
14014
14436
  if (!id) break;
14015
14437
  const ok = session.switchBranch(id);
14016
14438
  if (ok) {
@@ -14030,7 +14452,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14030
14452
  this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
14031
14453
  break;
14032
14454
  }
14033
- const id = resolve7(ref);
14455
+ const id = resolve8(ref);
14034
14456
  if (!id) break;
14035
14457
  const ok = session.deleteBranch(id);
14036
14458
  if (ok) {
@@ -14049,7 +14471,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14049
14471
  this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
14050
14472
  break;
14051
14473
  }
14052
- const id = resolve7(ref);
14474
+ const id = resolve8(ref);
14053
14475
  if (!id) break;
14054
14476
  const ok = session.renameBranch(id, title);
14055
14477
  if (ok) {
@@ -14067,7 +14489,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14067
14489
  this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
14068
14490
  break;
14069
14491
  }
14070
- const id = resolve7(ref);
14492
+ const id = resolve8(ref);
14071
14493
  if (!id) break;
14072
14494
  const d = session.diffBranches(id);
14073
14495
  if (!d) {
@@ -14109,7 +14531,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14109
14531
  this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
14110
14532
  break;
14111
14533
  }
14112
- const id = resolve7(ref);
14534
+ const id = resolve8(ref);
14113
14535
  if (!id) break;
14114
14536
  const idx = parseInt(idxStr, 10);
14115
14537
  if (Number.isNaN(idx)) {
@@ -14136,7 +14558,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14136
14558
  const sub = (args[0] ?? "status").toLowerCase();
14137
14559
  const root = process.cwd();
14138
14560
  const { loadIndex: loadIndex2, clearIndex } = await import("./store-VO37H6LS.js");
14139
- const { indexProject: indexProject2 } = await import("./indexer-BMYUUDLH.js");
14561
+ const { indexProject: indexProject2 } = await import("./indexer-4WWS3VIL.js");
14140
14562
  const { loadVectorStore, clearVectorStore } = await import("./vector-store-JBAE6PS4.js");
14141
14563
  if (sub === "status") {
14142
14564
  const idx = loadIndex2(root);
@@ -14255,8 +14677,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14255
14677
  const detailed = args.includes("--detailed");
14256
14678
  let diff;
14257
14679
  try {
14258
- const cmd = staged ? "git diff --staged" : "git diff";
14259
- diff = execSync3(cmd, { encoding: "utf-8", timeout: 1e4 }).trim();
14680
+ diff = readGitDiff({ staged });
14260
14681
  } catch {
14261
14682
  this.send({ type: "error", message: "Failed to run git diff." });
14262
14683
  break;
@@ -14273,7 +14694,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14273
14694
  diff = head + "\n\n... [diff truncated, " + diff.length + " chars total] ...\n\n" + tail;
14274
14695
  truncated = true;
14275
14696
  }
14276
- const reviewPrompt = this.buildReviewPrompt(diff, formatGitContextForPrompt(gitCtx), detailed);
14697
+ const reviewPrompt = buildReviewPrompt(diff, formatGitContextForPrompt(gitCtx), detailed);
14277
14698
  this.send({ type: "info", message: "\u{1F50D} Analyzing changes..." });
14278
14699
  try {
14279
14700
  const review = await this.chatOnce(reviewPrompt, { temperature: 0.3, maxTokens: 8192 });
@@ -14294,8 +14715,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14294
14715
  const secStaged = args.includes("--staged");
14295
14716
  let secDiff;
14296
14717
  try {
14297
- const cmd = secStaged ? "git diff --staged" : "git diff";
14298
- secDiff = execSync3(cmd, { encoding: "utf-8", timeout: 1e4 }).trim();
14718
+ secDiff = readGitDiff({ staged: secStaged });
14299
14719
  } catch {
14300
14720
  this.send({ type: "error", message: "Failed to run git diff." });
14301
14721
  break;
@@ -14312,7 +14732,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14312
14732
  secDiff = head + "\n\n... [diff truncated, " + secDiff.length + " chars total] ...\n\n" + tail;
14313
14733
  secTruncated = true;
14314
14734
  }
14315
- const secPrompt = this.buildSecurityReviewPrompt(secDiff, formatGitContextForPrompt(gitCtx));
14735
+ const secPrompt = buildSecurityReviewPrompt(secDiff, formatGitContextForPrompt(gitCtx));
14316
14736
  this.send({ type: "info", message: "\u{1F512} Scanning for security vulnerabilities..." });
14317
14737
  try {
14318
14738
  const secReview = await this.chatOnce(secPrompt, { temperature: 0.2, maxTokens: 8192 });
@@ -14373,7 +14793,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14373
14793
  case "test": {
14374
14794
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
14375
14795
  try {
14376
- const { executeTests } = await import("./run-tests-HZ2CRCTZ.js");
14796
+ const { executeTests } = await import("./run-tests-NT2UIUVB.js");
14377
14797
  const argStr = args.join(" ").trim();
14378
14798
  let testArgs = {};
14379
14799
  if (argStr) {
@@ -14390,19 +14810,19 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14390
14810
  // ── /init ───────────────────────────────────────────────────────
14391
14811
  case "init": {
14392
14812
  const cwd = process.cwd();
14393
- const targetPath = join15(cwd, "AICLI.md");
14813
+ const targetPath = join17(cwd, "AICLI.md");
14394
14814
  const force = args.includes("--force");
14395
- if (existsSync21(targetPath) && !force) {
14815
+ if (existsSync23(targetPath) && !force) {
14396
14816
  this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
14397
14817
  Use /init --force to overwrite.` });
14398
14818
  break;
14399
14819
  }
14400
14820
  this.send({ type: "info", message: "\u{1F4DD} Scanning project structure..." });
14401
14821
  try {
14402
- const projectInfo = this.scanProject(cwd);
14403
- const prompt = this.buildInitPrompt(projectInfo, cwd);
14822
+ const projectInfo = scanProject(cwd);
14823
+ const prompt = buildInitPrompt(projectInfo, cwd);
14404
14824
  const content = await this.chatOnce(prompt, { temperature: 0.3, maxTokens: 4096 });
14405
- writeFileSync4(targetPath, content, "utf-8");
14825
+ writeFileSync2(targetPath, content, "utf-8");
14406
14826
  this.send({ type: "info", message: `\u2713 Generated: ${targetPath} (${content.length} chars)
14407
14827
  Use /context reload to load it.` });
14408
14828
  } catch (err) {
@@ -14425,19 +14845,19 @@ Use /context reload to load it.` });
14425
14845
  lines.push("**Config Files:**");
14426
14846
  lines.push(` Dir: ${configDir}`);
14427
14847
  const checkFile = (label, filePath) => {
14428
- const exists = existsSync21(filePath);
14848
+ const exists = existsSync23(filePath);
14429
14849
  let extra = "";
14430
14850
  if (exists) {
14431
14851
  try {
14432
- extra = ` (${statSync8(filePath).size} bytes)`;
14852
+ extra = ` (${statSync9(filePath).size} bytes)`;
14433
14853
  } catch {
14434
14854
  }
14435
14855
  }
14436
14856
  lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
14437
14857
  };
14438
- checkFile("config.json", join15(configDir, "config.json"));
14439
- checkFile("memory.md", join15(configDir, MEMORY_FILE_NAME));
14440
- checkFile("dev-state.md", join15(configDir, "dev-state.md"));
14858
+ checkFile("config.json", join17(configDir, "config.json"));
14859
+ checkFile("memory.md", join17(configDir, MEMORY_FILE_NAME));
14860
+ checkFile("dev-state.md", join17(configDir, "dev-state.md"));
14441
14861
  lines.push("");
14442
14862
  if (this.mcpManager) {
14443
14863
  lines.push("**MCP Servers:**");
@@ -14537,29 +14957,24 @@ ${this.config.toFormattedJSON()}
14537
14957
  this.send({ type: "info", message: this.activeSystemPrompt ? `\u2713 Context reloaded (${this.activeSystemPrompt.length} chars).` : "\u2713 No context files found." });
14538
14958
  break;
14539
14959
  }
14540
- const configDir = this.config.getConfigDir();
14541
- const cwd = process.cwd();
14542
- const gitRoot = getGitRoot(cwd);
14543
- const projectRoot = gitRoot ?? cwd;
14960
+ const result = this.contextLoadResult;
14544
14961
  const layers = ["\u{1F4DA} **Context Layers:**", ""];
14545
- const checkLayer = (label, dir) => {
14546
- for (const name2 of CONTEXT_FILE_CANDIDATES) {
14547
- const fullPath = join15(dir, name2);
14548
- if (existsSync21(fullPath)) {
14549
- try {
14550
- const size = statSync8(fullPath).size;
14551
- layers.push(` \u2713 ${label}: ${fullPath} (${size} bytes)`);
14552
- return;
14553
- } catch {
14554
- }
14555
- }
14962
+ if (result && result.layers.length > 0) {
14963
+ const labels = { global: "Global ", project: "Project", local: "Local " };
14964
+ for (const layer of result.layers) {
14965
+ layers.push(` \u2713 ${labels[layer.level] ?? layer.level}: ${layer.displayPath} (${layer.charCount} chars${layer.truncated ? ", truncated" : ""})`);
14966
+ }
14967
+ layers.push(` Total: ${result.totalChars} chars (${result.layers.length} layer${result.layers.length > 1 ? "s" : ""})`);
14968
+ } else {
14969
+ layers.push(" No context files loaded.");
14970
+ layers.push(" Search order per layer: AICLI.override.md \u2192 AGENTS.override.md \u2192 AICLI.md \u2192 CLAUDE.md \u2192 AGENTS.md");
14971
+ }
14972
+ if (result && result.skipped.length > 0) {
14973
+ layers.push("");
14974
+ layers.push("Skipped:");
14975
+ for (const skipped of result.skipped.slice(0, 5)) {
14976
+ layers.push(` - ${skipped.displayPath}: ${skipped.reason}${skipped.message ? ` (${skipped.message})` : ""}`);
14556
14977
  }
14557
- layers.push(` \u2013 ${label}: (none)`);
14558
- };
14559
- checkLayer("Global", configDir);
14560
- checkLayer("Project", projectRoot);
14561
- if (resolve5(cwd) !== resolve5(projectRoot)) {
14562
- checkLayer("Subdir", cwd);
14563
14978
  }
14564
14979
  layers.push("");
14565
14980
  layers.push(`Total prompt length: ${this.activeSystemPrompt?.length ?? 0} chars`);
@@ -14629,12 +15044,12 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
14629
15044
  this.send({ type: "info", message: "\u2713 All added directories removed from context." });
14630
15045
  break;
14631
15046
  }
14632
- const dirPath = resolve5(sub);
14633
- if (!existsSync21(dirPath)) {
15047
+ const dirPath = resolve6(sub);
15048
+ if (!existsSync23(dirPath)) {
14634
15049
  this.send({ type: "error", message: `Directory not found: ${dirPath}` });
14635
15050
  break;
14636
15051
  }
14637
- if (!statSync8(dirPath).isDirectory()) {
15052
+ if (!statSync9(dirPath).isDirectory()) {
14638
15053
  this.send({ type: "error", message: `Not a directory: ${dirPath}` });
14639
15054
  break;
14640
15055
  }
@@ -14646,14 +15061,14 @@ It will be included in AI context for subsequent messages.` });
14646
15061
  // ── /commands ───────────────────────────────────────────────────
14647
15062
  case "commands": {
14648
15063
  const configDir = this.config.getConfigDir();
14649
- const commandsDir = join15(configDir, CUSTOM_COMMANDS_DIR_NAME);
14650
- if (!existsSync21(commandsDir)) {
15064
+ const commandsDir = join17(configDir, CUSTOM_COMMANDS_DIR_NAME);
15065
+ if (!existsSync23(commandsDir)) {
14651
15066
  this.send({ type: "info", message: `No custom commands directory.
14652
15067
  Create: ${commandsDir}/ with .md files.` });
14653
15068
  break;
14654
15069
  }
14655
15070
  try {
14656
- const files = readdirSync9(commandsDir).filter((f) => f.endsWith(".md"));
15071
+ const files = readdirSync10(commandsDir).filter((f) => f.endsWith(".md"));
14657
15072
  if (files.length === 0) {
14658
15073
  this.send({ type: "info", message: `No custom commands found in ${commandsDir}
14659
15074
  Add .md files to create commands.` });
@@ -14673,7 +15088,7 @@ Add .md files to create commands.` });
14673
15088
  // ── /plugins ────────────────────────────────────────────────────
14674
15089
  case "plugins": {
14675
15090
  const configDir = this.config.getConfigDir();
14676
- const pluginsDir = join15(configDir, PLUGINS_DIR_NAME);
15091
+ const pluginsDir = join17(configDir, PLUGINS_DIR_NAME);
14677
15092
  const pluginTools = this.toolRegistry.listPluginTools();
14678
15093
  const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
14679
15094
  if (pluginTools.length === 0) {
@@ -14847,11 +15262,11 @@ Add .md files to create commands.` });
14847
15262
  }
14848
15263
  memoryShow() {
14849
15264
  const configDir = this.config.getConfigDir();
14850
- const memPath = join15(configDir, MEMORY_FILE_NAME);
15265
+ const memPath = join17(configDir, MEMORY_FILE_NAME);
14851
15266
  let content = "";
14852
15267
  try {
14853
- if (existsSync21(memPath)) {
14854
- content = readFileSync14(memPath, "utf-8");
15268
+ if (existsSync23(memPath)) {
15269
+ content = readFileSync17(memPath, "utf-8");
14855
15270
  }
14856
15271
  } catch (err) {
14857
15272
  process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
@@ -14865,13 +15280,15 @@ Add .md files to create commands.` });
14865
15280
  }
14866
15281
  memoryAdd(text) {
14867
15282
  const configDir = this.config.getConfigDir();
14868
- const memPath = join15(configDir, MEMORY_FILE_NAME);
15283
+ const memPath = join17(configDir, MEMORY_FILE_NAME);
14869
15284
  try {
14870
15285
  mkdirSync10(configDir, { recursive: true });
14871
15286
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
14872
- appendFileSync3(memPath, `
15287
+ const previous = existsSync23(memPath) ? readFileSync17(memPath, "utf-8") : "";
15288
+ const entry = `
14873
15289
  - [${timestamp}] ${text}
14874
- `, "utf-8");
15290
+ `;
15291
+ atomicWriteFileSync(memPath, previous + entry);
14875
15292
  this.send({ type: "info", message: `\u{1F4DD} Memory entry added: "${text}"` });
14876
15293
  } catch (err) {
14877
15294
  this.send({ type: "error", message: `Failed to write memory: ${err.message}` });
@@ -14879,9 +15296,9 @@ Add .md files to create commands.` });
14879
15296
  }
14880
15297
  memoryClear() {
14881
15298
  const configDir = this.config.getConfigDir();
14882
- const memPath = join15(configDir, MEMORY_FILE_NAME);
15299
+ const memPath = join17(configDir, MEMORY_FILE_NAME);
14883
15300
  try {
14884
- writeFileSync4(memPath, "", "utf-8");
15301
+ atomicWriteFileSync(memPath, "");
14885
15302
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
14886
15303
  } catch (err) {
14887
15304
  this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
@@ -15044,7 +15461,7 @@ Add .md files to create commands.` });
15044
15461
  dirContext += `
15045
15462
  [Directory: ${dir}]
15046
15463
  `;
15047
- dirContext += this.scanDirTree(dir, 2, 40) + "\n";
15464
+ dirContext += scanDirTree(dir, 2, 40) + "\n";
15048
15465
  totalLen = dirContext.length;
15049
15466
  }
15050
15467
  stable += dirContext;
@@ -15083,265 +15500,21 @@ Add .md files to create commands.` });
15083
15500
  }
15084
15501
  return { toolDefs: this.toolRegistry.getDefinitions(), mcpBudgetNote: null };
15085
15502
  }
15086
- /**
15087
- * Find first matching context file in a directory.
15088
- */
15089
- findContextFile(dir) {
15090
- for (const name of CONTEXT_FILE_CANDIDATES) {
15091
- const fullPath = join15(dir, name);
15092
- try {
15093
- if (existsSync21(fullPath)) {
15094
- const content = readFileSync14(fullPath, "utf-8").trim();
15095
- if (content) return content;
15096
- }
15097
- } catch {
15098
- }
15099
- }
15100
- return null;
15101
- }
15102
- /**
15103
- * Load hierarchical context files (same as CLI):
15104
- * 1. Global: ~/.aicli/AICLI.md or CLAUDE.md
15105
- * 2. Project: <git-root>/AICLI.md or CLAUDE.md
15106
- * 3. Subdir: <cwd>/AICLI.md or CLAUDE.md (only if cwd ≠ project root)
15107
- */
15108
- // ── /init helpers ─────────────────────────────────────────────────
15109
- static SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
15110
- "node_modules",
15111
- ".git",
15112
- "dist",
15113
- "build",
15114
- "out",
15115
- "target",
15116
- ".next",
15117
- ".nuxt",
15118
- "__pycache__",
15119
- ".venv",
15120
- "venv",
15121
- ".tox",
15122
- ".mypy_cache",
15123
- ".pytest_cache",
15124
- ".gradle",
15125
- ".idea",
15126
- ".vscode",
15127
- ".vs",
15128
- "coverage",
15129
- ".cache",
15130
- ".parcel-cache",
15131
- "dist-cjs",
15132
- "release",
15133
- ".output",
15134
- ".turbo",
15135
- "vendor"
15136
- ]);
15137
- scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
15138
- const lines = [];
15139
- let count = 0;
15140
- const walk = (d, prefix, depth) => {
15141
- if (depth > maxDepth || count >= maxEntries) return;
15142
- let entries;
15143
- try {
15144
- entries = readdirSync9(d);
15145
- } catch {
15146
- return;
15147
- }
15148
- const filtered = entries.filter((e) => !e.startsWith(".") && !_SessionHandler.SCAN_SKIP_DIRS.has(e));
15149
- const sorted = filtered.sort((a, b) => {
15150
- let aIsDir = false, bIsDir = false;
15151
- try {
15152
- aIsDir = statSync8(join15(d, a)).isDirectory();
15153
- } catch {
15154
- }
15155
- try {
15156
- bIsDir = statSync8(join15(d, b)).isDirectory();
15157
- } catch {
15158
- }
15159
- if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
15160
- return a.localeCompare(b);
15161
- });
15162
- for (let i = 0; i < sorted.length && count < maxEntries; i++) {
15163
- const name = sorted[i];
15164
- const fullPath = join15(d, name);
15165
- const isLast = i === sorted.length - 1;
15166
- let isDir;
15167
- try {
15168
- isDir = statSync8(fullPath).isDirectory();
15169
- } catch {
15170
- continue;
15171
- }
15172
- lines.push(prefix + (isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ") + name + (isDir ? "/" : ""));
15173
- count++;
15174
- if (isDir) walk(fullPath, prefix + (isLast ? " " : "\u2502 "), depth + 1);
15175
- }
15176
- };
15177
- walk(dir, "", 0);
15178
- if (count >= maxEntries) lines.push("... (truncated)");
15179
- return lines.join("\n");
15180
- }
15181
- scanProject(cwd) {
15182
- const info = { type: "unknown", language: "unknown", configFiles: [], directoryStructure: "" };
15183
- const check = (file) => existsSync21(join15(cwd, file));
15184
- const configCandidates = [
15185
- "package.json",
15186
- "tsconfig.json",
15187
- "Cargo.toml",
15188
- "pyproject.toml",
15189
- "setup.py",
15190
- "requirements.txt",
15191
- "go.mod",
15192
- "pom.xml",
15193
- "build.gradle",
15194
- "build.gradle.kts",
15195
- "CMakeLists.txt",
15196
- "Makefile",
15197
- ".csproj",
15198
- ".sln",
15199
- "composer.json",
15200
- "Gemfile",
15201
- "mix.exs",
15202
- "deno.json",
15203
- "bun.lockb"
15204
- ];
15205
- info.configFiles = configCandidates.filter(check);
15206
- if (check("package.json")) {
15207
- info.type = "node";
15208
- info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
15209
- try {
15210
- const pkg = JSON.parse(readFileSync14(join15(cwd, "package.json"), "utf-8"));
15211
- const scripts = pkg.scripts ?? {};
15212
- info.buildCommand = scripts.build ? "npm run build" : void 0;
15213
- info.testCommand = scripts.test ? "npm test" : void 0;
15214
- info.devCommand = scripts.dev ? "npm run dev" : void 0;
15215
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
15216
- if (allDeps["react"]) info.framework = "React";
15217
- else if (allDeps["vue"]) info.framework = "Vue";
15218
- else if (allDeps["@angular/core"]) info.framework = "Angular";
15219
- else if (allDeps["next"]) info.framework = "Next.js";
15220
- else if (allDeps["express"]) info.framework = "Express";
15221
- } catch {
15222
- }
15223
- } else if (check("Cargo.toml")) {
15224
- info.type = "rust";
15225
- info.language = "Rust";
15226
- info.buildCommand = "cargo build";
15227
- info.testCommand = "cargo test";
15228
- } else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
15229
- info.type = "python";
15230
- info.language = "Python";
15231
- info.testCommand = "pytest";
15232
- } else if (check("go.mod")) {
15233
- info.type = "go";
15234
- info.language = "Go";
15235
- info.buildCommand = "go build ./...";
15236
- info.testCommand = "go test ./...";
15237
- } else if (check("pom.xml")) {
15238
- info.type = "java";
15239
- info.language = "Java";
15240
- info.buildCommand = "mvn package";
15241
- info.testCommand = "mvn test";
15242
- } else if (check("build.gradle") || check("build.gradle.kts")) {
15243
- info.type = "java";
15244
- info.language = "Java/Kotlin";
15245
- info.buildCommand = "./gradlew build";
15246
- info.testCommand = "./gradlew test";
15247
- }
15248
- info.directoryStructure = this.scanDirTree(cwd);
15249
- return info;
15250
- }
15251
- buildInitPrompt(info, cwd) {
15252
- const parts = [
15253
- "Please generate an AICLI.md context file (Markdown format) for the following project.",
15254
- "\n## Project Info\n",
15255
- `- Working directory: ${cwd}`,
15256
- `- Type: ${info.type}`,
15257
- `- Language: ${info.language}`
15258
- ];
15259
- if (info.framework) parts.push(`- Framework: ${info.framework}`);
15260
- if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
15261
- if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
15262
- if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
15263
- parts.push(`
15264
- ## Detected Config Files
15265
- ${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
15266
- parts.push(`
15267
- ## Directory Structure
15268
- \`\`\`
15269
- ${info.directoryStructure}
15270
- \`\`\``);
15271
- parts.push(`
15272
- ## Requirements
15273
- Generate a structured Markdown file with: project overview, tech stack, project structure, common commands, code style. Keep it concise, within 200 lines.`);
15274
- return parts.join("\n");
15275
- }
15276
- // ── /review helper ──────────────────────────────────────────────────
15277
- buildReviewPrompt(diff, gitContextStr, detailed) {
15278
- const level = detailed ? "Please perform a detailed in-depth review covering: security, performance, maintainability, error handling, naming conventions, and code duplication." : "Please perform a concise code review focusing on bugs, security issues, and key improvement suggestions.";
15279
- return `# Code Review Request
15280
-
15281
- ${level}
15282
-
15283
- ## Git Status
15284
- ${gitContextStr}
15285
-
15286
- ## Code Changes (diff)
15287
- \`\`\`diff
15288
- ${diff}
15289
- \`\`\`
15290
-
15291
- ## Output Format
15292
- 1. **Overall Assessment**: One-sentence summary
15293
- 2. **Issues** (if any): [Severity] file:line \u2014 description + fix
15294
- 3. **Improvement Suggestions** (if any)
15295
- 4. **Highlights** (if any)
15296
-
15297
- Severity: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F535} Info`;
15298
- }
15299
- buildSecurityReviewPrompt(diff, gitContextStr) {
15300
- return `# Security Vulnerability Review
15301
-
15302
- Analyze the following code changes **exclusively for security vulnerabilities**.
15303
-
15304
- ## Categories
15305
- 1. Injection (SQL, command, path traversal, XSS)
15306
- 2. Auth & Authorization (hardcoded creds, missing checks)
15307
- 3. Secrets & Sensitive Data (API keys, tokens in code)
15308
- 4. Input Validation (missing validation, unsafe deserialization)
15309
- 5. Cryptography (weak algorithms, hardcoded IVs)
15310
- 6. File System (path traversal, symlink attacks)
15311
- 7. Network (SSRF, insecure protocols)
15312
-
15313
- ## Git Status
15314
- ${gitContextStr}
15315
-
15316
- ## Code Changes
15317
- \`\`\`diff
15318
- ${diff}
15319
- \`\`\`
15320
-
15321
- ## Output Format
15322
- For each finding:
15323
- - **Severity**: \u{1F534} CRITICAL / \u{1F7E0} HIGH / \u{1F7E1} MEDIUM / \u{1F535} LOW
15324
- - **Category** + **File:line**
15325
- - **Description** + exploitation scenario
15326
- - **Recommended fix**
15327
-
15328
- If none found: "\u2705 No security vulnerabilities detected"`;
15329
- }
15330
15503
  loadContextFiles() {
15331
- const parts = [];
15504
+ const contextConfig = this.config.get("context");
15332
15505
  const cwd = process.cwd();
15333
- const configDir = this.config.getConfigDir();
15334
- const globalCtx = this.findContextFile(configDir);
15335
- if (globalCtx) parts.push(globalCtx);
15336
15506
  const gitRoot = getGitRoot(cwd);
15337
15507
  const projectRoot = gitRoot ?? cwd;
15338
- const projectCtx = this.findContextFile(projectRoot);
15339
- if (projectCtx) parts.push(projectCtx);
15340
- if (resolve5(cwd) !== resolve5(projectRoot)) {
15341
- const localCtx = this.findContextFile(cwd);
15342
- if (localCtx) parts.push(localCtx);
15343
- }
15344
- return parts.length > 0 ? parts.join("\n\n---\n\n") : void 0;
15508
+ const result = loadContextFiles({
15509
+ cwd,
15510
+ configDir: this.config.getConfigDir(),
15511
+ projectRoot,
15512
+ setting: this.config.get("contextFile"),
15513
+ fallbackFilenames: contextConfig.projectDocFallbackFilenames,
15514
+ maxBytes: contextConfig.projectDocMaxBytes
15515
+ });
15516
+ this.contextLoadResult = result;
15517
+ return result.mergedContent || void 0;
15345
15518
  }
15346
15519
  };
15347
15520
 
@@ -15357,8 +15530,8 @@ async function setupProxy(configProxy) {
15357
15530
  }
15358
15531
 
15359
15532
  // src/web/auth.ts
15360
- import { existsSync as existsSync22, readFileSync as readFileSync15, writeFileSync as writeFileSync5, mkdirSync as mkdirSync11, readdirSync as readdirSync10, copyFileSync } from "fs";
15361
- import { join as join16 } from "path";
15533
+ import { existsSync as existsSync24, readFileSync as readFileSync18, writeFileSync as writeFileSync3, mkdirSync as mkdirSync11, readdirSync as readdirSync11, copyFileSync } from "fs";
15534
+ import { join as join18 } from "path";
15362
15535
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
15363
15536
  var USERS_FILE = "users.json";
15364
15537
  var TOKEN_EXPIRY_HOURS = 24;
@@ -15373,7 +15546,7 @@ var AuthManager = class {
15373
15546
  db;
15374
15547
  constructor(baseDir) {
15375
15548
  this.baseDir = baseDir;
15376
- this.usersFile = join16(baseDir, USERS_FILE);
15549
+ this.usersFile = join18(baseDir, USERS_FILE);
15377
15550
  this.db = this.loadOrCreate();
15378
15551
  }
15379
15552
  // ── Public API ─────────────────────────────────────────────────
@@ -15402,9 +15575,9 @@ var AuthManager = class {
15402
15575
  }
15403
15576
  const salt = randomBytes(16).toString("hex");
15404
15577
  const passwordHash = this.hashPassword(password, salt);
15405
- const dataDir = join16(USERS_DIR, username);
15406
- const fullDataDir = join16(this.baseDir, dataDir);
15407
- mkdirSync11(join16(fullDataDir, "history"), { recursive: true });
15578
+ const dataDir = join18(USERS_DIR, username);
15579
+ const fullDataDir = join18(this.baseDir, dataDir);
15580
+ mkdirSync11(join18(fullDataDir, "history"), { recursive: true });
15408
15581
  const user = {
15409
15582
  username,
15410
15583
  passwordHash,
@@ -15519,7 +15692,7 @@ var AuthManager = class {
15519
15692
  getUserDataDir(username) {
15520
15693
  const user = this.db.users.find((u) => u.username === username);
15521
15694
  if (!user) throw new Error(`User not found: ${username}`);
15522
- return join16(this.baseDir, user.dataDir);
15695
+ return join18(this.baseDir, user.dataDir);
15523
15696
  }
15524
15697
  /** List all usernames */
15525
15698
  listUsers() {
@@ -15555,30 +15728,30 @@ var AuthManager = class {
15555
15728
  const err = this.register(username, password);
15556
15729
  if (err) return err;
15557
15730
  const userDir = this.getUserDataDir(username);
15558
- const globalConfig = join16(this.baseDir, "config.json");
15559
- if (existsSync22(globalConfig)) {
15731
+ const globalConfig = join18(this.baseDir, "config.json");
15732
+ if (existsSync24(globalConfig)) {
15560
15733
  try {
15561
- const content = readFileSync15(globalConfig, "utf-8");
15562
- writeFileSync5(join16(userDir, "config.json"), content, "utf-8");
15734
+ const content = readFileSync18(globalConfig, "utf-8");
15735
+ writeFileSync3(join18(userDir, "config.json"), content, "utf-8");
15563
15736
  } catch {
15564
15737
  }
15565
15738
  }
15566
- const globalMemory = join16(this.baseDir, "memory.md");
15567
- if (existsSync22(globalMemory)) {
15739
+ const globalMemory = join18(this.baseDir, "memory.md");
15740
+ if (existsSync24(globalMemory)) {
15568
15741
  try {
15569
- const content = readFileSync15(globalMemory, "utf-8");
15570
- writeFileSync5(join16(userDir, "memory.md"), content, "utf-8");
15742
+ const content = readFileSync18(globalMemory, "utf-8");
15743
+ writeFileSync3(join18(userDir, "memory.md"), content, "utf-8");
15571
15744
  } catch {
15572
15745
  }
15573
15746
  }
15574
- const globalHistory = join16(this.baseDir, "history");
15575
- if (existsSync22(globalHistory)) {
15747
+ const globalHistory = join18(this.baseDir, "history");
15748
+ if (existsSync24(globalHistory)) {
15576
15749
  try {
15577
- const files = readdirSync10(globalHistory).filter((f) => f.endsWith(".json"));
15578
- const userHistory = join16(userDir, "history");
15750
+ const files = readdirSync11(globalHistory).filter((f) => f.endsWith(".json"));
15751
+ const userHistory = join18(userDir, "history");
15579
15752
  for (const f of files) {
15580
15753
  try {
15581
- copyFileSync(join16(globalHistory, f), join16(userHistory, f));
15754
+ copyFileSync(join18(globalHistory, f), join18(userHistory, f));
15582
15755
  } catch {
15583
15756
  }
15584
15757
  }
@@ -15589,9 +15762,9 @@ var AuthManager = class {
15589
15762
  }
15590
15763
  // ── Private methods ────────────────────────────────────────────
15591
15764
  loadOrCreate() {
15592
- if (existsSync22(this.usersFile)) {
15765
+ if (existsSync24(this.usersFile)) {
15593
15766
  try {
15594
- return JSON.parse(readFileSync15(this.usersFile, "utf-8"));
15767
+ return JSON.parse(readFileSync18(this.usersFile, "utf-8"));
15595
15768
  } catch {
15596
15769
  }
15597
15770
  }
@@ -15643,7 +15816,7 @@ function getModuleDir() {
15643
15816
  }
15644
15817
  } catch {
15645
15818
  }
15646
- return resolve6(".");
15819
+ return resolve7(".");
15647
15820
  }
15648
15821
  async function startWebServer(options = {}) {
15649
15822
  const port = options.port ?? 3e3;
@@ -15708,8 +15881,8 @@ async function startWebServer(options = {}) {
15708
15881
  }
15709
15882
  }
15710
15883
  let skillManager = null;
15711
- const skillsDir = join17(config.getConfigDir(), SKILLS_DIR_NAME);
15712
- if (existsSync23(skillsDir)) {
15884
+ const skillsDir = join19(config.getConfigDir(), SKILLS_DIR_NAME);
15885
+ if (existsSync25(skillsDir)) {
15713
15886
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
15714
15887
  skillManager.loadSkills();
15715
15888
  const count = skillManager.listSkills().length;
@@ -15780,17 +15953,18 @@ async function startWebServer(options = {}) {
15780
15953
  next();
15781
15954
  };
15782
15955
  const moduleDir = getModuleDir();
15783
- let clientDir = join17(moduleDir, "web", "client");
15784
- if (!existsSync23(clientDir)) {
15785
- clientDir = join17(moduleDir, "client");
15956
+ let clientDir = join19(moduleDir, "web", "client");
15957
+ if (!existsSync25(clientDir)) {
15958
+ clientDir = join19(moduleDir, "client");
15786
15959
  }
15787
- if (!existsSync23(clientDir)) {
15788
- clientDir = join17(moduleDir, "..", "..", "src", "web", "client");
15960
+ if (!existsSync25(clientDir)) {
15961
+ clientDir = join19(moduleDir, "..", "..", "src", "web", "client");
15789
15962
  }
15790
- if (!existsSync23(clientDir)) {
15791
- clientDir = join17(process.cwd(), "src", "web", "client");
15963
+ if (!existsSync25(clientDir)) {
15964
+ clientDir = join19(process.cwd(), "src", "web", "client");
15792
15965
  }
15793
15966
  console.log(` Static files: ${clientDir}`);
15967
+ app.use("/vendor", express.static(join19(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
15794
15968
  app.use(express.static(clientDir));
15795
15969
  app.get("/api/status", (_req, res) => {
15796
15970
  res.json({
@@ -15833,7 +16007,7 @@ async function startWebServer(options = {}) {
15833
16007
  }
15834
16008
  const token = authManager.login(username, password);
15835
16009
  console.log(` \u2713 User registered via API: ${username}${firstRun ? " (first-run)" : ""}`);
15836
- res.cookie("aicli_token", token, { httpOnly: true, sameSite: "strict", maxAge: TOKEN_EXPIRY_MS });
16010
+ res.cookie("aicli_token", token, authCookieOptions(req));
15837
16011
  res.json({ success: true, username });
15838
16012
  });
15839
16013
  app.post("/api/auth/login", (req, res) => {
@@ -15847,20 +16021,20 @@ async function startWebServer(options = {}) {
15847
16021
  res.status(401).json({ error: "Invalid username or password" });
15848
16022
  return;
15849
16023
  }
15850
- res.cookie("aicli_token", token, { httpOnly: true, sameSite: "strict", maxAge: TOKEN_EXPIRY_MS });
16024
+ res.cookie("aicli_token", token, authCookieOptions(req));
15851
16025
  res.json({ success: true, username });
15852
16026
  });
15853
- app.post("/api/auth/logout", (_req, res) => {
15854
- res.clearCookie("aicli_token", { httpOnly: true, sameSite: "strict" });
16027
+ app.post("/api/auth/logout", (req, res) => {
16028
+ res.clearCookie("aicli_token", clearAuthCookieOptions(req));
15855
16029
  res.json({ success: true });
15856
16030
  });
15857
16031
  app.get("/api/files", requireAuth, (req, res) => {
15858
16032
  const cwd = process.cwd();
15859
16033
  const prefix = req.query.prefix || "";
15860
- const targetDir = join17(cwd, prefix);
16034
+ const targetDir = join19(cwd, prefix);
15861
16035
  try {
15862
- const canonicalTarget = realpathSync(resolve6(targetDir));
15863
- const canonicalCwd = realpathSync(resolve6(cwd));
16036
+ const canonicalTarget = realpathSync(resolve7(targetDir));
16037
+ const canonicalCwd = realpathSync(resolve7(cwd));
15864
16038
  if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
15865
16039
  res.json({ files: [] });
15866
16040
  return;
@@ -15871,10 +16045,10 @@ async function startWebServer(options = {}) {
15871
16045
  }
15872
16046
  try {
15873
16047
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
15874
- const entries = readdirSync11(targetDir, { withFileTypes: true });
16048
+ const entries = readdirSync12(targetDir, { withFileTypes: true });
15875
16049
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
15876
16050
  name: e.name,
15877
- path: relative3(cwd, join17(targetDir, e.name)).replace(/\\/g, "/"),
16051
+ path: relative4(cwd, join19(targetDir, e.name)).replace(/\\/g, "/"),
15878
16052
  isDir: e.isDirectory()
15879
16053
  }));
15880
16054
  res.json({ files });
@@ -15910,8 +16084,8 @@ async function startWebServer(options = {}) {
15910
16084
  try {
15911
16085
  const authUser = req._authUser;
15912
16086
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
15913
- const filePath = join17(histDir, `${id}.json`);
15914
- if (!existsSync23(filePath)) {
16087
+ const filePath = join19(histDir, `${id}.json`);
16088
+ if (!existsSync25(filePath)) {
15915
16089
  res.status(404).json({ error: "Session not found" });
15916
16090
  return;
15917
16091
  }
@@ -15926,7 +16100,7 @@ async function startWebServer(options = {}) {
15926
16100
  res.status(404).json({ error: "Session not found" });
15927
16101
  return;
15928
16102
  }
15929
- const data = JSON.parse(readFileSync16(filePath, "utf-8"));
16103
+ const data = JSON.parse(readFileSync19(filePath, "utf-8"));
15930
16104
  res.json({ session: data });
15931
16105
  } catch (err) {
15932
16106
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -15939,10 +16113,10 @@ async function startWebServer(options = {}) {
15939
16113
  return;
15940
16114
  }
15941
16115
  const cwd = process.cwd();
15942
- const fullPath = resolve6(join17(cwd, filePath));
16116
+ const fullPath = resolve7(join19(cwd, filePath));
15943
16117
  try {
15944
16118
  const canonicalFull = realpathSync(fullPath);
15945
- const canonicalCwd = realpathSync(resolve6(cwd));
16119
+ const canonicalCwd = realpathSync(resolve7(cwd));
15946
16120
  if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
15947
16121
  res.json({ error: "Access denied" });
15948
16122
  return;
@@ -15952,12 +16126,12 @@ async function startWebServer(options = {}) {
15952
16126
  return;
15953
16127
  }
15954
16128
  try {
15955
- const stat = statSync9(fullPath);
16129
+ const stat = statSync10(fullPath);
15956
16130
  if (stat.size > 512 * 1024) {
15957
16131
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
15958
16132
  return;
15959
16133
  }
15960
- const content = readFileSync16(fullPath, "utf-8");
16134
+ const content = readFileSync19(fullPath, "utf-8");
15961
16135
  res.json({ content, size: stat.size });
15962
16136
  } catch {
15963
16137
  res.json({ error: "Cannot read file" });
@@ -16161,7 +16335,7 @@ async function startWebServer(options = {}) {
16161
16335
  });
16162
16336
  const MAX_PORT_ATTEMPTS = 10;
16163
16337
  let actualPort = port;
16164
- const result = await new Promise((resolve7, reject) => {
16338
+ const result = await new Promise((resolve8, reject) => {
16165
16339
  const tryListen = (attempt) => {
16166
16340
  server.once("error", (err) => {
16167
16341
  if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
@@ -16192,7 +16366,7 @@ async function startWebServer(options = {}) {
16192
16366
  }
16193
16367
  console.log(` Press Ctrl+C to stop
16194
16368
  `);
16195
- resolve7({ port: actualPort, host, url });
16369
+ resolve8({ port: actualPort, host, url });
16196
16370
  });
16197
16371
  };
16198
16372
  tryListen(1);
@@ -16212,22 +16386,43 @@ function resolveProjectMcpPath() {
16212
16386
  const cwd = process.cwd();
16213
16387
  const gitRoot = getGitRoot(cwd);
16214
16388
  const projectRoot = gitRoot ?? cwd;
16215
- const configPath = join17(projectRoot, MCP_PROJECT_CONFIG_NAME);
16216
- return existsSync23(configPath) ? configPath : null;
16389
+ const configPath = join19(projectRoot, MCP_PROJECT_CONFIG_NAME);
16390
+ return existsSync25(configPath) ? configPath : null;
16217
16391
  }
16218
16392
  function loadProjectMcpConfig() {
16219
16393
  const cwd = process.cwd();
16220
16394
  const gitRoot = getGitRoot(cwd);
16221
16395
  const projectRoot = gitRoot ?? cwd;
16222
- const configPath = join17(projectRoot, MCP_PROJECT_CONFIG_NAME);
16223
- if (!existsSync23(configPath)) return null;
16396
+ const configPath = join19(projectRoot, MCP_PROJECT_CONFIG_NAME);
16397
+ if (!existsSync25(configPath)) return null;
16224
16398
  try {
16225
- const raw = JSON.parse(readFileSync16(configPath, "utf-8"));
16399
+ const raw = JSON.parse(readFileSync19(configPath, "utf-8"));
16226
16400
  return raw.mcpServers ?? raw;
16227
16401
  } catch {
16228
16402
  return null;
16229
16403
  }
16230
16404
  }
16405
+ function isHttpsRequest(req) {
16406
+ if (req.secure) return true;
16407
+ const forwardedProto = req.headers["x-forwarded-proto"];
16408
+ const proto = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
16409
+ return proto?.split(",")[0]?.trim().toLowerCase() === "https";
16410
+ }
16411
+ function authCookieOptions(req) {
16412
+ return {
16413
+ httpOnly: true,
16414
+ sameSite: "strict",
16415
+ maxAge: TOKEN_EXPIRY_MS,
16416
+ secure: isHttpsRequest(req)
16417
+ };
16418
+ }
16419
+ function clearAuthCookieOptions(req) {
16420
+ return {
16421
+ httpOnly: true,
16422
+ sameSite: "strict",
16423
+ secure: isHttpsRequest(req)
16424
+ };
16425
+ }
16231
16426
  function parseCookie(cookie) {
16232
16427
  const result = {};
16233
16428
  for (const part of cookie.split(";")) {