jinzd-ai-cli 0.4.209 → 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.
@@ -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-EYID2AIR.js";
40
+ } from "./chunk-HZQVX7VF.js";
40
41
  import {
41
42
  hasSemanticIndex,
42
43
  semanticSearch
@@ -59,8 +60,8 @@ 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 readFileSync17, 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
@@ -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 环境变量覆盖
@@ -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();
@@ -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`
@@ -8732,7 +8738,7 @@ function promptUser(rl, question) {
8732
8738
  console.log();
8733
8739
  console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
8734
8740
  process.stdout.write(chalk4.cyan("> "));
8735
- return new Promise((resolve7) => {
8741
+ return new Promise((resolve8) => {
8736
8742
  let completed = false;
8737
8743
  const cleanup = (answer) => {
8738
8744
  if (completed) return;
@@ -8742,7 +8748,7 @@ function promptUser(rl, question) {
8742
8748
  rl.pause();
8743
8749
  rlAny.output = savedOutput;
8744
8750
  askUserContext.prompting = false;
8745
- resolve7(answer);
8751
+ resolve8(answer);
8746
8752
  };
8747
8753
  const onLine = (line) => {
8748
8754
  cleanup(line);
@@ -10581,7 +10587,7 @@ var McpClient = class {
10581
10587
  // 内部方法:JSON-RPC 通信
10582
10588
  // ══════════════════════════════════════════════════════════════════
10583
10589
  sendRequest(method, params) {
10584
- return new Promise((resolve7, reject) => {
10590
+ return new Promise((resolve8, reject) => {
10585
10591
  if (!this.process?.stdin?.writable) {
10586
10592
  return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
10587
10593
  }
@@ -10605,7 +10611,7 @@ var McpClient = class {
10605
10611
  this.pendingRequests.set(id, {
10606
10612
  resolve: (result) => {
10607
10613
  cleanup();
10608
- resolve7(result);
10614
+ resolve8(result);
10609
10615
  },
10610
10616
  reject: (error) => {
10611
10617
  cleanup();
@@ -10682,13 +10688,13 @@ var McpClient = class {
10682
10688
  }
10683
10689
  /** Promise 超时包装 */
10684
10690
  withTimeout(promise, ms, label) {
10685
- return new Promise((resolve7, reject) => {
10691
+ return new Promise((resolve8, reject) => {
10686
10692
  const timer = setTimeout(() => {
10687
10693
  reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
10688
10694
  }, ms);
10689
10695
  promise.then((val) => {
10690
10696
  clearTimeout(timer);
10691
- resolve7(val);
10697
+ resolve8(val);
10692
10698
  }).catch((err) => {
10693
10699
  clearTimeout(timer);
10694
10700
  reject(err);
@@ -11176,33 +11182,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11176
11182
  }
11177
11183
  /** Resolve a pending confirm from client response */
11178
11184
  resolveConfirm(requestId, approved) {
11179
- const resolve7 = this.pendingConfirms.get(requestId);
11180
- if (resolve7) {
11185
+ const resolve8 = this.pendingConfirms.get(requestId);
11186
+ if (resolve8) {
11181
11187
  this.clearPendingTimer(requestId);
11182
11188
  this.pendingConfirms.delete(requestId);
11183
11189
  this.confirming = false;
11184
- resolve7(approved);
11190
+ resolve8(approved);
11185
11191
  }
11186
11192
  }
11187
11193
  /** Resolve a pending batch confirm from client response */
11188
11194
  resolveBatchConfirm(requestId, decision) {
11189
- const resolve7 = this.pendingBatchConfirms.get(requestId);
11190
- if (resolve7) {
11195
+ const resolve8 = this.pendingBatchConfirms.get(requestId);
11196
+ if (resolve8) {
11191
11197
  this.clearPendingTimer(requestId);
11192
11198
  this.pendingBatchConfirms.delete(requestId);
11193
11199
  this.confirming = false;
11194
11200
  if (decision === "all" || decision === "none") {
11195
- resolve7(decision);
11201
+ resolve8(decision);
11196
11202
  } else {
11197
- resolve7(new Set(decision));
11203
+ resolve8(new Set(decision));
11198
11204
  }
11199
11205
  }
11200
11206
  }
11201
11207
  /** Cancel all pending confirms (e.g., on disconnect) */
11202
11208
  cancelAll() {
11203
- for (const resolve7 of this.pendingConfirms.values()) resolve7(false);
11209
+ for (const resolve8 of this.pendingConfirms.values()) resolve8(false);
11204
11210
  this.pendingConfirms.clear();
11205
- for (const resolve7 of this.pendingBatchConfirms.values()) resolve7("none");
11211
+ for (const resolve8 of this.pendingBatchConfirms.values()) resolve8("none");
11206
11212
  this.pendingBatchConfirms.clear();
11207
11213
  this.confirming = false;
11208
11214
  }
@@ -11278,8 +11284,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11278
11284
  diff: this.getDiffPreview(call)
11279
11285
  };
11280
11286
  this.send(msg);
11281
- return new Promise((resolve7) => {
11282
- this.pendingConfirms.set(requestId, resolve7);
11287
+ return new Promise((resolve8) => {
11288
+ this.pendingConfirms.set(requestId, resolve8);
11283
11289
  this.pendingTimers.set(requestId, setTimeout(() => {
11284
11290
  if (this.pendingConfirms.has(requestId)) {
11285
11291
  this.resolveConfirm(requestId, false);
@@ -11303,8 +11309,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11303
11309
  files
11304
11310
  };
11305
11311
  this.send(msg);
11306
- return new Promise((resolve7) => {
11307
- this.pendingBatchConfirms.set(requestId, resolve7);
11312
+ return new Promise((resolve8) => {
11313
+ this.pendingBatchConfirms.set(requestId, resolve8);
11308
11314
  this.pendingTimers.set(requestId, setTimeout(() => {
11309
11315
  if (this.pendingBatchConfirms.has(requestId)) {
11310
11316
  this.resolveBatchConfirm(requestId, "none");
@@ -11729,14 +11735,171 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
11729
11735
  return committedSize < originalSize;
11730
11736
  }
11731
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
+
11732
11895
  // src/web/session-handler.ts
11733
- import { existsSync as existsSync21, readFileSync as readFileSync15, writeFileSync as writeFileSync2, mkdirSync as mkdirSync10, readdirSync as readdirSync9, statSync as statSync8, createWriteStream } from "fs";
11734
- import { join as join15, resolve as resolve5, dirname as dirname5 } from "path";
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,253 @@ 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
+
11826
12236
  // src/core/git-diff.ts
11827
12237
  import { execFileSync as execFileSync3 } from "child_process";
11828
12238
  function readGitDiff(options = {}) {
@@ -12406,7 +12816,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
12406
12816
  }
12407
12817
 
12408
12818
  // src/hub/persist.ts
12409
- import { join as join14 } from "path";
12819
+ import { join as join16 } from "path";
12410
12820
  function discussionToMessages(state2) {
12411
12821
  const out = [];
12412
12822
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -12444,7 +12854,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
12444
12854
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
12445
12855
  session.titleAiGenerated = true;
12446
12856
  await sm.save();
12447
- return { id: session.id, path: join14(config.getHistoryDir(), `${session.id}.json`) };
12857
+ return { id: session.id, path: join16(config.getHistoryDir(), `${session.id}.json`) };
12448
12858
  }
12449
12859
 
12450
12860
  // src/web/session-handler.ts
@@ -12462,7 +12872,7 @@ function lastAssistantText(messages) {
12462
12872
  }
12463
12873
  return void 0;
12464
12874
  }
12465
- var SessionHandler = class _SessionHandler {
12875
+ var SessionHandler = class {
12466
12876
  ws;
12467
12877
  config;
12468
12878
  providers;
@@ -12499,6 +12909,7 @@ var SessionHandler = class _SessionHandler {
12499
12909
  pendingAutoPause = /* @__PURE__ */ new Map();
12500
12910
  /** Active system prompt from context files */
12501
12911
  activeSystemPrompt;
12912
+ contextLoadResult = null;
12502
12913
  /** Directories added via /add-dir */
12503
12914
  addedDirs = /* @__PURE__ */ new Set();
12504
12915
  /** Track MCP tool names used across this session (for MCP budget prioritization) */
@@ -12605,10 +13016,10 @@ var SessionHandler = class _SessionHandler {
12605
13016
  return;
12606
13017
  }
12607
13018
  case "ask_user_response": {
12608
- const resolve7 = this.pendingAskUser.get(msg.requestId);
12609
- if (resolve7) {
13019
+ const resolve8 = this.pendingAskUser.get(msg.requestId);
13020
+ if (resolve8) {
12610
13021
  this.pendingAskUser.delete(msg.requestId);
12611
- resolve7(msg.answer);
13022
+ resolve8(msg.answer);
12612
13023
  }
12613
13024
  return;
12614
13025
  }
@@ -12619,10 +13030,10 @@ var SessionHandler = class _SessionHandler {
12619
13030
  case "memory_rebuild":
12620
13031
  return this.handleMemoryRebuild(Boolean(msg.full));
12621
13032
  case "auto_pause_response": {
12622
- const resolve7 = this.pendingAutoPause.get(msg.requestId);
12623
- if (resolve7) {
13033
+ const resolve8 = this.pendingAutoPause.get(msg.requestId);
13034
+ if (resolve8) {
12624
13035
  this.pendingAutoPause.delete(msg.requestId);
12625
- resolve7({ action: msg.action, message: msg.message });
13036
+ resolve8({ action: msg.action, message: msg.message });
12626
13037
  }
12627
13038
  return;
12628
13039
  }
@@ -12637,10 +13048,10 @@ var SessionHandler = class _SessionHandler {
12637
13048
  this.hubOrchestrator?.abort();
12638
13049
  return;
12639
13050
  case "hub_steer": {
12640
- const resolve7 = this.pendingHubReview.get(msg.requestId);
12641
- if (resolve7) {
13051
+ const resolve8 = this.pendingHubReview.get(msg.requestId);
13052
+ if (resolve8) {
12642
13053
  this.pendingHubReview.delete(msg.requestId);
12643
- resolve7({ action: msg.action, message: msg.message });
13054
+ resolve8({ action: msg.action, message: msg.message });
12644
13055
  }
12645
13056
  return;
12646
13057
  }
@@ -12652,9 +13063,9 @@ var SessionHandler = class _SessionHandler {
12652
13063
  onDisconnect() {
12653
13064
  this.toolExecutor.cancelAll();
12654
13065
  if (this.abortController) this.abortController.abort();
12655
- for (const resolve7 of this.pendingAskUser.values()) resolve7(null);
13066
+ for (const resolve8 of this.pendingAskUser.values()) resolve8(null);
12656
13067
  this.pendingAskUser.clear();
12657
- for (const resolve7 of this.pendingAutoPause.values()) resolve7({ action: "stop" });
13068
+ for (const resolve8 of this.pendingAutoPause.values()) resolve8({ action: "stop" });
12658
13069
  this.pendingAutoPause.clear();
12659
13070
  this.saveIfNeeded();
12660
13071
  }
@@ -12788,9 +13199,9 @@ var SessionHandler = class _SessionHandler {
12788
13199
  this.hubOrchestrator = orchestrator;
12789
13200
  orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
12790
13201
  if (config.humanSteer) {
12791
- orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve7) => {
13202
+ orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve8) => {
12792
13203
  const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
12793
- this.pendingHubReview.set(requestId, resolve7);
13204
+ this.pendingHubReview.set(requestId, resolve8);
12794
13205
  this.send({ type: "hub_review", requestId, round, maxRounds });
12795
13206
  });
12796
13207
  }
@@ -13127,8 +13538,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
13127
13538
  onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
13128
13539
  requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
13129
13540
  const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
13130
- const pauseResp = await new Promise((resolve7) => {
13131
- this.pendingAutoPause.set(requestId, resolve7);
13541
+ const pauseResp = await new Promise((resolve8) => {
13542
+ this.pendingAutoPause.set(requestId, resolve8);
13132
13543
  this.send({
13133
13544
  type: "auto_pause_request",
13134
13545
  requestId,
@@ -13307,8 +13718,8 @@ ${summaryContent}`,
13307
13718
  }
13308
13719
  if (chunk.done) break;
13309
13720
  }
13310
- await new Promise((resolve7, reject) => {
13311
- fileStream.end((err) => err ? reject(err) : resolve7());
13721
+ await new Promise((resolve8, reject) => {
13722
+ fileStream.end((err) => err ? reject(err) : resolve8());
13312
13723
  });
13313
13724
  const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
13314
13725
  if (verdict.kind === "reject") {
@@ -13334,7 +13745,7 @@ ${summaryContent}`,
13334
13745
  } catch (err) {
13335
13746
  if (fileStream) {
13336
13747
  try {
13337
- await new Promise((resolve7) => fileStream.end(() => resolve7()));
13748
+ await new Promise((resolve8) => fileStream.end(() => resolve8()));
13338
13749
  } catch {
13339
13750
  }
13340
13751
  }
@@ -13623,7 +14034,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
13623
14034
  " /plan [enter|exit] \u2014 Toggle read-only planning mode",
13624
14035
  " /session new|list|load|delete <id> \u2014 Session management",
13625
14036
  " /system [prompt|clear] \u2014 Set/view/reset system prompt",
13626
- " /context [reload] \u2014 Show/reload context layers",
14037
+ " /context [status|reload] \u2014 Show/reload context layers",
13627
14038
  " /status \u2014 Show session info & token usage",
13628
14039
  " /cost \u2014 Show cumulative token usage",
13629
14040
  " /config [show|get|set] \u2014 View/modify configuration",
@@ -13864,9 +14275,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
13864
14275
  let modifiedFiles = 0;
13865
14276
  const diffLines2 = [];
13866
14277
  for (const [filePath, { earliest }] of fileMap) {
13867
- const currentContent = existsSync21(filePath) ? (() => {
14278
+ const currentContent = existsSync23(filePath) ? (() => {
13868
14279
  try {
13869
- return readFileSync15(filePath, "utf-8");
14280
+ return readFileSync17(filePath, "utf-8");
13870
14281
  } catch {
13871
14282
  return null;
13872
14283
  }
@@ -13967,7 +14378,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
13967
14378
  break;
13968
14379
  }
13969
14380
  const sub = args[0]?.toLowerCase();
13970
- const resolve7 = (ref) => {
14381
+ const resolve8 = (ref) => {
13971
14382
  const r = session.resolveBranchRef(ref);
13972
14383
  if (r.ok) return r.id;
13973
14384
  if (r.reason === "ambiguous") {
@@ -14021,7 +14432,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14021
14432
  this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
14022
14433
  break;
14023
14434
  }
14024
- const id = resolve7(ref);
14435
+ const id = resolve8(ref);
14025
14436
  if (!id) break;
14026
14437
  const ok = session.switchBranch(id);
14027
14438
  if (ok) {
@@ -14041,7 +14452,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14041
14452
  this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
14042
14453
  break;
14043
14454
  }
14044
- const id = resolve7(ref);
14455
+ const id = resolve8(ref);
14045
14456
  if (!id) break;
14046
14457
  const ok = session.deleteBranch(id);
14047
14458
  if (ok) {
@@ -14060,7 +14471,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14060
14471
  this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
14061
14472
  break;
14062
14473
  }
14063
- const id = resolve7(ref);
14474
+ const id = resolve8(ref);
14064
14475
  if (!id) break;
14065
14476
  const ok = session.renameBranch(id, title);
14066
14477
  if (ok) {
@@ -14078,7 +14489,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14078
14489
  this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
14079
14490
  break;
14080
14491
  }
14081
- const id = resolve7(ref);
14492
+ const id = resolve8(ref);
14082
14493
  if (!id) break;
14083
14494
  const d = session.diffBranches(id);
14084
14495
  if (!d) {
@@ -14120,7 +14531,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14120
14531
  this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
14121
14532
  break;
14122
14533
  }
14123
- const id = resolve7(ref);
14534
+ const id = resolve8(ref);
14124
14535
  if (!id) break;
14125
14536
  const idx = parseInt(idxStr, 10);
14126
14537
  if (Number.isNaN(idx)) {
@@ -14283,7 +14694,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14283
14694
  diff = head + "\n\n... [diff truncated, " + diff.length + " chars total] ...\n\n" + tail;
14284
14695
  truncated = true;
14285
14696
  }
14286
- const reviewPrompt = this.buildReviewPrompt(diff, formatGitContextForPrompt(gitCtx), detailed);
14697
+ const reviewPrompt = buildReviewPrompt(diff, formatGitContextForPrompt(gitCtx), detailed);
14287
14698
  this.send({ type: "info", message: "\u{1F50D} Analyzing changes..." });
14288
14699
  try {
14289
14700
  const review = await this.chatOnce(reviewPrompt, { temperature: 0.3, maxTokens: 8192 });
@@ -14321,7 +14732,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14321
14732
  secDiff = head + "\n\n... [diff truncated, " + secDiff.length + " chars total] ...\n\n" + tail;
14322
14733
  secTruncated = true;
14323
14734
  }
14324
- const secPrompt = this.buildSecurityReviewPrompt(secDiff, formatGitContextForPrompt(gitCtx));
14735
+ const secPrompt = buildSecurityReviewPrompt(secDiff, formatGitContextForPrompt(gitCtx));
14325
14736
  this.send({ type: "info", message: "\u{1F512} Scanning for security vulnerabilities..." });
14326
14737
  try {
14327
14738
  const secReview = await this.chatOnce(secPrompt, { temperature: 0.2, maxTokens: 8192 });
@@ -14382,7 +14793,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14382
14793
  case "test": {
14383
14794
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
14384
14795
  try {
14385
- const { executeTests } = await import("./run-tests-UAS5NJBM.js");
14796
+ const { executeTests } = await import("./run-tests-NT2UIUVB.js");
14386
14797
  const argStr = args.join(" ").trim();
14387
14798
  let testArgs = {};
14388
14799
  if (argStr) {
@@ -14399,17 +14810,17 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14399
14810
  // ── /init ───────────────────────────────────────────────────────
14400
14811
  case "init": {
14401
14812
  const cwd = process.cwd();
14402
- const targetPath = join15(cwd, "AICLI.md");
14813
+ const targetPath = join17(cwd, "AICLI.md");
14403
14814
  const force = args.includes("--force");
14404
- if (existsSync21(targetPath) && !force) {
14815
+ if (existsSync23(targetPath) && !force) {
14405
14816
  this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
14406
14817
  Use /init --force to overwrite.` });
14407
14818
  break;
14408
14819
  }
14409
14820
  this.send({ type: "info", message: "\u{1F4DD} Scanning project structure..." });
14410
14821
  try {
14411
- const projectInfo = this.scanProject(cwd);
14412
- const prompt = this.buildInitPrompt(projectInfo, cwd);
14822
+ const projectInfo = scanProject(cwd);
14823
+ const prompt = buildInitPrompt(projectInfo, cwd);
14413
14824
  const content = await this.chatOnce(prompt, { temperature: 0.3, maxTokens: 4096 });
14414
14825
  writeFileSync2(targetPath, content, "utf-8");
14415
14826
  this.send({ type: "info", message: `\u2713 Generated: ${targetPath} (${content.length} chars)
@@ -14434,19 +14845,19 @@ Use /context reload to load it.` });
14434
14845
  lines.push("**Config Files:**");
14435
14846
  lines.push(` Dir: ${configDir}`);
14436
14847
  const checkFile = (label, filePath) => {
14437
- const exists = existsSync21(filePath);
14848
+ const exists = existsSync23(filePath);
14438
14849
  let extra = "";
14439
14850
  if (exists) {
14440
14851
  try {
14441
- extra = ` (${statSync8(filePath).size} bytes)`;
14852
+ extra = ` (${statSync9(filePath).size} bytes)`;
14442
14853
  } catch {
14443
14854
  }
14444
14855
  }
14445
14856
  lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
14446
14857
  };
14447
- checkFile("config.json", join15(configDir, "config.json"));
14448
- checkFile("memory.md", join15(configDir, MEMORY_FILE_NAME));
14449
- 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"));
14450
14861
  lines.push("");
14451
14862
  if (this.mcpManager) {
14452
14863
  lines.push("**MCP Servers:**");
@@ -14546,29 +14957,24 @@ ${this.config.toFormattedJSON()}
14546
14957
  this.send({ type: "info", message: this.activeSystemPrompt ? `\u2713 Context reloaded (${this.activeSystemPrompt.length} chars).` : "\u2713 No context files found." });
14547
14958
  break;
14548
14959
  }
14549
- const configDir = this.config.getConfigDir();
14550
- const cwd = process.cwd();
14551
- const gitRoot = getGitRoot(cwd);
14552
- const projectRoot = gitRoot ?? cwd;
14960
+ const result = this.contextLoadResult;
14553
14961
  const layers = ["\u{1F4DA} **Context Layers:**", ""];
14554
- const checkLayer = (label, dir) => {
14555
- for (const name2 of CONTEXT_FILE_CANDIDATES) {
14556
- const fullPath = join15(dir, name2);
14557
- if (existsSync21(fullPath)) {
14558
- try {
14559
- const size = statSync8(fullPath).size;
14560
- layers.push(` \u2713 ${label}: ${fullPath} (${size} bytes)`);
14561
- return;
14562
- } catch {
14563
- }
14564
- }
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})` : ""}`);
14565
14977
  }
14566
- layers.push(` \u2013 ${label}: (none)`);
14567
- };
14568
- checkLayer("Global", configDir);
14569
- checkLayer("Project", projectRoot);
14570
- if (resolve5(cwd) !== resolve5(projectRoot)) {
14571
- checkLayer("Subdir", cwd);
14572
14978
  }
14573
14979
  layers.push("");
14574
14980
  layers.push(`Total prompt length: ${this.activeSystemPrompt?.length ?? 0} chars`);
@@ -14638,12 +15044,12 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
14638
15044
  this.send({ type: "info", message: "\u2713 All added directories removed from context." });
14639
15045
  break;
14640
15046
  }
14641
- const dirPath = resolve5(sub);
14642
- if (!existsSync21(dirPath)) {
15047
+ const dirPath = resolve6(sub);
15048
+ if (!existsSync23(dirPath)) {
14643
15049
  this.send({ type: "error", message: `Directory not found: ${dirPath}` });
14644
15050
  break;
14645
15051
  }
14646
- if (!statSync8(dirPath).isDirectory()) {
15052
+ if (!statSync9(dirPath).isDirectory()) {
14647
15053
  this.send({ type: "error", message: `Not a directory: ${dirPath}` });
14648
15054
  break;
14649
15055
  }
@@ -14655,14 +15061,14 @@ It will be included in AI context for subsequent messages.` });
14655
15061
  // ── /commands ───────────────────────────────────────────────────
14656
15062
  case "commands": {
14657
15063
  const configDir = this.config.getConfigDir();
14658
- const commandsDir = join15(configDir, CUSTOM_COMMANDS_DIR_NAME);
14659
- if (!existsSync21(commandsDir)) {
15064
+ const commandsDir = join17(configDir, CUSTOM_COMMANDS_DIR_NAME);
15065
+ if (!existsSync23(commandsDir)) {
14660
15066
  this.send({ type: "info", message: `No custom commands directory.
14661
15067
  Create: ${commandsDir}/ with .md files.` });
14662
15068
  break;
14663
15069
  }
14664
15070
  try {
14665
- const files = readdirSync9(commandsDir).filter((f) => f.endsWith(".md"));
15071
+ const files = readdirSync10(commandsDir).filter((f) => f.endsWith(".md"));
14666
15072
  if (files.length === 0) {
14667
15073
  this.send({ type: "info", message: `No custom commands found in ${commandsDir}
14668
15074
  Add .md files to create commands.` });
@@ -14682,7 +15088,7 @@ Add .md files to create commands.` });
14682
15088
  // ── /plugins ────────────────────────────────────────────────────
14683
15089
  case "plugins": {
14684
15090
  const configDir = this.config.getConfigDir();
14685
- const pluginsDir = join15(configDir, PLUGINS_DIR_NAME);
15091
+ const pluginsDir = join17(configDir, PLUGINS_DIR_NAME);
14686
15092
  const pluginTools = this.toolRegistry.listPluginTools();
14687
15093
  const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
14688
15094
  if (pluginTools.length === 0) {
@@ -14856,11 +15262,11 @@ Add .md files to create commands.` });
14856
15262
  }
14857
15263
  memoryShow() {
14858
15264
  const configDir = this.config.getConfigDir();
14859
- const memPath = join15(configDir, MEMORY_FILE_NAME);
15265
+ const memPath = join17(configDir, MEMORY_FILE_NAME);
14860
15266
  let content = "";
14861
15267
  try {
14862
- if (existsSync21(memPath)) {
14863
- content = readFileSync15(memPath, "utf-8");
15268
+ if (existsSync23(memPath)) {
15269
+ content = readFileSync17(memPath, "utf-8");
14864
15270
  }
14865
15271
  } catch (err) {
14866
15272
  process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
@@ -14874,11 +15280,11 @@ Add .md files to create commands.` });
14874
15280
  }
14875
15281
  memoryAdd(text) {
14876
15282
  const configDir = this.config.getConfigDir();
14877
- const memPath = join15(configDir, MEMORY_FILE_NAME);
15283
+ const memPath = join17(configDir, MEMORY_FILE_NAME);
14878
15284
  try {
14879
15285
  mkdirSync10(configDir, { recursive: true });
14880
15286
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
14881
- const previous = existsSync21(memPath) ? readFileSync15(memPath, "utf-8") : "";
15287
+ const previous = existsSync23(memPath) ? readFileSync17(memPath, "utf-8") : "";
14882
15288
  const entry = `
14883
15289
  - [${timestamp}] ${text}
14884
15290
  `;
@@ -14890,7 +15296,7 @@ Add .md files to create commands.` });
14890
15296
  }
14891
15297
  memoryClear() {
14892
15298
  const configDir = this.config.getConfigDir();
14893
- const memPath = join15(configDir, MEMORY_FILE_NAME);
15299
+ const memPath = join17(configDir, MEMORY_FILE_NAME);
14894
15300
  try {
14895
15301
  atomicWriteFileSync(memPath, "");
14896
15302
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
@@ -15055,7 +15461,7 @@ Add .md files to create commands.` });
15055
15461
  dirContext += `
15056
15462
  [Directory: ${dir}]
15057
15463
  `;
15058
- dirContext += this.scanDirTree(dir, 2, 40) + "\n";
15464
+ dirContext += scanDirTree(dir, 2, 40) + "\n";
15059
15465
  totalLen = dirContext.length;
15060
15466
  }
15061
15467
  stable += dirContext;
@@ -15094,265 +15500,21 @@ Add .md files to create commands.` });
15094
15500
  }
15095
15501
  return { toolDefs: this.toolRegistry.getDefinitions(), mcpBudgetNote: null };
15096
15502
  }
15097
- /**
15098
- * Find first matching context file in a directory.
15099
- */
15100
- findContextFile(dir) {
15101
- for (const name of CONTEXT_FILE_CANDIDATES) {
15102
- const fullPath = join15(dir, name);
15103
- try {
15104
- if (existsSync21(fullPath)) {
15105
- const content = readFileSync15(fullPath, "utf-8").trim();
15106
- if (content) return content;
15107
- }
15108
- } catch {
15109
- }
15110
- }
15111
- return null;
15112
- }
15113
- /**
15114
- * Load hierarchical context files (same as CLI):
15115
- * 1. Global: ~/.aicli/AICLI.md or CLAUDE.md
15116
- * 2. Project: <git-root>/AICLI.md or CLAUDE.md
15117
- * 3. Subdir: <cwd>/AICLI.md or CLAUDE.md (only if cwd ≠ project root)
15118
- */
15119
- // ── /init helpers ─────────────────────────────────────────────────
15120
- static SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
15121
- "node_modules",
15122
- ".git",
15123
- "dist",
15124
- "build",
15125
- "out",
15126
- "target",
15127
- ".next",
15128
- ".nuxt",
15129
- "__pycache__",
15130
- ".venv",
15131
- "venv",
15132
- ".tox",
15133
- ".mypy_cache",
15134
- ".pytest_cache",
15135
- ".gradle",
15136
- ".idea",
15137
- ".vscode",
15138
- ".vs",
15139
- "coverage",
15140
- ".cache",
15141
- ".parcel-cache",
15142
- "dist-cjs",
15143
- "release",
15144
- ".output",
15145
- ".turbo",
15146
- "vendor"
15147
- ]);
15148
- scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
15149
- const lines = [];
15150
- let count = 0;
15151
- const walk = (d, prefix, depth) => {
15152
- if (depth > maxDepth || count >= maxEntries) return;
15153
- let entries;
15154
- try {
15155
- entries = readdirSync9(d);
15156
- } catch {
15157
- return;
15158
- }
15159
- const filtered = entries.filter((e) => !e.startsWith(".") && !_SessionHandler.SCAN_SKIP_DIRS.has(e));
15160
- const sorted = filtered.sort((a, b) => {
15161
- let aIsDir = false, bIsDir = false;
15162
- try {
15163
- aIsDir = statSync8(join15(d, a)).isDirectory();
15164
- } catch {
15165
- }
15166
- try {
15167
- bIsDir = statSync8(join15(d, b)).isDirectory();
15168
- } catch {
15169
- }
15170
- if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
15171
- return a.localeCompare(b);
15172
- });
15173
- for (let i = 0; i < sorted.length && count < maxEntries; i++) {
15174
- const name = sorted[i];
15175
- const fullPath = join15(d, name);
15176
- const isLast = i === sorted.length - 1;
15177
- let isDir;
15178
- try {
15179
- isDir = statSync8(fullPath).isDirectory();
15180
- } catch {
15181
- continue;
15182
- }
15183
- lines.push(prefix + (isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ") + name + (isDir ? "/" : ""));
15184
- count++;
15185
- if (isDir) walk(fullPath, prefix + (isLast ? " " : "\u2502 "), depth + 1);
15186
- }
15187
- };
15188
- walk(dir, "", 0);
15189
- if (count >= maxEntries) lines.push("... (truncated)");
15190
- return lines.join("\n");
15191
- }
15192
- scanProject(cwd) {
15193
- const info = { type: "unknown", language: "unknown", configFiles: [], directoryStructure: "" };
15194
- const check = (file) => existsSync21(join15(cwd, file));
15195
- const configCandidates = [
15196
- "package.json",
15197
- "tsconfig.json",
15198
- "Cargo.toml",
15199
- "pyproject.toml",
15200
- "setup.py",
15201
- "requirements.txt",
15202
- "go.mod",
15203
- "pom.xml",
15204
- "build.gradle",
15205
- "build.gradle.kts",
15206
- "CMakeLists.txt",
15207
- "Makefile",
15208
- ".csproj",
15209
- ".sln",
15210
- "composer.json",
15211
- "Gemfile",
15212
- "mix.exs",
15213
- "deno.json",
15214
- "bun.lockb"
15215
- ];
15216
- info.configFiles = configCandidates.filter(check);
15217
- if (check("package.json")) {
15218
- info.type = "node";
15219
- info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
15220
- try {
15221
- const pkg = JSON.parse(readFileSync15(join15(cwd, "package.json"), "utf-8"));
15222
- const scripts = pkg.scripts ?? {};
15223
- info.buildCommand = scripts.build ? "npm run build" : void 0;
15224
- info.testCommand = scripts.test ? "npm test" : void 0;
15225
- info.devCommand = scripts.dev ? "npm run dev" : void 0;
15226
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
15227
- if (allDeps["react"]) info.framework = "React";
15228
- else if (allDeps["vue"]) info.framework = "Vue";
15229
- else if (allDeps["@angular/core"]) info.framework = "Angular";
15230
- else if (allDeps["next"]) info.framework = "Next.js";
15231
- else if (allDeps["express"]) info.framework = "Express";
15232
- } catch {
15233
- }
15234
- } else if (check("Cargo.toml")) {
15235
- info.type = "rust";
15236
- info.language = "Rust";
15237
- info.buildCommand = "cargo build";
15238
- info.testCommand = "cargo test";
15239
- } else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
15240
- info.type = "python";
15241
- info.language = "Python";
15242
- info.testCommand = "pytest";
15243
- } else if (check("go.mod")) {
15244
- info.type = "go";
15245
- info.language = "Go";
15246
- info.buildCommand = "go build ./...";
15247
- info.testCommand = "go test ./...";
15248
- } else if (check("pom.xml")) {
15249
- info.type = "java";
15250
- info.language = "Java";
15251
- info.buildCommand = "mvn package";
15252
- info.testCommand = "mvn test";
15253
- } else if (check("build.gradle") || check("build.gradle.kts")) {
15254
- info.type = "java";
15255
- info.language = "Java/Kotlin";
15256
- info.buildCommand = "./gradlew build";
15257
- info.testCommand = "./gradlew test";
15258
- }
15259
- info.directoryStructure = this.scanDirTree(cwd);
15260
- return info;
15261
- }
15262
- buildInitPrompt(info, cwd) {
15263
- const parts = [
15264
- "Please generate an AICLI.md context file (Markdown format) for the following project.",
15265
- "\n## Project Info\n",
15266
- `- Working directory: ${cwd}`,
15267
- `- Type: ${info.type}`,
15268
- `- Language: ${info.language}`
15269
- ];
15270
- if (info.framework) parts.push(`- Framework: ${info.framework}`);
15271
- if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
15272
- if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
15273
- if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
15274
- parts.push(`
15275
- ## Detected Config Files
15276
- ${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
15277
- parts.push(`
15278
- ## Directory Structure
15279
- \`\`\`
15280
- ${info.directoryStructure}
15281
- \`\`\``);
15282
- parts.push(`
15283
- ## Requirements
15284
- Generate a structured Markdown file with: project overview, tech stack, project structure, common commands, code style. Keep it concise, within 200 lines.`);
15285
- return parts.join("\n");
15286
- }
15287
- // ── /review helper ──────────────────────────────────────────────────
15288
- buildReviewPrompt(diff, gitContextStr, detailed) {
15289
- 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.";
15290
- return `# Code Review Request
15291
-
15292
- ${level}
15293
-
15294
- ## Git Status
15295
- ${gitContextStr}
15296
-
15297
- ## Code Changes (diff)
15298
- \`\`\`diff
15299
- ${diff}
15300
- \`\`\`
15301
-
15302
- ## Output Format
15303
- 1. **Overall Assessment**: One-sentence summary
15304
- 2. **Issues** (if any): [Severity] file:line \u2014 description + fix
15305
- 3. **Improvement Suggestions** (if any)
15306
- 4. **Highlights** (if any)
15307
-
15308
- Severity: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F535} Info`;
15309
- }
15310
- buildSecurityReviewPrompt(diff, gitContextStr) {
15311
- return `# Security Vulnerability Review
15312
-
15313
- Analyze the following code changes **exclusively for security vulnerabilities**.
15314
-
15315
- ## Categories
15316
- 1. Injection (SQL, command, path traversal, XSS)
15317
- 2. Auth & Authorization (hardcoded creds, missing checks)
15318
- 3. Secrets & Sensitive Data (API keys, tokens in code)
15319
- 4. Input Validation (missing validation, unsafe deserialization)
15320
- 5. Cryptography (weak algorithms, hardcoded IVs)
15321
- 6. File System (path traversal, symlink attacks)
15322
- 7. Network (SSRF, insecure protocols)
15323
-
15324
- ## Git Status
15325
- ${gitContextStr}
15326
-
15327
- ## Code Changes
15328
- \`\`\`diff
15329
- ${diff}
15330
- \`\`\`
15331
-
15332
- ## Output Format
15333
- For each finding:
15334
- - **Severity**: \u{1F534} CRITICAL / \u{1F7E0} HIGH / \u{1F7E1} MEDIUM / \u{1F535} LOW
15335
- - **Category** + **File:line**
15336
- - **Description** + exploitation scenario
15337
- - **Recommended fix**
15338
-
15339
- If none found: "\u2705 No security vulnerabilities detected"`;
15340
- }
15341
15503
  loadContextFiles() {
15342
- const parts = [];
15504
+ const contextConfig = this.config.get("context");
15343
15505
  const cwd = process.cwd();
15344
- const configDir = this.config.getConfigDir();
15345
- const globalCtx = this.findContextFile(configDir);
15346
- if (globalCtx) parts.push(globalCtx);
15347
15506
  const gitRoot = getGitRoot(cwd);
15348
15507
  const projectRoot = gitRoot ?? cwd;
15349
- const projectCtx = this.findContextFile(projectRoot);
15350
- if (projectCtx) parts.push(projectCtx);
15351
- if (resolve5(cwd) !== resolve5(projectRoot)) {
15352
- const localCtx = this.findContextFile(cwd);
15353
- if (localCtx) parts.push(localCtx);
15354
- }
15355
- 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;
15356
15518
  }
15357
15519
  };
15358
15520
 
@@ -15368,8 +15530,8 @@ async function setupProxy(configProxy) {
15368
15530
  }
15369
15531
 
15370
15532
  // src/web/auth.ts
15371
- import { existsSync as existsSync22, readFileSync as readFileSync16, writeFileSync as writeFileSync3, mkdirSync as mkdirSync11, readdirSync as readdirSync10, copyFileSync } from "fs";
15372
- 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";
15373
15535
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
15374
15536
  var USERS_FILE = "users.json";
15375
15537
  var TOKEN_EXPIRY_HOURS = 24;
@@ -15384,7 +15546,7 @@ var AuthManager = class {
15384
15546
  db;
15385
15547
  constructor(baseDir) {
15386
15548
  this.baseDir = baseDir;
15387
- this.usersFile = join16(baseDir, USERS_FILE);
15549
+ this.usersFile = join18(baseDir, USERS_FILE);
15388
15550
  this.db = this.loadOrCreate();
15389
15551
  }
15390
15552
  // ── Public API ─────────────────────────────────────────────────
@@ -15413,9 +15575,9 @@ var AuthManager = class {
15413
15575
  }
15414
15576
  const salt = randomBytes(16).toString("hex");
15415
15577
  const passwordHash = this.hashPassword(password, salt);
15416
- const dataDir = join16(USERS_DIR, username);
15417
- const fullDataDir = join16(this.baseDir, dataDir);
15418
- 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 });
15419
15581
  const user = {
15420
15582
  username,
15421
15583
  passwordHash,
@@ -15530,7 +15692,7 @@ var AuthManager = class {
15530
15692
  getUserDataDir(username) {
15531
15693
  const user = this.db.users.find((u) => u.username === username);
15532
15694
  if (!user) throw new Error(`User not found: ${username}`);
15533
- return join16(this.baseDir, user.dataDir);
15695
+ return join18(this.baseDir, user.dataDir);
15534
15696
  }
15535
15697
  /** List all usernames */
15536
15698
  listUsers() {
@@ -15566,30 +15728,30 @@ var AuthManager = class {
15566
15728
  const err = this.register(username, password);
15567
15729
  if (err) return err;
15568
15730
  const userDir = this.getUserDataDir(username);
15569
- const globalConfig = join16(this.baseDir, "config.json");
15570
- if (existsSync22(globalConfig)) {
15731
+ const globalConfig = join18(this.baseDir, "config.json");
15732
+ if (existsSync24(globalConfig)) {
15571
15733
  try {
15572
- const content = readFileSync16(globalConfig, "utf-8");
15573
- writeFileSync3(join16(userDir, "config.json"), content, "utf-8");
15734
+ const content = readFileSync18(globalConfig, "utf-8");
15735
+ writeFileSync3(join18(userDir, "config.json"), content, "utf-8");
15574
15736
  } catch {
15575
15737
  }
15576
15738
  }
15577
- const globalMemory = join16(this.baseDir, "memory.md");
15578
- if (existsSync22(globalMemory)) {
15739
+ const globalMemory = join18(this.baseDir, "memory.md");
15740
+ if (existsSync24(globalMemory)) {
15579
15741
  try {
15580
- const content = readFileSync16(globalMemory, "utf-8");
15581
- writeFileSync3(join16(userDir, "memory.md"), content, "utf-8");
15742
+ const content = readFileSync18(globalMemory, "utf-8");
15743
+ writeFileSync3(join18(userDir, "memory.md"), content, "utf-8");
15582
15744
  } catch {
15583
15745
  }
15584
15746
  }
15585
- const globalHistory = join16(this.baseDir, "history");
15586
- if (existsSync22(globalHistory)) {
15747
+ const globalHistory = join18(this.baseDir, "history");
15748
+ if (existsSync24(globalHistory)) {
15587
15749
  try {
15588
- const files = readdirSync10(globalHistory).filter((f) => f.endsWith(".json"));
15589
- const userHistory = join16(userDir, "history");
15750
+ const files = readdirSync11(globalHistory).filter((f) => f.endsWith(".json"));
15751
+ const userHistory = join18(userDir, "history");
15590
15752
  for (const f of files) {
15591
15753
  try {
15592
- copyFileSync(join16(globalHistory, f), join16(userHistory, f));
15754
+ copyFileSync(join18(globalHistory, f), join18(userHistory, f));
15593
15755
  } catch {
15594
15756
  }
15595
15757
  }
@@ -15600,9 +15762,9 @@ var AuthManager = class {
15600
15762
  }
15601
15763
  // ── Private methods ────────────────────────────────────────────
15602
15764
  loadOrCreate() {
15603
- if (existsSync22(this.usersFile)) {
15765
+ if (existsSync24(this.usersFile)) {
15604
15766
  try {
15605
- return JSON.parse(readFileSync16(this.usersFile, "utf-8"));
15767
+ return JSON.parse(readFileSync18(this.usersFile, "utf-8"));
15606
15768
  } catch {
15607
15769
  }
15608
15770
  }
@@ -15654,7 +15816,7 @@ function getModuleDir() {
15654
15816
  }
15655
15817
  } catch {
15656
15818
  }
15657
- return resolve6(".");
15819
+ return resolve7(".");
15658
15820
  }
15659
15821
  async function startWebServer(options = {}) {
15660
15822
  const port = options.port ?? 3e3;
@@ -15719,8 +15881,8 @@ async function startWebServer(options = {}) {
15719
15881
  }
15720
15882
  }
15721
15883
  let skillManager = null;
15722
- const skillsDir = join17(config.getConfigDir(), SKILLS_DIR_NAME);
15723
- if (existsSync23(skillsDir)) {
15884
+ const skillsDir = join19(config.getConfigDir(), SKILLS_DIR_NAME);
15885
+ if (existsSync25(skillsDir)) {
15724
15886
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
15725
15887
  skillManager.loadSkills();
15726
15888
  const count = skillManager.listSkills().length;
@@ -15791,18 +15953,18 @@ async function startWebServer(options = {}) {
15791
15953
  next();
15792
15954
  };
15793
15955
  const moduleDir = getModuleDir();
15794
- let clientDir = join17(moduleDir, "web", "client");
15795
- if (!existsSync23(clientDir)) {
15796
- clientDir = join17(moduleDir, "client");
15956
+ let clientDir = join19(moduleDir, "web", "client");
15957
+ if (!existsSync25(clientDir)) {
15958
+ clientDir = join19(moduleDir, "client");
15797
15959
  }
15798
- if (!existsSync23(clientDir)) {
15799
- clientDir = join17(moduleDir, "..", "..", "src", "web", "client");
15960
+ if (!existsSync25(clientDir)) {
15961
+ clientDir = join19(moduleDir, "..", "..", "src", "web", "client");
15800
15962
  }
15801
- if (!existsSync23(clientDir)) {
15802
- clientDir = join17(process.cwd(), "src", "web", "client");
15963
+ if (!existsSync25(clientDir)) {
15964
+ clientDir = join19(process.cwd(), "src", "web", "client");
15803
15965
  }
15804
15966
  console.log(` Static files: ${clientDir}`);
15805
- app.use("/vendor", express.static(join17(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
15967
+ app.use("/vendor", express.static(join19(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
15806
15968
  app.use(express.static(clientDir));
15807
15969
  app.get("/api/status", (_req, res) => {
15808
15970
  res.json({
@@ -15869,10 +16031,10 @@ async function startWebServer(options = {}) {
15869
16031
  app.get("/api/files", requireAuth, (req, res) => {
15870
16032
  const cwd = process.cwd();
15871
16033
  const prefix = req.query.prefix || "";
15872
- const targetDir = join17(cwd, prefix);
16034
+ const targetDir = join19(cwd, prefix);
15873
16035
  try {
15874
- const canonicalTarget = realpathSync(resolve6(targetDir));
15875
- const canonicalCwd = realpathSync(resolve6(cwd));
16036
+ const canonicalTarget = realpathSync(resolve7(targetDir));
16037
+ const canonicalCwd = realpathSync(resolve7(cwd));
15876
16038
  if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
15877
16039
  res.json({ files: [] });
15878
16040
  return;
@@ -15883,10 +16045,10 @@ async function startWebServer(options = {}) {
15883
16045
  }
15884
16046
  try {
15885
16047
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
15886
- const entries = readdirSync11(targetDir, { withFileTypes: true });
16048
+ const entries = readdirSync12(targetDir, { withFileTypes: true });
15887
16049
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
15888
16050
  name: e.name,
15889
- path: relative3(cwd, join17(targetDir, e.name)).replace(/\\/g, "/"),
16051
+ path: relative4(cwd, join19(targetDir, e.name)).replace(/\\/g, "/"),
15890
16052
  isDir: e.isDirectory()
15891
16053
  }));
15892
16054
  res.json({ files });
@@ -15922,8 +16084,8 @@ async function startWebServer(options = {}) {
15922
16084
  try {
15923
16085
  const authUser = req._authUser;
15924
16086
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
15925
- const filePath = join17(histDir, `${id}.json`);
15926
- if (!existsSync23(filePath)) {
16087
+ const filePath = join19(histDir, `${id}.json`);
16088
+ if (!existsSync25(filePath)) {
15927
16089
  res.status(404).json({ error: "Session not found" });
15928
16090
  return;
15929
16091
  }
@@ -15938,7 +16100,7 @@ async function startWebServer(options = {}) {
15938
16100
  res.status(404).json({ error: "Session not found" });
15939
16101
  return;
15940
16102
  }
15941
- const data = JSON.parse(readFileSync17(filePath, "utf-8"));
16103
+ const data = JSON.parse(readFileSync19(filePath, "utf-8"));
15942
16104
  res.json({ session: data });
15943
16105
  } catch (err) {
15944
16106
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -15951,10 +16113,10 @@ async function startWebServer(options = {}) {
15951
16113
  return;
15952
16114
  }
15953
16115
  const cwd = process.cwd();
15954
- const fullPath = resolve6(join17(cwd, filePath));
16116
+ const fullPath = resolve7(join19(cwd, filePath));
15955
16117
  try {
15956
16118
  const canonicalFull = realpathSync(fullPath);
15957
- const canonicalCwd = realpathSync(resolve6(cwd));
16119
+ const canonicalCwd = realpathSync(resolve7(cwd));
15958
16120
  if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
15959
16121
  res.json({ error: "Access denied" });
15960
16122
  return;
@@ -15964,12 +16126,12 @@ async function startWebServer(options = {}) {
15964
16126
  return;
15965
16127
  }
15966
16128
  try {
15967
- const stat = statSync9(fullPath);
16129
+ const stat = statSync10(fullPath);
15968
16130
  if (stat.size > 512 * 1024) {
15969
16131
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
15970
16132
  return;
15971
16133
  }
15972
- const content = readFileSync17(fullPath, "utf-8");
16134
+ const content = readFileSync19(fullPath, "utf-8");
15973
16135
  res.json({ content, size: stat.size });
15974
16136
  } catch {
15975
16137
  res.json({ error: "Cannot read file" });
@@ -16173,7 +16335,7 @@ async function startWebServer(options = {}) {
16173
16335
  });
16174
16336
  const MAX_PORT_ATTEMPTS = 10;
16175
16337
  let actualPort = port;
16176
- const result = await new Promise((resolve7, reject) => {
16338
+ const result = await new Promise((resolve8, reject) => {
16177
16339
  const tryListen = (attempt) => {
16178
16340
  server.once("error", (err) => {
16179
16341
  if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
@@ -16204,7 +16366,7 @@ async function startWebServer(options = {}) {
16204
16366
  }
16205
16367
  console.log(` Press Ctrl+C to stop
16206
16368
  `);
16207
- resolve7({ port: actualPort, host, url });
16369
+ resolve8({ port: actualPort, host, url });
16208
16370
  });
16209
16371
  };
16210
16372
  tryListen(1);
@@ -16224,17 +16386,17 @@ function resolveProjectMcpPath() {
16224
16386
  const cwd = process.cwd();
16225
16387
  const gitRoot = getGitRoot(cwd);
16226
16388
  const projectRoot = gitRoot ?? cwd;
16227
- const configPath = join17(projectRoot, MCP_PROJECT_CONFIG_NAME);
16228
- return existsSync23(configPath) ? configPath : null;
16389
+ const configPath = join19(projectRoot, MCP_PROJECT_CONFIG_NAME);
16390
+ return existsSync25(configPath) ? configPath : null;
16229
16391
  }
16230
16392
  function loadProjectMcpConfig() {
16231
16393
  const cwd = process.cwd();
16232
16394
  const gitRoot = getGitRoot(cwd);
16233
16395
  const projectRoot = gitRoot ?? cwd;
16234
- const configPath = join17(projectRoot, MCP_PROJECT_CONFIG_NAME);
16235
- if (!existsSync23(configPath)) return null;
16396
+ const configPath = join19(projectRoot, MCP_PROJECT_CONFIG_NAME);
16397
+ if (!existsSync25(configPath)) return null;
16236
16398
  try {
16237
- const raw = JSON.parse(readFileSync17(configPath, "utf-8"));
16399
+ const raw = JSON.parse(readFileSync19(configPath, "utf-8"));
16238
16400
  return raw.mcpServers ?? raw;
16239
16401
  } catch {
16240
16402
  return null;