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.
package/dist/index.js CHANGED
@@ -1,22 +1,25 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- buildReviewPrompt,
4
- buildSecurityReviewPrompt
5
- } from "./chunk-HLWUDRBO.js";
6
2
  import {
7
3
  McpManager,
8
4
  SNAPSHOT_PROMPT,
9
5
  SkillManager,
10
6
  autoTrimSessionIfNeeded,
7
+ buildInitPrompt,
11
8
  clearDevState,
9
+ loadContextFiles,
12
10
  loadDevState,
13
11
  parseSimpleYaml,
14
12
  persistToolRound,
15
13
  readGitDiff,
16
14
  saveDevState,
15
+ scanProject,
17
16
  sessionHasMeaningfulContent,
18
17
  setupProxy
19
- } from "./chunk-ATNYI6JL.js";
18
+ } from "./chunk-7JWT3KXO.js";
19
+ import {
20
+ buildReviewPrompt,
21
+ buildSecurityReviewPrompt
22
+ } from "./chunk-HLWUDRBO.js";
20
23
  import {
21
24
  ToolExecutor,
22
25
  ToolRegistry,
@@ -36,10 +39,10 @@ import {
36
39
  spawnAgentContext,
37
40
  theme,
38
41
  undoStack
39
- } from "./chunk-JVLRMIHA.js";
42
+ } from "./chunk-NEPFADHX.js";
40
43
  import "./chunk-T2NL5ZIA.js";
41
44
  import "./chunk-BXP6YZ2P.js";
42
- import "./chunk-EN63JA7N.js";
45
+ import "./chunk-E2ZWWE6Q.js";
43
46
  import {
44
47
  SessionManager,
45
48
  getContentText
@@ -56,7 +59,7 @@ import {
56
59
  getConfigDirUsage,
57
60
  listRecentCrashes,
58
61
  writeCrashLog
59
- } from "./chunk-D6M4E7SH.js";
62
+ } from "./chunk-NWP7C6CC.js";
60
63
  import {
61
64
  ProviderRegistry
62
65
  } from "./chunk-QMXC327F.js";
@@ -65,7 +68,7 @@ import {
65
68
  getTopFailingTools,
66
69
  getTopUsedTools,
67
70
  installFlushOnExit
68
- } from "./chunk-STIEOOOL.js";
71
+ } from "./chunk-DJ35G5VO.js";
69
72
  import {
70
73
  CONTENT_ONLY_STREAM_REMINDER,
71
74
  TEE_FINAL_USER_NUDGE,
@@ -87,7 +90,7 @@ import {
87
90
  } from "./chunk-XPBEJB27.js";
88
91
  import {
89
92
  ConfigManager
90
- } from "./chunk-DUZRQXIP.js";
93
+ } from "./chunk-P6MTFCXB.js";
91
94
  import {
92
95
  AuthError,
93
96
  ProviderError,
@@ -98,7 +101,6 @@ import {
98
101
  AUTHOR,
99
102
  AUTHOR_EMAIL,
100
103
  CONFIG_DIR_NAME,
101
- CONTEXT_FILE_CANDIDATES,
102
104
  CONTEXT_PRESSURE_THRESHOLD,
103
105
  CUSTOM_COMMANDS_DIR_NAME,
104
106
  DEFAULT_MAX_TOKENS,
@@ -114,7 +116,7 @@ import {
114
116
  SKILLS_DIR_NAME,
115
117
  VERSION,
116
118
  buildUserIdentityPrompt
117
- } from "./chunk-UPMBIS4T.js";
119
+ } from "./chunk-GRJNQJA5.js";
118
120
  import {
119
121
  formatGitContextForPrompt,
120
122
  getGitContext,
@@ -145,8 +147,8 @@ import { program } from "commander";
145
147
 
146
148
  // src/repl/repl.ts
147
149
  import * as readline from "readline";
148
- import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync4, mkdirSync as mkdirSync4 } from "fs";
149
- import { join as join5, resolve as resolve2, extname as extname2, dirname as dirname3, basename as basename2 } from "path";
150
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync3, mkdirSync as mkdirSync4 } from "fs";
151
+ import { join as join4, resolve as resolve2, extname as extname2, dirname as dirname3, basename as basename2 } from "path";
150
152
  import chalk4 from "chalk";
151
153
 
152
154
  // src/session/title-generator.ts
@@ -638,10 +640,10 @@ Error${typeName}: ${lines.join("\n")}
638
640
  };
639
641
 
640
642
  // src/repl/commands/index.ts
641
- import { writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
643
+ import { writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync2, readFileSync, statSync as statSync2 } from "fs";
642
644
  import { execSync as execSync2 } from "child_process";
643
645
  import { platform } from "os";
644
- import { resolve, dirname as dirname2, join as join3, basename } from "path";
646
+ import { resolve, dirname as dirname2, join as join2, basename } from "path";
645
647
  import chalk2 from "chalk";
646
648
 
647
649
  // src/repl/clipboard.ts
@@ -731,195 +733,6 @@ function getClipboardHint() {
731
733
  return "";
732
734
  }
733
735
 
734
- // src/repl/commands/project-init.ts
735
- import { existsSync as existsSync2, readFileSync, readdirSync, statSync as statSync2 } from "fs";
736
- import { join as join2 } from "path";
737
- var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
738
- "node_modules",
739
- ".git",
740
- "dist",
741
- "build",
742
- "out",
743
- "target",
744
- ".next",
745
- ".nuxt",
746
- "__pycache__",
747
- ".venv",
748
- "venv",
749
- ".tox",
750
- ".mypy_cache",
751
- ".pytest_cache",
752
- ".gradle",
753
- ".idea",
754
- ".vscode",
755
- ".vs",
756
- "coverage",
757
- ".cache",
758
- ".parcel-cache",
759
- "dist-cjs",
760
- "release",
761
- ".output",
762
- ".turbo",
763
- "vendor"
764
- ]);
765
- function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
766
- const lines = [];
767
- let count = 0;
768
- const walk = (d, prefix, depth) => {
769
- if (depth > maxDepth || count >= maxEntries) return;
770
- let entries;
771
- try {
772
- entries = readdirSync(d);
773
- } catch {
774
- return;
775
- }
776
- const filtered = entries.filter((e) => !e.startsWith(".") && !SCAN_SKIP_DIRS.has(e));
777
- const sorted = filtered.sort((a, b) => {
778
- let aIsDir = false, bIsDir = false;
779
- try {
780
- aIsDir = statSync2(join2(d, a)).isDirectory();
781
- } catch {
782
- }
783
- try {
784
- bIsDir = statSync2(join2(d, b)).isDirectory();
785
- } catch {
786
- }
787
- if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
788
- return a.localeCompare(b);
789
- });
790
- for (let i = 0; i < sorted.length && count < maxEntries; i++) {
791
- const name = sorted[i];
792
- const fullPath = join2(d, name);
793
- const isLast = i === sorted.length - 1;
794
- const connector = isLast ? "+-- " : "|-- ";
795
- let isDir;
796
- try {
797
- isDir = statSync2(fullPath).isDirectory();
798
- } catch {
799
- continue;
800
- }
801
- lines.push(prefix + connector + name + (isDir ? "/" : ""));
802
- count++;
803
- if (isDir) {
804
- walk(fullPath, prefix + (isLast ? " " : "| "), depth + 1);
805
- }
806
- }
807
- };
808
- walk(dir, "", 0);
809
- if (count >= maxEntries) lines.push("... (truncated)");
810
- return lines.join("\n");
811
- }
812
- function scanProject(cwd) {
813
- const info = {
814
- type: "unknown",
815
- language: "unknown",
816
- configFiles: [],
817
- directoryStructure: ""
818
- };
819
- const check = (file) => existsSync2(join2(cwd, file));
820
- const configCandidates = [
821
- "package.json",
822
- "tsconfig.json",
823
- "Cargo.toml",
824
- "pyproject.toml",
825
- "setup.py",
826
- "requirements.txt",
827
- "go.mod",
828
- "pom.xml",
829
- "build.gradle",
830
- "build.gradle.kts",
831
- "CMakeLists.txt",
832
- "Makefile",
833
- ".csproj",
834
- ".sln",
835
- "composer.json",
836
- "Gemfile",
837
- "mix.exs",
838
- "deno.json",
839
- "bun.lockb"
840
- ];
841
- info.configFiles = configCandidates.filter(check);
842
- if (check("package.json")) {
843
- info.type = "node";
844
- info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
845
- try {
846
- const pkg = JSON.parse(readFileSync(join2(cwd, "package.json"), "utf-8"));
847
- const scripts = pkg.scripts ?? {};
848
- info.buildCommand = scripts.build ? "npm run build" : void 0;
849
- info.testCommand = scripts.test ? "npm test" : void 0;
850
- info.devCommand = scripts.dev ? "npm run dev" : void 0;
851
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
852
- if (allDeps["react"]) info.framework = "React";
853
- else if (allDeps["vue"]) info.framework = "Vue";
854
- else if (allDeps["@angular/core"]) info.framework = "Angular";
855
- else if (allDeps["next"]) info.framework = "Next.js";
856
- else if (allDeps["nuxt"]) info.framework = "Nuxt";
857
- else if (allDeps["express"]) info.framework = "Express";
858
- else if (allDeps["fastify"]) info.framework = "Fastify";
859
- else if (allDeps["svelte"]) info.framework = "Svelte";
860
- } catch {
861
- }
862
- } else if (check("Cargo.toml")) {
863
- info.type = "rust";
864
- info.language = "Rust";
865
- info.buildCommand = "cargo build";
866
- info.testCommand = "cargo test";
867
- } else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
868
- info.type = "python";
869
- info.language = "Python";
870
- info.testCommand = check("pytest.ini") || check("pyproject.toml") ? "pytest" : "python -m unittest";
871
- } else if (check("go.mod")) {
872
- info.type = "go";
873
- info.language = "Go";
874
- info.buildCommand = "go build ./...";
875
- info.testCommand = "go test ./...";
876
- } else if (check("pom.xml")) {
877
- info.type = "java";
878
- info.language = "Java";
879
- info.buildCommand = "mvn package";
880
- info.testCommand = "mvn test";
881
- } else if (check("build.gradle") || check("build.gradle.kts")) {
882
- info.type = "java";
883
- info.language = "Java/Kotlin";
884
- info.buildCommand = "./gradlew build";
885
- info.testCommand = "./gradlew test";
886
- }
887
- info.directoryStructure = scanDirTree(cwd);
888
- return info;
889
- }
890
- function buildInitPrompt(info, cwd) {
891
- const parts = [
892
- "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.",
893
- "\n## Project Info\n",
894
- `- Working directory: ${cwd}`,
895
- `- Type: ${info.type}`,
896
- `- Language: ${info.language}`
897
- ];
898
- if (info.framework) parts.push(`- Framework: ${info.framework}`);
899
- if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
900
- if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
901
- if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
902
- parts.push(`
903
- ## Detected Config Files
904
- ${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
905
- parts.push(`
906
- ## Directory Structure
907
- \`\`\`
908
- ${info.directoryStructure}
909
- \`\`\``);
910
- parts.push(`
911
- ## Requirements
912
- Please generate a structured Markdown file containing:
913
- 1. Project overview (one-sentence summary)
914
- 2. Tech stack
915
- 3. Project structure description (based on the directory structure above)
916
- 4. Common commands (build, test, dev, etc.)
917
- 5. Code style and conventions (inferred from config files)
918
-
919
- Output the Markdown content directly, do not wrap the entire file in a code block. Keep it concise, within 200 lines.`);
920
- return parts.join("\n");
921
- }
922
-
923
736
  // src/repl/commands/index.ts
924
737
  function fmtCtx(tokens) {
925
738
  if (tokens >= 1e6) return `${Math.round(tokens / 1e5) / 10}M`;
@@ -1489,7 +1302,7 @@ No commands match "${filter}".
1489
1302
  {
1490
1303
  name: "context",
1491
1304
  description: "Show or reload hierarchical context files",
1492
- usage: "/context [reload]",
1305
+ usage: "/context [status|reload]",
1493
1306
  execute(args, ctx) {
1494
1307
  const sub = args[0];
1495
1308
  if (sub === "reload") {
@@ -1499,7 +1312,7 @@ No commands match "${filter}".
1499
1312
  const summary = layers2.map((l) => `${l.level}:${l.charCount}`).join(", ");
1500
1313
  ctx.renderer.printSuccess(`Context reloaded: ${layers2.length} layer(s) (${summary} chars)`);
1501
1314
  } else {
1502
- ctx.renderer.printInfo("No context file found. Place AICLI.md or CLAUDE.md in your project root or ~/.aicli/.");
1315
+ ctx.renderer.printInfo("No context file found. Place AICLI.md, CLAUDE.md, or AGENTS.md in your project root or ~/.aicli/.");
1503
1316
  }
1504
1317
  return;
1505
1318
  }
@@ -1509,16 +1322,16 @@ No commands match "${filter}".
1509
1322
  console.log(" Context layers:");
1510
1323
  const levelLabels = { global: "Global ", project: "Project", local: "Local " };
1511
1324
  for (const l of layers) {
1512
- console.log(` ${levelLabels[l.level] ?? l.level} : ${l.filePath} (${l.charCount} chars)`);
1325
+ console.log(` ${levelLabels[l.level] ?? l.level} : ${l.displayPath ?? l.filePath} (${l.charCount} chars${l.truncated ? ", truncated" : ""})`);
1513
1326
  }
1514
1327
  const totalChars = layers.reduce((s, l) => s + l.charCount, 0);
1515
1328
  console.log(` Total : ${totalChars} chars (${layers.length} layer${layers.length > 1 ? "s" : ""})`);
1516
1329
  } else {
1517
1330
  console.log(" No context files loaded.");
1518
1331
  console.log(" Hierarchical context search order:");
1519
- console.log(" 1. Global : ~/.aicli/AICLI.md (or CLAUDE.md)");
1520
- console.log(" 2. Project : <git-root>/AICLI.md");
1521
- console.log(" 3. Local : <cwd>/AICLI.md (when cwd \u2260 project root)");
1332
+ console.log(" 1. Global : ~/.aicli/AICLI.override.md, AGENTS.override.md, AICLI.md, CLAUDE.md, or AGENTS.md");
1333
+ console.log(" 2. Project : <git-root>/same candidate order");
1334
+ console.log(" 3. Local : <cwd>/same candidate order (when cwd \u2260 project root)");
1522
1335
  }
1523
1336
  console.log();
1524
1337
  }
@@ -1830,16 +1643,16 @@ No tools match "${filter}".
1830
1643
  usage: "/mcp [reconnect [serverId] | trust-project]",
1831
1644
  async execute(args, ctx) {
1832
1645
  if (args[0] === "trust-project") {
1833
- const { join: join6 } = await import("path");
1834
- const { existsSync: existsSync6 } = await import("fs");
1646
+ const { join: join5 } = await import("path");
1647
+ const { existsSync: existsSync5 } = await import("fs");
1835
1648
  const { getGitRoot: getGitRoot2 } = await import("./git-context-EXOEHQSF.js");
1836
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-VTZLKDAG.js");
1649
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-47OR72MV.js");
1837
1650
  const { approveProject, hashMcpFile } = await import("./project-trust-NKYHL3VZ.js");
1838
1651
  const cwd = process.cwd();
1839
1652
  const projectRoot = getGitRoot2(cwd) ?? cwd;
1840
- const mcpPath = join6(projectRoot, MCP_PROJECT_CONFIG_NAME2);
1653
+ const mcpPath = join5(projectRoot, MCP_PROJECT_CONFIG_NAME2);
1841
1654
  console.log();
1842
- if (!existsSync6(mcpPath)) {
1655
+ if (!existsSync5(mcpPath)) {
1843
1656
  console.log(theme.dim(` No .mcp.json in ${projectRoot}.`));
1844
1657
  console.log();
1845
1658
  return;
@@ -2172,9 +1985,9 @@ No tools match "${filter}".
2172
1985
  usage: "/init [--force]",
2173
1986
  async execute(args, ctx) {
2174
1987
  const cwd = process.cwd();
2175
- const targetPath = join3(cwd, "AICLI.md");
1988
+ const targetPath = join2(cwd, "AICLI.md");
2176
1989
  const force = args.includes("--force");
2177
- if (existsSync3(targetPath) && !force) {
1990
+ if (existsSync2(targetPath) && !force) {
2178
1991
  ctx.renderer.printInfo(`AICLI.md already exists at ${targetPath}`);
2179
1992
  ctx.renderer.printInfo("Use /init --force to overwrite, or edit it manually.");
2180
1993
  return;
@@ -2892,7 +2705,7 @@ ${hint}` : "")
2892
2705
  usage: "/test [command|filter]",
2893
2706
  async execute(args, ctx) {
2894
2707
  try {
2895
- const { executeTests } = await import("./run-tests-NDS2IEMK.js");
2708
+ const { executeTests } = await import("./run-tests-UT7RZ7Y3.js");
2896
2709
  const argStr = args.join(" ").trim();
2897
2710
  let testArgs = {};
2898
2711
  if (argStr) {
@@ -3050,7 +2863,7 @@ ${hint}` : "")
3050
2863
  description: "Persistent memory (memory.md) + chat memory recall index (v0.4.89+)",
3051
2864
  usage: "/memory [show|add <text>|clear|path|rebuild|refresh|status|recall <query>|index-clear]",
3052
2865
  async execute(args, ctx) {
3053
- const memoryFile = join3(ctx.config.getConfigDir(), MEMORY_FILE_NAME);
2866
+ const memoryFile = join2(ctx.config.getConfigDir(), MEMORY_FILE_NAME);
3054
2867
  const sub = args[0] ?? "show";
3055
2868
  if (sub === "rebuild" || sub === "refresh") {
3056
2869
  const full = sub === "rebuild";
@@ -3134,11 +2947,11 @@ ${hint}` : "")
3134
2947
  return;
3135
2948
  }
3136
2949
  if (sub === "show" || sub === "view") {
3137
- if (!existsSync3(memoryFile)) {
2950
+ if (!existsSync2(memoryFile)) {
3138
2951
  ctx.renderer.printInfo("Memory is empty (memory.md not found)");
3139
2952
  return;
3140
2953
  }
3141
- const content = readFileSync2(memoryFile, "utf-8").trim();
2954
+ const content = readFileSync(memoryFile, "utf-8").trim();
3142
2955
  if (!content) {
3143
2956
  ctx.renderer.printInfo("Memory is empty");
3144
2957
  return;
@@ -3163,14 +2976,14 @@ ${text}
3163
2976
  `;
3164
2977
  try {
3165
2978
  mkdirSync2(dirname2(memoryFile), { recursive: true });
3166
- const previous = existsSync3(memoryFile) ? readFileSync2(memoryFile, "utf-8") : "";
2979
+ const previous = existsSync2(memoryFile) ? readFileSync(memoryFile, "utf-8") : "";
3167
2980
  atomicWriteFileSync(memoryFile, previous + entry);
3168
2981
  ctx.renderer.printSuccess(`Added to memory: "${text.slice(0, 60)}${text.length > 60 ? "\u2026" : ""}"`);
3169
2982
  } catch (err) {
3170
2983
  ctx.renderer.renderError(`Failed to write memory: ${err instanceof Error ? err.message : String(err)}`);
3171
2984
  }
3172
2985
  } else if (sub === "clear") {
3173
- if (!existsSync3(memoryFile)) {
2986
+ if (!existsSync2(memoryFile)) {
3174
2987
  ctx.renderer.printInfo("Memory is already empty");
3175
2988
  return;
3176
2989
  }
@@ -3206,20 +3019,20 @@ ${text}
3206
3019
  console.log(theme.heading("Config Files:"));
3207
3020
  console.log(` Dir: ${theme.accent(configDir)}`);
3208
3021
  const checkFile = (label, filePath) => {
3209
- const exists = existsSync3(filePath);
3022
+ const exists = existsSync2(filePath);
3210
3023
  const icon = exists ? theme.success("\u2713") : theme.dim("\u2013");
3211
3024
  let extra = "";
3212
3025
  if (exists) {
3213
3026
  try {
3214
- extra = theme.dim(` (${statSync3(filePath).size} bytes)`);
3027
+ extra = theme.dim(` (${statSync2(filePath).size} bytes)`);
3215
3028
  } catch {
3216
3029
  }
3217
3030
  }
3218
3031
  console.log(` ${icon} ${label.padEnd(14)} ${exists ? filePath : theme.dim("(not found)")}${extra}`);
3219
3032
  };
3220
- checkFile("config.json", join3(configDir, "config.json"));
3221
- checkFile("memory.md", join3(configDir, MEMORY_FILE_NAME));
3222
- checkFile("dev-state.md", join3(configDir, "dev-state.md"));
3033
+ checkFile("config.json", join2(configDir, "config.json"));
3034
+ checkFile("memory.md", join2(configDir, MEMORY_FILE_NAME));
3035
+ checkFile("dev-state.md", join2(configDir, "dev-state.md"));
3223
3036
  console.log();
3224
3037
  const mcpManager = ctx.getMcpManager();
3225
3038
  if (mcpManager) {
@@ -3383,9 +3196,9 @@ ${text}
3383
3196
  let newFiles = 0;
3384
3197
  let modifiedFiles = 0;
3385
3198
  for (const [filePath, { earliest }] of fileMap) {
3386
- const currentContent = existsSync3(filePath) ? (() => {
3199
+ const currentContent = existsSync2(filePath) ? (() => {
3387
3200
  try {
3388
- return readFileSync2(filePath, "utf-8");
3201
+ return readFileSync(filePath, "utf-8");
3389
3202
  } catch {
3390
3203
  return null;
3391
3204
  }
@@ -3511,12 +3324,12 @@ Summary: ${fileMap.size} file(s) \u2014 ${newFiles} new, ${modifiedFiles} modifi
3511
3324
  if (scanAll) {
3512
3325
  const metas = ctx.sessions.listSessions();
3513
3326
  console.log(theme.info(` Scanning ${metas.length} session(s)\u2026`));
3514
- const { readFileSync: readFileSync5 } = await import("fs");
3515
- const { join: join6 } = await import("path");
3327
+ const { readFileSync: readFileSync4 } = await import("fs");
3328
+ const { join: join5 } = await import("path");
3516
3329
  const historyDir = ctx.config.getHistoryDir();
3517
3330
  for (const m of metas) {
3518
3331
  try {
3519
- const content = readFileSync5(join6(historyDir, `${m.id}.json`), "utf-8");
3332
+ const content = readFileSync4(join5(historyDir, `${m.id}.json`), "utf-8");
3520
3333
  const hits2 = scanString(content, opts);
3521
3334
  if (hits2.length) {
3522
3335
  filesWithHits++;
@@ -4201,12 +4014,12 @@ Managing ${displayName} API Key`);
4201
4014
  };
4202
4015
 
4203
4016
  // src/repl/custom-commands.ts
4204
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, mkdirSync as mkdirSync3 } from "fs";
4205
- import { join as join4, extname } from "path";
4017
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, mkdirSync as mkdirSync3 } from "fs";
4018
+ import { join as join3, extname } from "path";
4206
4019
  function parseCommandFile(filePath) {
4207
4020
  let content;
4208
4021
  try {
4209
- content = readFileSync3(filePath, "utf-8");
4022
+ content = readFileSync2(filePath, "utf-8");
4210
4023
  } catch {
4211
4024
  return null;
4212
4025
  }
@@ -4230,7 +4043,7 @@ function expandTemplate(template, args) {
4230
4043
  result = result.replace(/\{\{input\}\}/g, args.join(" "));
4231
4044
  result = result.replace(/\{\{file:([^}]+)\}\}/g, (_m, p) => {
4232
4045
  try {
4233
- return readFileSync3(p.trim(), "utf-8");
4046
+ return readFileSync2(p.trim(), "utf-8");
4234
4047
  } catch {
4235
4048
  return `[Error: cannot read ${p.trim()}]`;
4236
4049
  }
@@ -4255,14 +4068,14 @@ var CustomCommandManager = class {
4255
4068
  commands = /* @__PURE__ */ new Map();
4256
4069
  loadCommands() {
4257
4070
  this.commands.clear();
4258
- if (!existsSync4(this.commandsDir)) {
4071
+ if (!existsSync3(this.commandsDir)) {
4259
4072
  mkdirSync3(this.commandsDir, { recursive: true });
4260
4073
  return 0;
4261
4074
  }
4262
4075
  let count = 0;
4263
- for (const file of readdirSync2(this.commandsDir)) {
4076
+ for (const file of readdirSync(this.commandsDir)) {
4264
4077
  if (extname(file) !== ".md") continue;
4265
- const cmd = parseCommandFile(join4(this.commandsDir, file));
4078
+ const cmd = parseCommandFile(join3(this.commandsDir, file));
4266
4079
  if (cmd) {
4267
4080
  this.commands.set(cmd.meta.name, cmd);
4268
4081
  count++;
@@ -4567,17 +4380,17 @@ function parseAtReferences(input2, cwd) {
4567
4380
  const absPath = resolve2(cwd, rawPath);
4568
4381
  const ext = extname2(rawPath).toLowerCase();
4569
4382
  const mime = IMAGE_MIME[ext];
4570
- if (!existsSync5(absPath)) {
4383
+ if (!existsSync4(absPath)) {
4571
4384
  refs.push({ path: rawPath, type: "notfound" });
4572
4385
  continue;
4573
4386
  }
4574
4387
  if (mime) {
4575
- const fileSize = statSync4(absPath).size;
4388
+ const fileSize = statSync3(absPath).size;
4576
4389
  if (fileSize > MAX_IMAGE_BYTES) {
4577
4390
  refs.push({ path: rawPath, type: "toolarge" });
4578
4391
  continue;
4579
4392
  }
4580
- const data = readFileSync4(absPath).toString("base64");
4393
+ const data = readFileSync3(absPath).toString("base64");
4581
4394
  imageParts.push({
4582
4395
  type: "image_url",
4583
4396
  image_url: { url: `data:${mime};base64,${data}` }
@@ -4585,7 +4398,7 @@ function parseAtReferences(input2, cwd) {
4585
4398
  refs.push({ path: rawPath, type: "image" });
4586
4399
  textBody = textBody.replace(match[0], "").trim();
4587
4400
  } else {
4588
- const content = readFileSync4(absPath, "utf-8");
4401
+ const content = readFileSync3(absPath, "utf-8");
4589
4402
  const inlined = `
4590
4403
 
4591
4404
  [File: ${rawPath}]
@@ -4677,6 +4490,7 @@ var Repl = class {
4677
4490
  };
4678
4491
  /** 当前加载的层级上下文(全局/项目/子目录) */
4679
4492
  contextLayers = [];
4493
+ contextLoadResult = null;
4680
4494
  /** 本次会话累计 token 用量 */
4681
4495
  sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
4682
4496
  /** Fold a single-request TokenUsage (with optional cache fields) into sessionTokenUsage + cost tracker.
@@ -4803,19 +4617,19 @@ var Repl = class {
4803
4617
  if (depth > 2 || entryCount >= MAX_TREE_ENTRIES) return;
4804
4618
  let entries;
4805
4619
  try {
4806
- entries = readdirSync3(dir);
4620
+ entries = readdirSync2(dir);
4807
4621
  } catch {
4808
4622
  return;
4809
4623
  }
4810
4624
  const filtered = entries.filter((e) => !SKIP_DIRS_SET.has(e));
4811
4625
  for (let i = 0; i < filtered.length && entryCount < MAX_TREE_ENTRIES; i++) {
4812
4626
  const name = filtered[i];
4813
- const fullPath = join5(dir, name);
4627
+ const fullPath = join4(dir, name);
4814
4628
  const isLast = i === filtered.length - 1;
4815
4629
  const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
4816
4630
  let isDir;
4817
4631
  try {
4818
- isDir = statSync4(fullPath).isDirectory();
4632
+ isDir = statSync3(fullPath).isDirectory();
4819
4633
  } catch {
4820
4634
  continue;
4821
4635
  }
@@ -4837,17 +4651,17 @@ ${treeLines.join("\n")}`
4837
4651
  if (totalChars >= MAX_TOTAL_CHARS) return;
4838
4652
  let entries;
4839
4653
  try {
4840
- entries = readdirSync3(dir);
4654
+ entries = readdirSync2(dir);
4841
4655
  } catch {
4842
4656
  return;
4843
4657
  }
4844
4658
  for (const name of entries) {
4845
4659
  if (totalChars >= MAX_TOTAL_CHARS) break;
4846
4660
  if (SKIP_DIRS_SET.has(name)) continue;
4847
- const fullPath = join5(dir, name);
4661
+ const fullPath = join4(dir, name);
4848
4662
  let st;
4849
4663
  try {
4850
- st = statSync4(fullPath);
4664
+ st = statSync3(fullPath);
4851
4665
  } catch {
4852
4666
  continue;
4853
4667
  }
@@ -4859,7 +4673,7 @@ ${treeLines.join("\n")}`
4859
4673
  if (!TEXT_EXTS.has(ext) && !isSpecial) continue;
4860
4674
  if (st.size > MAX_FILE_CHARS * 3) continue;
4861
4675
  try {
4862
- let content = readFileSync4(fullPath, "utf-8");
4676
+ let content = readFileSync3(fullPath, "utf-8");
4863
4677
  if (content.length > MAX_FILE_CHARS) {
4864
4678
  content = content.slice(0, MAX_FILE_CHARS) + `
4865
4679
  ... (truncated, ${content.length} chars total)`;
@@ -4889,12 +4703,12 @@ ${content}
4889
4703
  */
4890
4704
  addExtraContextDir(dirPath) {
4891
4705
  const absPath = resolve2(dirPath);
4892
- if (!existsSync5(absPath)) {
4706
+ if (!existsSync4(absPath)) {
4893
4707
  return { success: false, charCount: 0, added: false, error: `Directory not found: ${dirPath}` };
4894
4708
  }
4895
4709
  let isDir;
4896
4710
  try {
4897
- isDir = statSync4(absPath).isDirectory();
4711
+ isDir = statSync3(absPath).isDirectory();
4898
4712
  } catch {
4899
4713
  return { success: false, charCount: 0, added: false, error: `Cannot access: ${dirPath}` };
4900
4714
  }
@@ -4917,33 +4731,6 @@ ${content}
4917
4731
  this.extraContextDirs.splice(idx, 1);
4918
4732
  return true;
4919
4733
  }
4920
- /**
4921
- * 在指定目录中按候选文件名列表查找第一个存在且非空的上下文文件。
4922
- * 返回 { filePath, content } 或 null。
4923
- */
4924
- findContextFile(dir, candidates = CONTEXT_FILE_CANDIDATES) {
4925
- for (const candidate of candidates) {
4926
- const fullPath = join5(dir, candidate);
4927
- if (existsSync5(fullPath)) {
4928
- const content = readFileSync4(fullPath, "utf-8").trim();
4929
- if (content) return { filePath: fullPath, content };
4930
- }
4931
- }
4932
- return null;
4933
- }
4934
- /**
4935
- * 加载层级上下文文件系统。
4936
- *
4937
- * 三层级结构(按顺序拼接):
4938
- * 1. 全局层 ~/.aicli/AICLI.md — 所有项目通用的个人偏好
4939
- * 2. 项目层 <git-root>/AICLI.md — 项目级规则(提交到 git 供团队共享)
4940
- * 3. 子目录层 <cwd>/AICLI.md — 当前工作子目录的特定指令
4941
- *
4942
- * contextFile 配置语义:
4943
- * - 'auto'(默认):启用三层级自动发现
4944
- * - false:禁用所有上下文加载
4945
- * - 具体文件名:仅加载 cwd 下的指定文件(回退到旧的单文件行为)
4946
- */
4947
4734
  /**
4948
4735
  * 加载项目级 .mcp.json 文件(位于 git 根目录或 cwd)。
4949
4736
  * 格式兼容 Claude Desktop:`{ "mcpServers": { ... } }`
@@ -4953,10 +4740,10 @@ ${content}
4953
4740
  const cwd = process.cwd();
4954
4741
  const gitRoot = getGitRoot(cwd);
4955
4742
  const projectRoot = gitRoot ?? cwd;
4956
- const mcpPath = join5(projectRoot, MCP_PROJECT_CONFIG_NAME);
4957
- if (!existsSync5(mcpPath)) return null;
4743
+ const mcpPath = join4(projectRoot, MCP_PROJECT_CONFIG_NAME);
4744
+ if (!existsSync4(mcpPath)) return null;
4958
4745
  try {
4959
- const raw = JSON.parse(readFileSync4(mcpPath, "utf-8"));
4746
+ const raw = JSON.parse(readFileSync3(mcpPath, "utf-8"));
4960
4747
  const servers = raw?.mcpServers;
4961
4748
  if (!servers || typeof servers !== "object") {
4962
4749
  process.stderr.write(
@@ -4990,79 +4777,25 @@ ${content}
4990
4777
  }
4991
4778
  }
4992
4779
  loadHierarchicalContext() {
4993
- const setting = this.config.get("contextFile");
4994
- if (setting === false) return { layers: [], mergedContent: "" };
4780
+ const contextConfig = this.config.get("context");
4995
4781
  const cwd = process.cwd();
4996
- if (setting !== "auto") {
4997
- const fullPath = resolve2(cwd, String(setting));
4998
- if (!fullPath.startsWith(resolve2(cwd))) {
4999
- process.stderr.write(
5000
- `[Warning] contextFile path "${setting}" is outside current directory, ignoring.
5001
- `
5002
- );
5003
- return { layers: [], mergedContent: "" };
5004
- }
5005
- if (existsSync5(fullPath)) {
5006
- const content = readFileSync4(fullPath, "utf-8").trim();
5007
- if (content) {
5008
- const layer = {
5009
- level: "project",
5010
- filePath: setting,
5011
- content,
5012
- charCount: content.length
5013
- };
5014
- return { layers: [layer], mergedContent: content };
5015
- }
5016
- }
5017
- return { layers: [], mergedContent: "" };
5018
- }
5019
- const layers = [];
5020
- const configDir = this.config.getConfigDir();
5021
- const globalCtx = this.findContextFile(configDir);
5022
- if (globalCtx) {
5023
- layers.push({
5024
- level: "global",
5025
- filePath: globalCtx.filePath,
5026
- content: globalCtx.content,
5027
- charCount: globalCtx.content.length
5028
- });
5029
- }
5030
4782
  const gitRoot = getGitRoot(cwd);
5031
4783
  const projectRoot = gitRoot ?? cwd;
5032
- const projectCtx = this.findContextFile(projectRoot);
5033
- if (projectCtx) {
5034
- layers.push({
5035
- level: "project",
5036
- filePath: projectCtx.filePath,
5037
- content: projectCtx.content,
5038
- charCount: projectCtx.content.length
5039
- });
5040
- }
5041
- const normalizedCwd = resolve2(cwd);
5042
- const normalizedRoot = resolve2(projectRoot);
5043
- if (normalizedCwd !== normalizedRoot) {
5044
- const localCtx = this.findContextFile(cwd);
5045
- if (localCtx) {
5046
- layers.push({
5047
- level: "local",
5048
- filePath: localCtx.filePath,
5049
- content: localCtx.content,
5050
- charCount: localCtx.content.length
5051
- });
5052
- }
5053
- }
5054
- const mergedContent = layers.map((l) => l.content).join("\n\n---\n\n");
5055
- return { layers, mergedContent };
4784
+ const result = loadContextFiles({
4785
+ cwd,
4786
+ configDir: this.config.getConfigDir(),
4787
+ projectRoot,
4788
+ setting: this.config.get("contextFile"),
4789
+ fallbackFilenames: contextConfig.projectDocFallbackFilenames,
4790
+ maxBytes: contextConfig.projectDocMaxBytes
4791
+ });
4792
+ this.contextLoadResult = result;
4793
+ return { layers: result.layers, mergedContent: result.mergedContent };
5056
4794
  }
5057
- /**
5058
- * 加载持久记忆内容(~/.aicli/memory.md)。
5059
- * 不存在或为空时返回 null。
5060
- * 超过 MEMORY_MAX_CHARS 时只取末尾最新部分。
5061
- */
5062
4795
  loadMemoryContent() {
5063
- const memoryPath = join5(this.config.getConfigDir(), MEMORY_FILE_NAME);
5064
- if (!existsSync5(memoryPath)) return null;
5065
- let content = readFileSync4(memoryPath, "utf-8").trim();
4796
+ const memoryPath = join4(this.config.getConfigDir(), MEMORY_FILE_NAME);
4797
+ if (!existsSync4(memoryPath)) return null;
4798
+ let content = readFileSync3(memoryPath, "utf-8").trim();
5066
4799
  if (!content) return null;
5067
4800
  if (content.length > MEMORY_MAX_CHARS) {
5068
4801
  content = content.slice(-MEMORY_MAX_CHARS);
@@ -5450,14 +5183,14 @@ Session '${this.resumeSessionId}' not found.
5450
5183
  process.stdout.write(theme.dim(` \u{1F50C} Plugins loaded: ${pluginCount} tool(s) from plugins/
5451
5184
  `));
5452
5185
  }
5453
- const skillsDir = join5(this.config.getConfigDir(), SKILLS_DIR_NAME);
5186
+ const skillsDir = join4(this.config.getConfigDir(), SKILLS_DIR_NAME);
5454
5187
  this.skillManager = new SkillManager(skillsDir, this.config.get("ui").skillSizeWarn);
5455
5188
  const skillCount = this.skillManager.loadSkills();
5456
5189
  if (skillCount > 0) {
5457
5190
  process.stdout.write(theme.dim(` \u{1F3AF} Skills: ${skillCount} available (use /skill to manage)
5458
5191
  `));
5459
5192
  }
5460
- const commandsDir = join5(this.config.getConfigDir(), CUSTOM_COMMANDS_DIR_NAME);
5193
+ const commandsDir = join4(this.config.getConfigDir(), CUSTOM_COMMANDS_DIR_NAME);
5461
5194
  this.customCommandManager = new CustomCommandManager(commandsDir);
5462
5195
  const customCmdCount = this.customCommandManager.loadCommands();
5463
5196
  if (customCmdCount > 0) {
@@ -6093,15 +5826,15 @@ Session '${this.resumeSessionId}' not found.
6093
5826
  const dir = normalized.includes("/") ? dirname3(normalized) : ".";
6094
5827
  const prefix = normalized.includes("/") ? basename2(normalized) : normalized;
6095
5828
  const absDir = resolve2(process.cwd(), dir);
6096
- if (!existsSync5(absDir)) return [];
6097
- const entries = readdirSync3(absDir);
5829
+ if (!existsSync4(absDir)) return [];
5830
+ const entries = readdirSync2(absDir);
6098
5831
  const results = [];
6099
5832
  for (const entry of entries) {
6100
5833
  if (entry.startsWith(".")) continue;
6101
5834
  if (!entry.toLowerCase().startsWith(prefix.toLowerCase())) continue;
6102
5835
  try {
6103
- const fullPath = join5(absDir, entry);
6104
- const stat = statSync4(fullPath);
5836
+ const fullPath = join4(absDir, entry);
5837
+ const stat = statSync3(fullPath);
6105
5838
  const rel = dir === "." ? entry : `${dir}/${entry}`;
6106
5839
  results.push(stat.isDirectory() ? `${rel}/` : rel);
6107
5840
  } catch {
@@ -7270,7 +7003,7 @@ program.command("web").description("Start Web UI server with browser-based chat
7270
7003
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
7271
7004
  process.exit(1);
7272
7005
  }
7273
- const { startWebServer } = await import("./server-4URLNC7P.js");
7006
+ const { startWebServer } = await import("./server-E5D54DIZ.js");
7274
7007
  await startWebServer({ port, host: options.host });
7275
7008
  });
7276
7009
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | logout-all <name> | migrate <name>)").action(async (action, username) => {
@@ -7437,16 +7170,16 @@ program.command("sessions").description("List recent conversation sessions").opt
7437
7170
  console.log(footer + "\n");
7438
7171
  });
7439
7172
  program.command("usage").description("Show token + cost usage grouped by provider/model (cross-session)").option("--days <n>", "Only the last N days (inclusive of today)").option("--month <ym>", "Only a specific month, format YYYY-MM (e.g. 2026-06)").option("--json", "Output as JSON (for scripting)").action(async (options) => {
7440
- const { runUsageCli } = await import("./usage-ZNCKGF6U.js");
7173
+ const { runUsageCli } = await import("./usage-P4B2RZKL.js");
7441
7174
  await runUsageCli(options);
7442
7175
  });
7443
7176
  program.command("doctor").description("Health check: API keys, config, MCP, recent crashes, tool usage, disk usage").option("--json", "Output as JSON (for scripting)").option("--reset-stats", "Reset accumulated tool usage statistics").action(async (options) => {
7444
- const { runDoctorCli } = await import("./doctor-cli-IU6FSVLF.js");
7177
+ const { runDoctorCli } = await import("./doctor-cli-32COMA5K.js");
7445
7178
  await runDoctorCli({ json: !!options.json, resetStats: !!options.resetStats });
7446
7179
  });
7447
7180
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
7448
7181
  try {
7449
- const batch = await import("./batch-45WLJUVP.js");
7182
+ const batch = await import("./batch-MFPYHOIL.js");
7450
7183
  switch (action) {
7451
7184
  case "submit":
7452
7185
  if (!arg) {
@@ -7489,7 +7222,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7489
7222
  }
7490
7223
  });
7491
7224
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
7492
- const { startMcpServer } = await import("./server-72SAPLPS.js");
7225
+ const { startMcpServer } = await import("./server-AIHVMLNU.js");
7493
7226
  await startMcpServer({
7494
7227
  allowDestructive: !!options.allowDestructive,
7495
7228
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7498,7 +7231,7 @@ program.command("mcp-serve").description("Start an MCP server over STDIO, exposi
7498
7231
  });
7499
7232
  });
7500
7233
  program.command("ci").description("Headless PR review (code + security) \u2014 reads git/gh diff, optionally posts to PR. Designed for GitHub Actions.").option("--pr <num>", "PR number; diff fetched via `gh pr diff <num>`", (v) => parseInt(v, 10)).option("--base <ref>", "Base ref for `git diff <ref>...HEAD` (ignored when --pr set)").option("--post", "Post review as a PR comment (requires gh CLI + GH_TOKEN, needs --pr)").option("--no-update", "Always create a new comment instead of updating the previous aicli review").option("--skip-code", "Skip the code review section").option("--skip-security", "Skip the security review section").option("--detailed", "Use the detailed code-review prompt").option("--max-diff <n>", "Max diff chars sent to the model (default 30000)", (v) => parseInt(v, 10)).option("--provider <id>", "Override provider (default: config.defaultProvider)").option("--model <id>", "Override model").option("--dry-run", "Print result to stdout instead of posting (overrides --post)").action(async (options) => {
7501
- const { runCi } = await import("./ci-PUE3MQ7J.js");
7234
+ const { runCi } = await import("./ci-QLJUDLMY.js");
7502
7235
  const result = await runCi({
7503
7236
  pr: options.pr,
7504
7237
  base: options.base,
@@ -7644,7 +7377,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7644
7377
  }),
7645
7378
  config.get("customProviders")
7646
7379
  );
7647
- const { startHub } = await import("./hub-4ETK4R2J.js");
7380
+ const { startHub } = await import("./hub-3NWJUCLK.js");
7648
7381
  await startHub(
7649
7382
  {
7650
7383
  topic: topic ?? "",
@@ -7744,7 +7477,27 @@ ${options.prompt}` : options.prompt;
7744
7477
  const maxTokens = modelParams.maxTokens ?? DEFAULT_MAX_TOKENS;
7745
7478
  const timeout = modelParams.timeout ?? 6e4;
7746
7479
  const temperature = modelParams.temperature;
7747
- const systemPrompt = options.system ?? config.get("session").systemPrompt ?? void 0;
7480
+ let systemPrompt = options.system;
7481
+ if (!systemPrompt) {
7482
+ const contextConfig = config.get("context");
7483
+ const cwd = process.cwd();
7484
+ const gitRoot = getGitRoot(cwd);
7485
+ const loaded = loadContextFiles({
7486
+ cwd,
7487
+ configDir: config.getConfigDir(),
7488
+ projectRoot: gitRoot ?? cwd,
7489
+ setting: config.get("contextFile"),
7490
+ fallbackFilenames: contextConfig.projectDocFallbackFilenames,
7491
+ maxBytes: contextConfig.projectDocMaxBytes
7492
+ });
7493
+ const parts = [
7494
+ config.get("session").systemPrompt,
7495
+ loaded.mergedContent ? `# Project Context
7496
+
7497
+ ${loaded.mergedContent}` : void 0
7498
+ ].filter(Boolean);
7499
+ systemPrompt = parts.length > 0 ? parts.join("\n\n---\n\n") : void 0;
7500
+ }
7748
7501
  const isStreamingJson = options.outputFormat === "streaming-json";
7749
7502
  const useStream = options.stream !== false && !options.json && !isStreamingJson;
7750
7503
  try {