jinzd-ai-cli 0.4.103 → 0.4.105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/{agent-client-6GX6QQDU.js → agent-client-25TIQ6AP.js} +1 -0
  4. package/dist/{auth-MSUWO6SE.js → auth-SC6KHHI3.js} +1 -0
  5. package/dist/{batch-LNTG2IRQ.js → batch-NPK4USGH.js} +3 -2
  6. package/dist/{chat-index-W2UZ34ZI.js → chat-index-7OHUKJY5.js} +1 -0
  7. package/dist/{chat-index-QKFH7ZP6.js → chat-index-ADG2GPCC.js} +1 -0
  8. package/dist/chunk-3RG5ZIWI.js +10 -0
  9. package/dist/{chunk-SN56X6RE.js → chunk-B6NUQVYK.js} +1 -1
  10. package/dist/{chunk-VOWVIR2U.js → chunk-F7XJ67XB.js} +30 -112
  11. package/dist/chunk-HOSJZMQS.js +97 -0
  12. package/dist/{chunk-OVYOYUP7.js → chunk-LVX667WL.js} +89 -36
  13. package/dist/{chunk-JHPSWYO3.js → chunk-LX5FXZVP.js} +7 -4
  14. package/dist/chunk-PDX44BCA.js +11 -0
  15. package/dist/{chunk-RZWWODW7.js → chunk-RFKT3T5S.js} +199 -74
  16. package/dist/{chunk-DGXUO7D4.js → chunk-VOF6OTZB.js} +89 -39
  17. package/dist/constants-HK5BB5EZ.js +78 -0
  18. package/dist/electron-server.js +289 -129
  19. package/dist/{file-checkpoint-CGH6OJVI.js → file-checkpoint-UHSMHCRU.js} +1 -0
  20. package/dist/{file-checkpoint-NKBHGC7L.js → file-checkpoint-ZN7KE3TN.js} +1 -0
  21. package/dist/git-context-7KIP4X2V.js +12 -0
  22. package/dist/{hub-4YGZ4XHN.js → hub-5VFGLTHY.js} +3 -2
  23. package/dist/{hub-server-BYXNQGDY.js → hub-server-AUMVPNU6.js} +1 -0
  24. package/dist/index.js +98 -47
  25. package/dist/{indexer-C7QYYHSZ.js → indexer-XGY7XGJM.js} +1 -0
  26. package/dist/{indexer-O5FCGFBJ.js → indexer-Z6AQTGBK.js} +1 -0
  27. package/dist/project-trust-EBGHD7LE.js +67 -0
  28. package/dist/project-trust-IFM7FXEV.js +68 -0
  29. package/dist/{run-tests-SN74WT4Z.js → run-tests-IMVI43CZ.js} +2 -1
  30. package/dist/{run-tests-3YOJEN2Q.js → run-tests-VQ3YZB75.js} +3 -2
  31. package/dist/{semantic-3KJPAUW6.js → semantic-FR2ZSQLY.js} +1 -0
  32. package/dist/{semantic-YDRPPVWK.js → semantic-UFKVYKFE.js} +3 -2
  33. package/dist/{server-BG4WR6RF.js → server-XDBIWNRW.js} +9 -8
  34. package/dist/{server-TNPDHGQT.js → server-ZVY3CKTJ.js} +65 -28
  35. package/dist/{store-S24SPPDZ.js → store-JDEW743P.js} +1 -0
  36. package/dist/{store-247B3TAU.js → store-Q7NMUCPP.js} +1 -0
  37. package/dist/{task-orchestrator-MUIH3XBY.js → task-orchestrator-UEZOFXQX.js} +9 -8
  38. package/dist/{vector-store-NDUFLNGN.js → vector-store-AK6J3RIA.js} +1 -0
  39. package/dist/{vector-store-QARQ2P6D.js → vector-store-MCQ77OOJ.js} +1 -0
  40. package/package.json +1 -1
  41. package/dist/{chunk-KJLJPUY2.js → chunk-3BICTI5M.js} +3 -3
package/README.md CHANGED
@@ -45,7 +45,7 @@
45
45
  - **PWA Support** — Install Web UI as a desktop/mobile app, accessible over LAN
46
46
  - **Hierarchical Context** — 3-layer context files (global / project / subdirectory) auto-injected
47
47
  - **Headless Mode** — `ai-cli -p "prompt"` for CI/CD pipelines and scripting
48
- - **43 REPL Commands** — Session management, checkpointing, code review, security review/scan, rewind, scaffolding, cross-session history search, chat-memory recall, and more
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
50
50
  - **Cross-Platform** — Windows, macOS, Linux
51
51
 
package/README.zh-CN.md CHANGED
@@ -37,7 +37,7 @@
37
37
  - **PWA 支持** — Web UI 可安装为桌面/移动应用,支持局域网访问
38
38
  - **三层级上下文** — 全局 / 项目 / 子目录上下文文件自动注入
39
39
  - **无头模式** — `aicli -p "提示词"` 用于 CI/CD 管道和脚本
40
- - **43 个 REPL 命令** — 会话管理、检查点、代码审查、安全审查/扫描、对话回退、脚手架、聊天记忆召回等
40
+ - **44 个 REPL 命令** — 会话管理、检查点、代码审查、安全审查/扫描、对话回退、脚手架、聊天记忆召回、智能模型路由(`/route`)等
41
41
  - **GitHub Actions CI/CD** — Node 20/22 自动测试 + Release tag 自动发布 npm
42
42
  - **跨平台** — Windows、macOS、Linux
43
43
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import "./chunk-PDX44BCA.js";
2
3
 
3
4
  // src/hub/agent-client.ts
4
5
  import WebSocket from "ws";
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  AuthManager
4
4
  } from "./chunk-BYNY5JPB.js";
5
+ import "./chunk-PDX44BCA.js";
5
6
  export {
6
7
  AuthManager
7
8
  };
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-SN56X6RE.js";
4
+ } from "./chunk-B6NUQVYK.js";
5
5
  import "./chunk-2ZD3YTVM.js";
6
- import "./chunk-JHPSWYO3.js";
6
+ import "./chunk-LX5FXZVP.js";
7
+ import "./chunk-PDX44BCA.js";
7
8
 
8
9
  // src/cli/batch.ts
9
10
  import Anthropic from "@anthropic-ai/sdk";
@@ -8,6 +8,7 @@ import {
8
8
  searchChatMemory
9
9
  } from "./chunk-ANYYM4CF.js";
10
10
  import "./chunk-KHYD3WXE.js";
11
+ import "./chunk-PDX44BCA.js";
11
12
  export {
12
13
  buildChatIndex,
13
14
  chunkSession,
@@ -7,6 +7,7 @@ import {
7
7
  searchChatMemory
8
8
  } from "./chunk-5S3PIG5O.js";
9
9
  import "./chunk-JV5N65KN.js";
10
+ import "./chunk-3RG5ZIWI.js";
10
11
  export {
11
12
  buildChatIndex,
12
13
  chunkSession,
@@ -0,0 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export {
9
+ __require
10
+ };
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-JHPSWYO3.js";
11
+ } from "./chunk-LX5FXZVP.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -2,16 +2,13 @@
2
2
  import {
3
3
  schemaToJsonSchema,
4
4
  truncateForPersist
5
- } from "./chunk-RZWWODW7.js";
5
+ } from "./chunk-RFKT3T5S.js";
6
6
  import {
7
7
  AuthError,
8
8
  ProviderError,
9
9
  ProviderNotFoundError,
10
10
  RateLimitError
11
11
  } from "./chunk-2ZD3YTVM.js";
12
- import {
13
- redactJson
14
- } from "./chunk-ANYYM4CF.js";
15
12
  import {
16
13
  APP_NAME,
17
14
  CONFIG_DIR_NAME,
@@ -21,7 +18,10 @@ import {
21
18
  MCP_PROTOCOL_VERSION,
22
19
  MCP_TOOL_PREFIX,
23
20
  VERSION
24
- } from "./chunk-JHPSWYO3.js";
21
+ } from "./chunk-LX5FXZVP.js";
22
+ import {
23
+ redactJson
24
+ } from "./chunk-ANYYM4CF.js";
25
25
 
26
26
  // src/providers/claude.ts
27
27
  import Anthropic from "@anthropic-ai/sdk";
@@ -1686,7 +1686,18 @@ function extractClaimedFilePaths(content) {
1686
1686
  const enRe = /(?:saved|written|created)\s+(?:to|as|at)\s+[`'”]?([^\s`'”\n,]+?\.\w{1,6})/gi;
1687
1687
  while ((m = enRe.exec(content)) !== null) add(m[1]);
1688
1688
  const checkRe = /✅[^\n`]*?[`'”]?([^\s`'”\n,,。]+?\.\w{1,6})/g;
1689
- while ((m = checkRe.exec(content)) !== null) add(m[1]);
1689
+ while ((m = checkRe.exec(content)) !== null) {
1690
+ let pos = m.index;
1691
+ let linesBack = 0;
1692
+ while (linesBack < 9 && pos > 0) {
1693
+ pos--;
1694
+ if (content[pos] === "\n") linesBack++;
1695
+ }
1696
+ const windowStart = pos === 0 ? 0 : pos + 1;
1697
+ const lineEndIdx = content.indexOf("\n", m.index + m[0].length);
1698
+ const window = content.slice(windowStart, lineEndIdx === -1 ? void 0 : lineEndIdx);
1699
+ if (actionLineRe.test(window)) add(m[1]);
1700
+ }
1690
1701
  return Array.from(paths);
1691
1702
  }
1692
1703
  function extractWrittenFilePaths(extraMessages) {
@@ -3083,96 +3094,6 @@ var SessionManager = class {
3083
3094
  }
3084
3095
  };
3085
3096
 
3086
- // src/tools/git-context.ts
3087
- import { execSync } from "child_process";
3088
- import { existsSync as existsSync2 } from "fs";
3089
- import { join as join2 } from "path";
3090
- function runGit(cmd, cwd) {
3091
- try {
3092
- return execSync(`git ${cmd}`, {
3093
- cwd,
3094
- encoding: "utf-8",
3095
- stdio: ["pipe", "pipe", "pipe"],
3096
- timeout: 5e3
3097
- }).trim();
3098
- } catch {
3099
- return null;
3100
- }
3101
- }
3102
- function getGitRoot(cwd = process.cwd()) {
3103
- return runGit("rev-parse --show-toplevel", cwd);
3104
- }
3105
- function getGitContext(cwd = process.cwd()) {
3106
- if (!existsSync2(join2(cwd, ".git"))) {
3107
- const result = runGit("rev-parse --git-dir", cwd);
3108
- if (!result) return null;
3109
- }
3110
- const branch = runGit("rev-parse --abbrev-ref HEAD", cwd);
3111
- if (!branch) return null;
3112
- const statusOutput = runGit("status --porcelain", cwd) ?? "";
3113
- const statusLines = statusOutput ? statusOutput.split("\n").filter(Boolean) : [];
3114
- const stagedFiles = [];
3115
- const changedFiles = [];
3116
- for (const line of statusLines) {
3117
- const xy = line.slice(0, 2);
3118
- const file = line.slice(3).trim();
3119
- const indexStatus = xy[0];
3120
- const workStatus = xy[1];
3121
- if (indexStatus && indexStatus !== " " && indexStatus !== "?") {
3122
- stagedFiles.push(`${indexStatus} ${file}`);
3123
- }
3124
- if (workStatus && workStatus !== " ") {
3125
- changedFiles.push(`${workStatus} ${file}`);
3126
- }
3127
- }
3128
- const logOutput = runGit("log --oneline -3", cwd) ?? "";
3129
- const recentCommits = logOutput ? logOutput.split("\n").filter(Boolean) : [];
3130
- const unpushedOutput = runGit("log @{u}..HEAD --oneline", cwd);
3131
- const hasUnpushed = unpushedOutput !== null && unpushedOutput.trim().length > 0;
3132
- return {
3133
- branch,
3134
- changedFiles,
3135
- stagedFiles,
3136
- recentCommits,
3137
- hasUnpushed
3138
- };
3139
- }
3140
- function formatGitContextForPrompt(ctx) {
3141
- const lines = ["# Git Repository Status", ""];
3142
- lines.push(`- **Branch**: \`${ctx.branch}\``);
3143
- if (ctx.stagedFiles.length > 0) {
3144
- lines.push(`- **Staged** (${ctx.stagedFiles.length} files):`);
3145
- for (const f of ctx.stagedFiles.slice(0, 10)) {
3146
- lines.push(` - ${f}`);
3147
- }
3148
- if (ctx.stagedFiles.length > 10) {
3149
- lines.push(` - ... and ${ctx.stagedFiles.length - 10} more`);
3150
- }
3151
- }
3152
- if (ctx.changedFiles.length > 0) {
3153
- lines.push(`- **Modified** (${ctx.changedFiles.length} files):`);
3154
- for (const f of ctx.changedFiles.slice(0, 10)) {
3155
- lines.push(` - ${f}`);
3156
- }
3157
- if (ctx.changedFiles.length > 10) {
3158
- lines.push(` - ... and ${ctx.changedFiles.length - 10} more`);
3159
- }
3160
- }
3161
- if (ctx.stagedFiles.length === 0 && ctx.changedFiles.length === 0) {
3162
- lines.push("- **Working tree**: clean");
3163
- }
3164
- if (ctx.recentCommits.length > 0) {
3165
- lines.push("- **Recent commits**:");
3166
- for (const c of ctx.recentCommits) {
3167
- lines.push(` - ${c}`);
3168
- }
3169
- }
3170
- if (ctx.hasUnpushed) {
3171
- lines.push("- \u26A0\uFE0F Has unpushed commits");
3172
- }
3173
- return lines.join("\n");
3174
- }
3175
-
3176
3097
  // src/mcp/client.ts
3177
3098
  import { spawn } from "child_process";
3178
3099
  var McpClient = class {
@@ -3683,8 +3604,8 @@ var McpManager = class {
3683
3604
  };
3684
3605
 
3685
3606
  // src/skills/manager.ts
3686
- import { existsSync as existsSync3, readdirSync as readdirSync2, mkdirSync as mkdirSync2, statSync } from "fs";
3687
- import { join as join3 } from "path";
3607
+ import { existsSync as existsSync2, readdirSync as readdirSync2, mkdirSync as mkdirSync2, statSync } from "fs";
3608
+ import { join as join2 } from "path";
3688
3609
 
3689
3610
  // src/skills/types.ts
3690
3611
  import { readFileSync as readFileSync2 } from "fs";
@@ -3752,7 +3673,7 @@ var SkillManager = class {
3752
3673
  /** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
3753
3674
  loadSkills() {
3754
3675
  this.skills.clear();
3755
- if (!existsSync3(this.skillsDir)) {
3676
+ if (!existsSync2(this.skillsDir)) {
3756
3677
  try {
3757
3678
  mkdirSync2(this.skillsDir, { recursive: true });
3758
3679
  } catch {
@@ -3767,14 +3688,14 @@ var SkillManager = class {
3767
3688
  }
3768
3689
  for (const entry of entries) {
3769
3690
  let filePath;
3770
- const fullPath = join3(this.skillsDir, entry);
3691
+ const fullPath = join2(this.skillsDir, entry);
3771
3692
  if (entry.endsWith(".md")) {
3772
3693
  filePath = fullPath;
3773
3694
  } else {
3774
3695
  try {
3775
3696
  if (statSync(fullPath).isDirectory()) {
3776
- const skillMd = join3(fullPath, "SKILL.md");
3777
- if (existsSync3(skillMd)) {
3697
+ const skillMd = join2(fullPath, "SKILL.md");
3698
+ if (existsSync2(skillMd)) {
3778
3699
  filePath = skillMd;
3779
3700
  } else {
3780
3701
  continue;
@@ -4028,8 +3949,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
4028
3949
  }
4029
3950
 
4030
3951
  // src/repl/dev-state.ts
4031
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3 } from "fs";
4032
- import { join as join4 } from "path";
3952
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3 } from "fs";
3953
+ import { join as join3 } from "path";
4033
3954
  import { homedir } from "os";
4034
3955
  var DEV_STATE_MAX_CHARS = 6e3;
4035
3956
  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.
@@ -4083,11 +4004,11 @@ function sessionHasMeaningfulContent(messages) {
4083
4004
  return hasUser && hasAssistant;
4084
4005
  }
4085
4006
  function getDevStatePath() {
4086
- return join4(homedir(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
4007
+ return join3(homedir(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
4087
4008
  }
4088
4009
  function saveDevState(content) {
4089
- const configDir = join4(homedir(), CONFIG_DIR_NAME);
4090
- if (!existsSync4(configDir)) {
4010
+ const configDir = join3(homedir(), CONFIG_DIR_NAME);
4011
+ if (!existsSync3(configDir)) {
4091
4012
  mkdirSync3(configDir, { recursive: true });
4092
4013
  }
4093
4014
  let trimmed = content.trim();
@@ -4103,13 +4024,13 @@ function saveDevState(content) {
4103
4024
  }
4104
4025
  function loadDevState() {
4105
4026
  const path = getDevStatePath();
4106
- if (!existsSync4(path)) return null;
4027
+ if (!existsSync3(path)) return null;
4107
4028
  const content = readFileSync3(path, "utf-8").trim();
4108
4029
  return content || null;
4109
4030
  }
4110
4031
  function clearDevState() {
4111
4032
  const path = getDevStatePath();
4112
- if (existsSync4(path)) {
4033
+ if (existsSync3(path)) {
4113
4034
  try {
4114
4035
  unlinkSync2(path);
4115
4036
  } catch {
@@ -4129,9 +4050,6 @@ export {
4129
4050
  ProviderRegistry,
4130
4051
  getContentText,
4131
4052
  SessionManager,
4132
- getGitRoot,
4133
- getGitContext,
4134
- formatGitContextForPrompt,
4135
4053
  getPricing,
4136
4054
  computeCost,
4137
4055
  formatCost,
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/tools/git-context.ts
4
+ import { execSync } from "child_process";
5
+ import { existsSync } from "fs";
6
+ import { join } from "path";
7
+ function runGit(cmd, cwd) {
8
+ try {
9
+ return execSync(`git ${cmd}`, {
10
+ cwd,
11
+ encoding: "utf-8",
12
+ stdio: ["pipe", "pipe", "pipe"],
13
+ timeout: 5e3
14
+ }).trim();
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ function getGitRoot(cwd = process.cwd()) {
20
+ return runGit("rev-parse --show-toplevel", cwd);
21
+ }
22
+ function getGitContext(cwd = process.cwd()) {
23
+ if (!existsSync(join(cwd, ".git"))) {
24
+ const result = runGit("rev-parse --git-dir", cwd);
25
+ if (!result) return null;
26
+ }
27
+ const branch = runGit("rev-parse --abbrev-ref HEAD", cwd);
28
+ if (!branch) return null;
29
+ const statusOutput = runGit("status --porcelain", cwd) ?? "";
30
+ const statusLines = statusOutput ? statusOutput.split("\n").filter(Boolean) : [];
31
+ const stagedFiles = [];
32
+ const changedFiles = [];
33
+ for (const line of statusLines) {
34
+ const xy = line.slice(0, 2);
35
+ const file = line.slice(3).trim();
36
+ const indexStatus = xy[0];
37
+ const workStatus = xy[1];
38
+ if (indexStatus && indexStatus !== " " && indexStatus !== "?") {
39
+ stagedFiles.push(`${indexStatus} ${file}`);
40
+ }
41
+ if (workStatus && workStatus !== " ") {
42
+ changedFiles.push(`${workStatus} ${file}`);
43
+ }
44
+ }
45
+ const logOutput = runGit("log --oneline -3", cwd) ?? "";
46
+ const recentCommits = logOutput ? logOutput.split("\n").filter(Boolean) : [];
47
+ const unpushedOutput = runGit("log @{u}..HEAD --oneline", cwd);
48
+ const hasUnpushed = unpushedOutput !== null && unpushedOutput.trim().length > 0;
49
+ return {
50
+ branch,
51
+ changedFiles,
52
+ stagedFiles,
53
+ recentCommits,
54
+ hasUnpushed
55
+ };
56
+ }
57
+ function formatGitContextForPrompt(ctx) {
58
+ const lines = ["# Git Repository Status", ""];
59
+ lines.push(`- **Branch**: \`${ctx.branch}\``);
60
+ if (ctx.stagedFiles.length > 0) {
61
+ lines.push(`- **Staged** (${ctx.stagedFiles.length} files):`);
62
+ for (const f of ctx.stagedFiles.slice(0, 10)) {
63
+ lines.push(` - ${f}`);
64
+ }
65
+ if (ctx.stagedFiles.length > 10) {
66
+ lines.push(` - ... and ${ctx.stagedFiles.length - 10} more`);
67
+ }
68
+ }
69
+ if (ctx.changedFiles.length > 0) {
70
+ lines.push(`- **Modified** (${ctx.changedFiles.length} files):`);
71
+ for (const f of ctx.changedFiles.slice(0, 10)) {
72
+ lines.push(` - ${f}`);
73
+ }
74
+ if (ctx.changedFiles.length > 10) {
75
+ lines.push(` - ... and ${ctx.changedFiles.length - 10} more`);
76
+ }
77
+ }
78
+ if (ctx.stagedFiles.length === 0 && ctx.changedFiles.length === 0) {
79
+ lines.push("- **Working tree**: clean");
80
+ }
81
+ if (ctx.recentCommits.length > 0) {
82
+ lines.push("- **Recent commits**:");
83
+ for (const c of ctx.recentCommits) {
84
+ lines.push(` - ${c}`);
85
+ }
86
+ }
87
+ if (ctx.hasUnpushed) {
88
+ lines.push("- \u26A0\uFE0F Has unpushed commits");
89
+ }
90
+ return lines.join("\n");
91
+ }
92
+
93
+ export {
94
+ getGitRoot,
95
+ getGitContext,
96
+ formatGitContextForPrompt
97
+ };
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-JHPSWYO3.js";
4
+ } from "./chunk-LX5FXZVP.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
- import { execSync } from "child_process";
7
+ import { execSync, spawnSync } from "child_process";
8
8
  import { existsSync, readFileSync, readdirSync } from "fs";
9
9
  import { join } from "path";
10
10
  import { platform } from "os";
11
11
  import chalk from "chalk";
12
+ var FILTER_WHITELIST = /^[\w.\-/:#\s*?|^()\[\]@+]{1,200}$/;
12
13
  var IS_WINDOWS = platform() === "win32";
13
14
  function detectNodeTestFramework(cwd, pkg) {
14
15
  const devDeps = pkg.devDependencies ?? {};
@@ -238,12 +239,62 @@ function renderColorReport(summary, framework) {
238
239
  }
239
240
  console.log();
240
241
  }
242
+ function needsWindowsShell(file) {
243
+ if (!IS_WINDOWS) return false;
244
+ const lower = file.toLowerCase();
245
+ return lower.endsWith(".cmd") || lower.endsWith(".bat") || lower === "mvn" || lower === "gradle" || lower === "npm" || lower === "npx";
246
+ }
247
+ function buildArgv(detected, filter) {
248
+ switch (detected.type) {
249
+ case "java": {
250
+ const parts2 = detected.command.split(/\s+/);
251
+ const file = parts2[0];
252
+ const rest = parts2.slice(1);
253
+ const args = [...rest];
254
+ if (filter) {
255
+ args.push(detected.framework.startsWith("Maven") ? `-Dtest=${filter}` : `--tests=${filter}`);
256
+ }
257
+ return { file, args };
258
+ }
259
+ case "python":
260
+ return { file: "pytest", args: filter ? ["-v", "-k", filter] : ["-v"] };
261
+ case "rust":
262
+ return { file: "cargo", args: filter ? ["test", filter] : ["test"] };
263
+ case "go":
264
+ return filter ? { file: "go", args: ["test", "./...", "-run", filter] } : { file: "go", args: ["test", "./..."] };
265
+ case "node": {
266
+ if (detected.framework === "vitest") {
267
+ return { file: "npx", args: filter ? ["vitest", "run", "-t", filter] : ["vitest", "run"] };
268
+ }
269
+ if (detected.framework === "jest") {
270
+ return { file: "npx", args: filter ? ["jest", "-t", filter] : ["jest"] };
271
+ }
272
+ if (detected.framework === "mocha") {
273
+ return { file: "npx", args: filter ? ["mocha", "--grep", filter] : ["mocha"] };
274
+ }
275
+ if (detected.framework === "playwright") {
276
+ return { file: "npx", args: filter ? ["playwright", "test", "--grep", filter] : ["playwright", "test"] };
277
+ }
278
+ if (detected.framework === "ava") {
279
+ return { file: "npx", args: filter ? ["ava", "--match", filter] : ["ava"] };
280
+ }
281
+ return filter ? { file: "npm", args: ["test", "--", "--grep", filter] } : { file: "npm", args: ["test"] };
282
+ }
283
+ }
284
+ const parts = detected.command.split(/\s+/);
285
+ return { file: parts[0], args: parts.slice(1) };
286
+ }
241
287
  async function executeTests(args) {
242
288
  const cwd = process.cwd();
243
289
  const customCmd = args["command"] ? String(args["command"]).trim() : "";
244
290
  const filter = args["filter"] ? String(args["filter"]).trim() : "";
291
+ if (filter && !FILTER_WHITELIST.test(filter)) {
292
+ return `Error: filter contains characters that are not allowed in test names. Allowed: word chars, dots, dashes, slashes, colons, parens, spaces, and basic glob/regex meta (* ? | ^). Rejected: quotes, semicolons, backticks, redirects, and shell metacharacters.
293
+ Got: ${filter.slice(0, 60)}${filter.length > 60 ? "..." : ""}`;
294
+ }
245
295
  let command;
246
296
  let framework;
297
+ let argv = null;
247
298
  if (customCmd) {
248
299
  command = customCmd;
249
300
  framework = "custom";
@@ -254,50 +305,52 @@ async function executeTests(args) {
254
305
  }
255
306
  command = detected.command;
256
307
  framework = detected.framework;
257
- if (filter) {
258
- if (detected.type === "java" && command.includes("mvn")) {
259
- command += ` -Dtest="${filter}"`;
260
- } else if (detected.type === "python") {
261
- command += ` -k "${filter}"`;
262
- } else if (detected.type === "rust") {
263
- command += ` ${filter}`;
264
- } else if (detected.type === "go") {
265
- command = `go test ./... -run "${filter}"`;
266
- } else if (detected.type === "node") {
267
- if (detected.framework === "vitest") {
268
- command += ` -t "${filter}"`;
269
- } else if (detected.framework === "jest") {
270
- command += ` -t "${filter}"`;
271
- } else if (detected.framework === "mocha") {
272
- command += ` --grep "${filter}"`;
273
- } else if (detected.framework === "playwright") {
274
- command += ` --grep "${filter}"`;
275
- } else {
276
- command += ` -- --grep "${filter}"`;
277
- }
278
- }
279
- }
308
+ argv = buildArgv(detected, filter);
280
309
  }
281
310
  let output;
282
311
  let exitCode = 0;
283
- try {
284
- const buf = execSync(command, {
312
+ if (argv) {
313
+ const r = spawnSync(argv.file, argv.args, {
285
314
  cwd,
286
315
  timeout: TEST_TIMEOUT,
287
- encoding: "buffer",
288
316
  stdio: ["pipe", "pipe", "pipe"],
317
+ // shell:false is the default for spawnSync; on Windows .cmd/.bat
318
+ // requires shell:true, so we opt-in only for known wrapper names.
319
+ shell: needsWindowsShell(argv.file),
289
320
  env: {
290
321
  ...process.env,
291
322
  ...IS_WINDOWS ? {} : { FORCE_COLOR: "0" }
292
- }
323
+ },
324
+ encoding: "utf-8",
325
+ maxBuffer: 64 * 1024 * 1024
293
326
  });
294
- output = buf.toString("utf-8");
295
- } catch (err) {
296
- const e = err;
297
- exitCode = e.status ?? 1;
298
- const stdout = e.stdout?.toString("utf-8") ?? "";
299
- const stderr = e.stderr?.toString("utf-8") ?? "";
300
- output = stdout + (stderr ? "\n" + stderr : "");
327
+ if (r.error) {
328
+ output = `Failed to launch test runner: ${r.error.message}`;
329
+ exitCode = 1;
330
+ } else {
331
+ exitCode = r.status ?? 1;
332
+ output = (r.stdout ?? "") + (r.stderr ? "\n" + r.stderr : "");
333
+ }
334
+ } else {
335
+ try {
336
+ const buf = execSync(command, {
337
+ cwd,
338
+ timeout: TEST_TIMEOUT,
339
+ encoding: "buffer",
340
+ stdio: ["pipe", "pipe", "pipe"],
341
+ env: {
342
+ ...process.env,
343
+ ...IS_WINDOWS ? {} : { FORCE_COLOR: "0" }
344
+ }
345
+ });
346
+ output = buf.toString("utf-8");
347
+ } catch (err) {
348
+ const e = err;
349
+ exitCode = e.status ?? 1;
350
+ const stdout = e.stdout?.toString("utf-8") ?? "";
351
+ const stderr = e.stderr?.toString("utf-8") ?? "";
352
+ output = stdout + (stderr ? "\n" + stderr : "");
353
+ }
301
354
  }
302
355
  let summary = {
303
356
  tests: 0,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.103";
4
+ var VERSION = "0.4.105";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -14,6 +14,8 @@ var MEMORY_FILE_NAME = "memory.md";
14
14
  var MEMORY_MAX_CHARS = 1e4;
15
15
  var DEV_STATE_FILE_NAME = "dev-state.md";
16
16
  var DEFAULT_MAX_TOKENS = 8192;
17
+ var DEFAULT_TEMPERATURE = 0.7;
18
+ var MAX_HISTORY_DAYS = 30;
17
19
  var DEFAULT_MAX_TOOL_ROUNDS = 200;
18
20
  var DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP = 5e5;
19
21
  var MCP_TOOL_PREFIX = "mcp__";
@@ -68,24 +70,22 @@ Once planning is complete, clearly inform the user: type \`/plan execute\` to be
68
70
  var SUBAGENT_DEFAULT_MAX_ROUNDS = 15;
69
71
  var SUBAGENT_MAX_ROUNDS_LIMIT = 30;
70
72
  var SUBAGENT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
71
- "bash",
72
73
  "read_file",
73
74
  "write_file",
74
75
  "edit_file",
75
76
  "list_dir",
76
77
  "grep_files",
77
78
  "glob_files",
78
- "run_interactive",
79
79
  "web_fetch",
80
80
  "google_search",
81
81
  "write_todos",
82
- "run_tests",
83
82
  "find_symbol",
84
83
  "get_outline",
85
84
  "find_references",
86
85
  "search_code"
87
86
  ]);
88
87
  var CONTEXT_PRESSURE_THRESHOLD = 0.8;
88
+ var CONTEXT_WARNING_THRESHOLD = 0.6;
89
89
  var TEST_TIMEOUT = 3e5;
90
90
  var AGENTIC_BEHAVIOR_GUIDELINE = `# Important Behavioral Guidelines
91
91
 
@@ -146,6 +146,8 @@ export {
146
146
  MEMORY_MAX_CHARS,
147
147
  DEV_STATE_FILE_NAME,
148
148
  DEFAULT_MAX_TOKENS,
149
+ DEFAULT_TEMPERATURE,
150
+ MAX_HISTORY_DAYS,
149
151
  DEFAULT_MAX_TOOL_ROUNDS,
150
152
  DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
151
153
  MCP_TOOL_PREFIX,
@@ -159,6 +161,7 @@ export {
159
161
  SUBAGENT_MAX_ROUNDS_LIMIT,
160
162
  SUBAGENT_ALLOWED_TOOLS,
161
163
  CONTEXT_PRESSURE_THRESHOLD,
164
+ CONTEXT_WARNING_THRESHOLD,
162
165
  TEST_TIMEOUT,
163
166
  AGENTIC_BEHAVIOR_GUIDELINE,
164
167
  buildUserIdentityPrompt,
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ export {
10
+ __require
11
+ };