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/README.md CHANGED
@@ -43,7 +43,7 @@
43
43
  - **MCP Protocol** — Connect external MCP servers for dynamic tool discovery
44
44
  - **Multi-User Auth** — Web UI supports multiple users with password authentication
45
45
  - **PWA Support** — Install Web UI as a desktop/mobile app, accessible over LAN
46
- - **Hierarchical Context** — 3-layer context files (global / project / subdirectory) auto-injected
46
+ - **Hierarchical Context** — 3-layer context files (global / project / subdirectory) auto-injected; supports native AICLI.md, Claude-compatible CLAUDE.md, and Codex-compatible AGENTS.md with override files
47
47
  - **Headless Mode** — `ai-cli -p "prompt"` for CI/CD pipelines and scripting
48
48
  - **44 REPL Commands** — Session management, checkpointing, code review, security review/scan, rewind, scaffolding, cross-session history search, chat-memory recall, smart model routing (`/route`), and more
49
49
  - **GitHub Actions CI/CD** — Automated testing on Node 20/22 + npm publish on release tags
@@ -370,11 +370,11 @@ ai-cli automatically discovers and injects context files into the system prompt:
370
370
 
371
371
  | Layer | Path | Purpose |
372
372
  |-------|------|---------|
373
- | Global | `~/.aicli/AICLI.md` | Personal preferences across all projects |
374
- | Project | `<git-root>/AICLI.md` | Project rules (commit to git for team sharing) |
375
- | Subdirectory | `<cwd>/AICLI.md` | Directory-specific instructions |
373
+ | Global | `~/.aicli/<context-file>` | Personal preferences across all projects |
374
+ | Project | `<git-root>/<context-file>` | Project rules (commit to git for team sharing) |
375
+ | Subdirectory | `<cwd>/<context-file>` | Directory-specific instructions |
376
376
 
377
- Also supports `CLAUDE.md` as an alternative filename at each layer.
377
+ At each layer, ai-cli loads the first non-empty file in this priority order: `AICLI.override.md`, `AGENTS.override.md`, `AICLI.md`, `CLAUDE.md`, `AGENTS.md`. `AICLI.md` is the native filename; `CLAUDE.md` and `AGENTS.md` are compatibility filenames for Claude Code and Codex-style projects.
378
378
 
379
379
  ### MCP Integration
380
380
 
package/README.zh-CN.md CHANGED
@@ -35,7 +35,7 @@
35
35
  - **MCP 协议** — 接入外部 MCP 服务器,动态发现工具
36
36
  - **多用户认证** — Web UI 支持多用户密码登录
37
37
  - **PWA 支持** — Web UI 可安装为桌面/移动应用,支持局域网访问
38
- - **三层级上下文** — 全局 / 项目 / 子目录上下文文件自动注入
38
+ - **三层级上下文** — 全局 / 项目 / 子目录上下文文件自动注入;支持原生 `AICLI.md`、Claude 兼容 `CLAUDE.md`、Codex 兼容 `AGENTS.md` 及 override 文件
39
39
  - **无头模式** — `aicli -p "提示词"` 用于 CI/CD 管道和脚本
40
40
  - **44 个 REPL 命令** — 会话管理、检查点、代码审查、安全审查/扫描、对话回退、脚手架、聊天记忆召回、智能模型路由(`/route`)等
41
41
  - **GitHub Actions CI/CD** — Node 20/22 自动测试 + Release tag 自动发布 npm
@@ -391,11 +391,11 @@ ai-cli 自动发现并注入上下文文件到 system prompt:
391
391
 
392
392
  | 层级 | 路径 | 用途 |
393
393
  |------|------|------|
394
- | 全局层 | `~/.aicli/AICLI.md` | 所有项目通用的个人偏好 |
395
- | 项目层 | `<git-root>/AICLI.md` | 项目级规则(提交到 git 供团队共享) |
396
- | 子目录层 | `<cwd>/AICLI.md` | 当前子目录的特定指令 |
394
+ | 全局层 | `~/.aicli/<context-file>` | 所有项目通用的个人偏好 |
395
+ | 项目层 | `<git-root>/<context-file>` | 项目级规则(提交到 git 供团队共享) |
396
+ | 子目录层 | `<cwd>/<context-file>` | 当前子目录的特定指令 |
397
397
 
398
- 每层也支持 `CLAUDE.md` 作为备选文件名。
398
+ 每层按以下优先级加载第一个非空文件:`AICLI.override.md` `AGENTS.override.md` → `AICLI.md` → `CLAUDE.md` → `AGENTS.md`。`AICLI.md` 是 ai-cli 原生文件名;`CLAUDE.md` 和 `AGENTS.md` 分别用于兼容 Claude Code 与 Codex 风格项目。
399
399
 
400
400
  ### MCP 集成
401
401
 
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-DUZRQXIP.js";
4
+ } from "./chunk-P6MTFCXB.js";
5
5
  import "./chunk-TZQHYZKT.js";
6
- import "./chunk-UPMBIS4T.js";
6
+ import "./chunk-GRJNQJA5.js";
7
7
  import {
8
8
  atomicWriteFileSync
9
9
  } from "./chunk-IW3Q7AE5.js";
@@ -1,21 +1,180 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  truncateForPersist
4
- } from "./chunk-JVLRMIHA.js";
4
+ } from "./chunk-NEPFADHX.js";
5
5
  import {
6
6
  APP_NAME,
7
7
  CONFIG_DIR_NAME,
8
+ CONTEXT_FILE_CANDIDATES,
9
+ CONTEXT_FILE_MAX_BYTES,
8
10
  DEV_STATE_FILE_NAME,
9
11
  MCP_CALL_TIMEOUT,
10
12
  MCP_CONNECT_TIMEOUT,
11
13
  MCP_PROTOCOL_VERSION,
12
14
  MCP_TOOL_PREFIX,
13
15
  VERSION
14
- } from "./chunk-UPMBIS4T.js";
16
+ } from "./chunk-GRJNQJA5.js";
15
17
  import {
16
18
  atomicWriteFileSync
17
19
  } from "./chunk-IW3Q7AE5.js";
18
20
 
21
+ // src/core/context-files.ts
22
+ import { existsSync, readFileSync } from "fs";
23
+ import { join, relative, resolve } from "path";
24
+ function uniqueNonEmpty(values) {
25
+ const seen = /* @__PURE__ */ new Set();
26
+ const out = [];
27
+ for (const value of values) {
28
+ const trimmed = value.trim();
29
+ if (!trimmed || seen.has(trimmed)) continue;
30
+ seen.add(trimmed);
31
+ out.push(trimmed);
32
+ }
33
+ return out;
34
+ }
35
+ function buildContextFileCandidates(fallbackFilenames = []) {
36
+ return uniqueNonEmpty([...CONTEXT_FILE_CANDIDATES, ...fallbackFilenames]);
37
+ }
38
+ function displayPath(filePath, cwd) {
39
+ const rel = relative(cwd, filePath);
40
+ if (rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\")) {
41
+ return rel;
42
+ }
43
+ return filePath;
44
+ }
45
+ function isInsideOrEqual(parent, child) {
46
+ const rel = relative(resolve(parent), resolve(child));
47
+ return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
48
+ }
49
+ function readContextFile(level, filePath, cwd, maxBytes) {
50
+ const name = filePath.split(/[\\/]/).pop() ?? filePath;
51
+ const shown = displayPath(filePath, cwd);
52
+ try {
53
+ const raw = readFileSync(filePath);
54
+ const byteCount = raw.byteLength;
55
+ if (byteCount === 0) {
56
+ return {
57
+ layer: null,
58
+ skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
59
+ };
60
+ }
61
+ const truncated = byteCount > maxBytes;
62
+ const bytes = truncated ? raw.subarray(0, maxBytes) : raw;
63
+ const content = bytes.toString("utf-8").trim();
64
+ if (!content) {
65
+ return {
66
+ layer: null,
67
+ skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
68
+ };
69
+ }
70
+ return {
71
+ layer: {
72
+ level: level === "single" ? "project" : level,
73
+ filePath,
74
+ displayPath: shown,
75
+ fileName: name,
76
+ content,
77
+ charCount: content.length,
78
+ byteCount,
79
+ truncated
80
+ },
81
+ skipped: truncated ? { level, filePath, displayPath: shown, fileName: name, reason: "too-large", message: `truncated to ${maxBytes} bytes` } : null
82
+ };
83
+ } catch (err) {
84
+ return {
85
+ layer: null,
86
+ skipped: {
87
+ level,
88
+ filePath,
89
+ displayPath: shown,
90
+ fileName: name,
91
+ reason: "read-error",
92
+ message: err instanceof Error ? err.message : String(err)
93
+ }
94
+ };
95
+ }
96
+ }
97
+ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
98
+ const skipped = [];
99
+ for (const candidate of candidates) {
100
+ const filePath = join(dir, candidate);
101
+ if (!existsSync(filePath)) continue;
102
+ const result = readContextFile(level, filePath, cwd, maxBytes);
103
+ if (result.skipped) skipped.push(result.skipped);
104
+ if (result.layer) return { layer: result.layer, skipped };
105
+ }
106
+ return { layer: null, skipped };
107
+ }
108
+ function loadContextFiles(options) {
109
+ const cwd = resolve(options.cwd);
110
+ const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
111
+ const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
112
+ const skipped = [];
113
+ if (options.setting === false) {
114
+ return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
115
+ }
116
+ if (options.setting && options.setting !== "auto") {
117
+ const filePath = resolve(cwd, String(options.setting));
118
+ if (!isInsideOrEqual(cwd, filePath)) {
119
+ skipped.push({
120
+ level: "single",
121
+ filePath,
122
+ displayPath: filePath,
123
+ fileName: String(options.setting),
124
+ reason: "outside-cwd"
125
+ });
126
+ return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
127
+ }
128
+ if (!existsSync(filePath)) {
129
+ skipped.push({
130
+ level: "single",
131
+ filePath,
132
+ displayPath: displayPath(filePath, cwd),
133
+ fileName: String(options.setting),
134
+ reason: "missing"
135
+ });
136
+ return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
137
+ }
138
+ const result = readContextFile("single", filePath, cwd, maxBytes);
139
+ if (result.skipped) skipped.push(result.skipped);
140
+ const layers2 = result.layer ? [result.layer] : [];
141
+ const mergedContent2 = layers2.map((l) => l.content).join("\n\n---\n\n");
142
+ return {
143
+ layers: layers2,
144
+ mergedContent: mergedContent2,
145
+ skipped,
146
+ totalChars: layers2.reduce((sum, l) => sum + l.charCount, 0),
147
+ totalBytes: layers2.reduce((sum, l) => sum + l.byteCount, 0),
148
+ maxBytes,
149
+ candidates
150
+ };
151
+ }
152
+ const layers = [];
153
+ for (const [level, dir] of [
154
+ ["global", options.configDir],
155
+ ["project", options.projectRoot]
156
+ ]) {
157
+ const result = findFirstContextFile(level, dir, cwd, candidates, maxBytes);
158
+ skipped.push(...result.skipped);
159
+ if (result.layer) layers.push(result.layer);
160
+ }
161
+ if (resolve(options.cwd) !== resolve(options.projectRoot)) {
162
+ const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
163
+ skipped.push(...result.skipped);
164
+ if (result.layer) layers.push(result.layer);
165
+ }
166
+ const mergedContent = layers.map((l) => l.content).join("\n\n---\n\n");
167
+ return {
168
+ layers,
169
+ mergedContent,
170
+ skipped,
171
+ totalChars: layers.reduce((sum, l) => sum + l.charCount, 0),
172
+ totalBytes: layers.reduce((sum, l) => sum + l.byteCount, 0),
173
+ maxBytes,
174
+ candidates
175
+ };
176
+ }
177
+
19
178
  // src/mcp/client.ts
20
179
  import { spawn } from "child_process";
21
180
  var McpClient = class {
@@ -151,7 +310,7 @@ var McpClient = class {
151
310
  // 内部方法:JSON-RPC 通信
152
311
  // ══════════════════════════════════════════════════════════════════
153
312
  sendRequest(method, params) {
154
- return new Promise((resolve, reject) => {
313
+ return new Promise((resolve2, reject) => {
155
314
  if (!this.process?.stdin?.writable) {
156
315
  return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
157
316
  }
@@ -175,7 +334,7 @@ var McpClient = class {
175
334
  this.pendingRequests.set(id, {
176
335
  resolve: (result) => {
177
336
  cleanup();
178
- resolve(result);
337
+ resolve2(result);
179
338
  },
180
339
  reject: (error) => {
181
340
  cleanup();
@@ -252,13 +411,13 @@ var McpClient = class {
252
411
  }
253
412
  /** Promise 超时包装 */
254
413
  withTimeout(promise, ms, label) {
255
- return new Promise((resolve, reject) => {
414
+ return new Promise((resolve2, reject) => {
256
415
  const timer = setTimeout(() => {
257
416
  reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
258
417
  }, ms);
259
418
  promise.then((val) => {
260
419
  clearTimeout(timer);
261
- resolve(val);
420
+ resolve2(val);
262
421
  }).catch((err) => {
263
422
  clearTimeout(timer);
264
423
  reject(err);
@@ -535,11 +694,11 @@ var McpManager = class {
535
694
  };
536
695
 
537
696
  // src/skills/manager.ts
538
- import { existsSync, readdirSync, mkdirSync, statSync } from "fs";
539
- import { join } from "path";
697
+ import { existsSync as existsSync2, readdirSync, mkdirSync, statSync } from "fs";
698
+ import { join as join2 } from "path";
540
699
 
541
700
  // src/skills/types.ts
542
- import { readFileSync } from "fs";
701
+ import { readFileSync as readFileSync2 } from "fs";
543
702
  import { basename } from "path";
544
703
  function parseSimpleYaml(yaml) {
545
704
  const result = {};
@@ -561,7 +720,7 @@ function parseYamlArray(value) {
561
720
  function parseSkillFile(filePath) {
562
721
  let raw;
563
722
  try {
564
- raw = readFileSync(filePath, "utf-8");
723
+ raw = readFileSync2(filePath, "utf-8");
565
724
  } catch {
566
725
  return null;
567
726
  }
@@ -604,7 +763,7 @@ var SkillManager = class {
604
763
  /** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
605
764
  loadSkills() {
606
765
  this.skills.clear();
607
- if (!existsSync(this.skillsDir)) {
766
+ if (!existsSync2(this.skillsDir)) {
608
767
  try {
609
768
  mkdirSync(this.skillsDir, { recursive: true });
610
769
  } catch {
@@ -619,14 +778,14 @@ var SkillManager = class {
619
778
  }
620
779
  for (const entry of entries) {
621
780
  let filePath;
622
- const fullPath = join(this.skillsDir, entry);
781
+ const fullPath = join2(this.skillsDir, entry);
623
782
  if (entry.endsWith(".md")) {
624
783
  filePath = fullPath;
625
784
  } else {
626
785
  try {
627
786
  if (statSync(fullPath).isDirectory()) {
628
- const skillMd = join(fullPath, "SKILL.md");
629
- if (existsSync(skillMd)) {
787
+ const skillMd = join2(fullPath, "SKILL.md");
788
+ if (existsSync2(skillMd)) {
630
789
  filePath = skillMd;
631
790
  } else {
632
791
  continue;
@@ -699,6 +858,195 @@ async function setupProxy(configProxy) {
699
858
  }
700
859
  }
701
860
 
861
+ // src/repl/commands/project-init.ts
862
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
863
+ import { join as join3 } from "path";
864
+ var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
865
+ "node_modules",
866
+ ".git",
867
+ "dist",
868
+ "build",
869
+ "out",
870
+ "target",
871
+ ".next",
872
+ ".nuxt",
873
+ "__pycache__",
874
+ ".venv",
875
+ "venv",
876
+ ".tox",
877
+ ".mypy_cache",
878
+ ".pytest_cache",
879
+ ".gradle",
880
+ ".idea",
881
+ ".vscode",
882
+ ".vs",
883
+ "coverage",
884
+ ".cache",
885
+ ".parcel-cache",
886
+ "dist-cjs",
887
+ "release",
888
+ ".output",
889
+ ".turbo",
890
+ "vendor"
891
+ ]);
892
+ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
893
+ const lines = [];
894
+ let count = 0;
895
+ const walk = (d, prefix, depth) => {
896
+ if (depth > maxDepth || count >= maxEntries) return;
897
+ let entries;
898
+ try {
899
+ entries = readdirSync2(d);
900
+ } catch {
901
+ return;
902
+ }
903
+ const filtered = entries.filter((e) => !e.startsWith(".") && !SCAN_SKIP_DIRS.has(e));
904
+ const sorted = filtered.sort((a, b) => {
905
+ let aIsDir = false, bIsDir = false;
906
+ try {
907
+ aIsDir = statSync2(join3(d, a)).isDirectory();
908
+ } catch {
909
+ }
910
+ try {
911
+ bIsDir = statSync2(join3(d, b)).isDirectory();
912
+ } catch {
913
+ }
914
+ if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
915
+ return a.localeCompare(b);
916
+ });
917
+ for (let i = 0; i < sorted.length && count < maxEntries; i++) {
918
+ const name = sorted[i];
919
+ const fullPath = join3(d, name);
920
+ const isLast = i === sorted.length - 1;
921
+ const connector = isLast ? "+-- " : "|-- ";
922
+ let isDir;
923
+ try {
924
+ isDir = statSync2(fullPath).isDirectory();
925
+ } catch {
926
+ continue;
927
+ }
928
+ lines.push(prefix + connector + name + (isDir ? "/" : ""));
929
+ count++;
930
+ if (isDir) {
931
+ walk(fullPath, prefix + (isLast ? " " : "| "), depth + 1);
932
+ }
933
+ }
934
+ };
935
+ walk(dir, "", 0);
936
+ if (count >= maxEntries) lines.push("... (truncated)");
937
+ return lines.join("\n");
938
+ }
939
+ function scanProject(cwd) {
940
+ const info = {
941
+ type: "unknown",
942
+ language: "unknown",
943
+ configFiles: [],
944
+ directoryStructure: ""
945
+ };
946
+ const check = (file) => existsSync3(join3(cwd, file));
947
+ const configCandidates = [
948
+ "package.json",
949
+ "tsconfig.json",
950
+ "Cargo.toml",
951
+ "pyproject.toml",
952
+ "setup.py",
953
+ "requirements.txt",
954
+ "go.mod",
955
+ "pom.xml",
956
+ "build.gradle",
957
+ "build.gradle.kts",
958
+ "CMakeLists.txt",
959
+ "Makefile",
960
+ ".csproj",
961
+ ".sln",
962
+ "composer.json",
963
+ "Gemfile",
964
+ "mix.exs",
965
+ "deno.json",
966
+ "bun.lockb"
967
+ ];
968
+ info.configFiles = configCandidates.filter(check);
969
+ if (check("package.json")) {
970
+ info.type = "node";
971
+ info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
972
+ try {
973
+ const pkg = JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf-8"));
974
+ const scripts = pkg.scripts ?? {};
975
+ info.buildCommand = scripts.build ? "npm run build" : void 0;
976
+ info.testCommand = scripts.test ? "npm test" : void 0;
977
+ info.devCommand = scripts.dev ? "npm run dev" : void 0;
978
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
979
+ if (allDeps["react"]) info.framework = "React";
980
+ else if (allDeps["vue"]) info.framework = "Vue";
981
+ else if (allDeps["@angular/core"]) info.framework = "Angular";
982
+ else if (allDeps["next"]) info.framework = "Next.js";
983
+ else if (allDeps["nuxt"]) info.framework = "Nuxt";
984
+ else if (allDeps["express"]) info.framework = "Express";
985
+ else if (allDeps["fastify"]) info.framework = "Fastify";
986
+ else if (allDeps["svelte"]) info.framework = "Svelte";
987
+ } catch {
988
+ }
989
+ } else if (check("Cargo.toml")) {
990
+ info.type = "rust";
991
+ info.language = "Rust";
992
+ info.buildCommand = "cargo build";
993
+ info.testCommand = "cargo test";
994
+ } else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
995
+ info.type = "python";
996
+ info.language = "Python";
997
+ info.testCommand = check("pytest.ini") || check("pyproject.toml") ? "pytest" : "python -m unittest";
998
+ } else if (check("go.mod")) {
999
+ info.type = "go";
1000
+ info.language = "Go";
1001
+ info.buildCommand = "go build ./...";
1002
+ info.testCommand = "go test ./...";
1003
+ } else if (check("pom.xml")) {
1004
+ info.type = "java";
1005
+ info.language = "Java";
1006
+ info.buildCommand = "mvn package";
1007
+ info.testCommand = "mvn test";
1008
+ } else if (check("build.gradle") || check("build.gradle.kts")) {
1009
+ info.type = "java";
1010
+ info.language = "Java/Kotlin";
1011
+ info.buildCommand = "./gradlew build";
1012
+ info.testCommand = "./gradlew test";
1013
+ }
1014
+ info.directoryStructure = scanDirTree(cwd);
1015
+ return info;
1016
+ }
1017
+ function buildInitPrompt(info, cwd) {
1018
+ const parts = [
1019
+ "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.",
1020
+ "\n## Project Info\n",
1021
+ `- Working directory: ${cwd}`,
1022
+ `- Type: ${info.type}`,
1023
+ `- Language: ${info.language}`
1024
+ ];
1025
+ if (info.framework) parts.push(`- Framework: ${info.framework}`);
1026
+ if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
1027
+ if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
1028
+ if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
1029
+ parts.push(`
1030
+ ## Detected Config Files
1031
+ ${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
1032
+ parts.push(`
1033
+ ## Directory Structure
1034
+ \`\`\`
1035
+ ${info.directoryStructure}
1036
+ \`\`\``);
1037
+ parts.push(`
1038
+ ## Requirements
1039
+ Please generate a structured Markdown file containing:
1040
+ 1. Project overview (one-sentence summary)
1041
+ 2. Tech stack
1042
+ 3. Project structure description (based on the directory structure above)
1043
+ 4. Common commands (build, test, dev, etc.)
1044
+ 5. Code style and conventions (inferred from config files)
1045
+
1046
+ Output the Markdown content directly, do not wrap the entire file in a code block. Keep it concise, within 200 lines.`);
1047
+ return parts.join("\n");
1048
+ }
1049
+
702
1050
  // src/core/git-diff.ts
703
1051
  import { execFileSync } from "child_process";
704
1052
  function readGitDiff(options = {}) {
@@ -798,8 +1146,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
798
1146
  }
799
1147
 
800
1148
  // src/repl/dev-state.ts
801
- import { existsSync as existsSync2, readFileSync as readFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
802
- import { join as join2 } from "path";
1149
+ import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
1150
+ import { join as join4 } from "path";
803
1151
  import { homedir } from "os";
804
1152
  var DEV_STATE_MAX_CHARS = 6e3;
805
1153
  var SNAPSHOT_PROMPT = `You are about to be replaced by a different AI model. Please generate a structured development state snapshot so the next model can continue seamlessly.
@@ -853,11 +1201,11 @@ function sessionHasMeaningfulContent(messages) {
853
1201
  return hasUser && hasAssistant;
854
1202
  }
855
1203
  function getDevStatePath() {
856
- return join2(homedir(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
1204
+ return join4(homedir(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
857
1205
  }
858
1206
  function saveDevState(content) {
859
- const configDir = join2(homedir(), CONFIG_DIR_NAME);
860
- if (!existsSync2(configDir)) {
1207
+ const configDir = join4(homedir(), CONFIG_DIR_NAME);
1208
+ if (!existsSync4(configDir)) {
861
1209
  mkdirSync2(configDir, { recursive: true });
862
1210
  }
863
1211
  let trimmed = content.trim();
@@ -873,13 +1221,13 @@ function saveDevState(content) {
873
1221
  }
874
1222
  function loadDevState() {
875
1223
  const path = getDevStatePath();
876
- if (!existsSync2(path)) return null;
877
- const content = readFileSync2(path, "utf-8").trim();
1224
+ if (!existsSync4(path)) return null;
1225
+ const content = readFileSync4(path, "utf-8").trim();
878
1226
  return content || null;
879
1227
  }
880
1228
  function clearDevState() {
881
1229
  const path = getDevStatePath();
882
- if (existsSync2(path)) {
1230
+ if (existsSync4(path)) {
883
1231
  try {
884
1232
  unlinkSync(path);
885
1233
  } catch {
@@ -888,10 +1236,14 @@ function clearDevState() {
888
1236
  }
889
1237
 
890
1238
  export {
1239
+ scanDirTree,
1240
+ scanProject,
1241
+ buildInitPrompt,
891
1242
  readGitDiff,
892
1243
  parseSimpleYaml,
893
1244
  persistToolRound,
894
1245
  autoTrimSessionIfNeeded,
1246
+ loadContextFiles,
895
1247
  SNAPSHOT_PROMPT,
896
1248
  sessionHasMeaningfulContent,
897
1249
  saveDevState,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CONFIG_DIR_NAME
4
- } from "./chunk-UPMBIS4T.js";
4
+ } from "./chunk-GRJNQJA5.js";
5
5
  import {
6
6
  atomicWriteFileSync
7
7
  } from "./chunk-IW3Q7AE5.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-UPMBIS4T.js";
4
+ } from "./chunk-GRJNQJA5.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.209";
4
+ var VERSION = "0.4.210";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -9,7 +9,8 @@ var HISTORY_DIR_NAME = "history";
9
9
  var PLUGINS_DIR_NAME = "plugins";
10
10
  var SKILLS_DIR_NAME = "skills";
11
11
  var CUSTOM_COMMANDS_DIR_NAME = "commands";
12
- var CONTEXT_FILE_CANDIDATES = ["AICLI.md", "CLAUDE.md"];
12
+ var CONTEXT_FILE_CANDIDATES = ["AICLI.override.md", "AGENTS.override.md", "AICLI.md", "CLAUDE.md", "AGENTS.md"];
13
+ var CONTEXT_FILE_MAX_BYTES = 32 * 1024;
13
14
  var MEMORY_FILE_NAME = "memory.md";
14
15
  var MEMORY_MAX_CHARS = 1e4;
15
16
  var DEV_STATE_FILE_NAME = "dev-state.md";
@@ -95,7 +96,7 @@ var AGENTIC_BEHAVIOR_GUIDELINE = `# Important Behavioral Guidelines
95
96
  - Only begin using write/execute tools when the user **explicitly requests** an action (e.g., "generate", "create", "modify", "run", "start", etc.).
96
97
  - **Stop when the explicit task is done \u2014 do NOT autonomously test, verify, or run what you just created.** If the user asks "write/create/save file X", finish writing the file and STOP. Do not run it, do not test it, do not "verify it works" with bash/run_interactive. The user will run it themselves and tell you if anything is wrong. Only run/test when the user explicitly says "run", "test", "execute", "try it", "verify", "make sure it works", etc.
97
98
  - **Do NOT retry the same failing command with variants.** If a command fails (exit code non-zero), report the failure to the user with a brief diagnosis and STOP. Do not loop through alternate shells, alternate quoting, alternate paths, or alternate Python invocations. The user can decide whether to retry.
98
- - Project context files (CLAUDE.md, AICLI.md) provide background information about the project. They are NOT instructions to start working. Only use them as reference when the user asks a project-related question or task.
99
+ - Project context files (AICLI.md, CLAUDE.md, AGENTS.md, and override variants) provide background information about the project. They are NOT instructions to start working. Only use them as reference when the user asks a project-related question or task.
99
100
  - If you are unsure about the user's intent, use the ask_user tool to confirm with the user, rather than assuming and executing on your own.
100
101
  - **Do NOT abuse ask_user for redundant confirmations**: When the user has already given a clear, explicit instruction (e.g., "write lesson 142", "generate file X", "create the report"), execute it immediately. Do NOT ask "are you sure?" or request details that can be found in project documents. Repeatedly asking the user to confirm wastes their time and is extremely frustrating. Only use ask_user when critical information is genuinely missing and cannot be inferred from context files.`;
101
102
  function buildUserIdentityPrompt(profile) {
@@ -142,6 +143,7 @@ export {
142
143
  SKILLS_DIR_NAME,
143
144
  CUSTOM_COMMANDS_DIR_NAME,
144
145
  CONTEXT_FILE_CANDIDATES,
146
+ CONTEXT_FILE_MAX_BYTES,
145
147
  MEMORY_FILE_NAME,
146
148
  MEMORY_MAX_CHARS,
147
149
  DEV_STATE_FILE_NAME,
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.209";
9
+ var VERSION = "0.4.210";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -14,7 +14,8 @@ var HISTORY_DIR_NAME = "history";
14
14
  var PLUGINS_DIR_NAME = "plugins";
15
15
  var SKILLS_DIR_NAME = "skills";
16
16
  var CUSTOM_COMMANDS_DIR_NAME = "commands";
17
- var CONTEXT_FILE_CANDIDATES = ["AICLI.md", "CLAUDE.md"];
17
+ var CONTEXT_FILE_CANDIDATES = ["AICLI.override.md", "AGENTS.override.md", "AICLI.md", "CLAUDE.md", "AGENTS.md"];
18
+ var CONTEXT_FILE_MAX_BYTES = 32 * 1024;
18
19
  var MEMORY_FILE_NAME = "memory.md";
19
20
  var MEMORY_MAX_CHARS = 1e4;
20
21
  var DEV_STATE_FILE_NAME = "dev-state.md";
@@ -96,7 +97,7 @@ var AGENTIC_BEHAVIOR_GUIDELINE = `# Important Behavioral Guidelines
96
97
  - Only begin using write/execute tools when the user **explicitly requests** an action (e.g., "generate", "create", "modify", "run", "start", etc.).
97
98
  - **Stop when the explicit task is done \u2014 do NOT autonomously test, verify, or run what you just created.** If the user asks "write/create/save file X", finish writing the file and STOP. Do not run it, do not test it, do not "verify it works" with bash/run_interactive. The user will run it themselves and tell you if anything is wrong. Only run/test when the user explicitly says "run", "test", "execute", "try it", "verify", "make sure it works", etc.
98
99
  - **Do NOT retry the same failing command with variants.** If a command fails (exit code non-zero), report the failure to the user with a brief diagnosis and STOP. Do not loop through alternate shells, alternate quoting, alternate paths, or alternate Python invocations. The user can decide whether to retry.
99
- - Project context files (CLAUDE.md, AICLI.md) provide background information about the project. They are NOT instructions to start working. Only use them as reference when the user asks a project-related question or task.
100
+ - Project context files (AICLI.md, CLAUDE.md, AGENTS.md, and override variants) provide background information about the project. They are NOT instructions to start working. Only use them as reference when the user asks a project-related question or task.
100
101
  - If you are unsure about the user's intent, use the ask_user tool to confirm with the user, rather than assuming and executing on your own.
101
102
  - **Do NOT abuse ask_user for redundant confirmations**: When the user has already given a clear, explicit instruction (e.g., "write lesson 142", "generate file X", "create the report"), execute it immediately. Do NOT ask "are you sure?" or request details that can be found in project documents. Repeatedly asking the user to confirm wastes their time and is extremely frustrating. Only use ask_user when critical information is genuinely missing and cannot be inferred from context files.`;
102
103
  function buildUserIdentityPrompt(profile) {
@@ -549,6 +550,7 @@ export {
549
550
  SKILLS_DIR_NAME,
550
551
  CUSTOM_COMMANDS_DIR_NAME,
551
552
  CONTEXT_FILE_CANDIDATES,
553
+ CONTEXT_FILE_MAX_BYTES,
552
554
  MEMORY_FILE_NAME,
553
555
  MEMORY_MAX_CHARS,
554
556
  DEV_STATE_FILE_NAME,